diff --git a/CMakeLists.txt b/CMakeLists.txt index b7ef1140..d5455484 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(drivers) add_subdirectory(lib) +add_subdirectory(subsys) diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index fef03cca..00000000 --- a/CODEOWNERS +++ /dev/null @@ -1,13 +0,0 @@ -# CODEOWNERS for auto-review assigning in GitHub -# https://help.github.com/en/articles/about-code-owners#codeowners-syntax - -/app @markaj-nordic @MarGasiorek -/nfc_driver @markaj-nordic @MarGasiorek -/zephyr @markaj-nordic @MarGasiorek -/.clang-format @markaj-nordic @MarGasiorek -/.gitignore @markaj-nordic @MarGasiorek -/CMakeLists.txt @markaj-nordic @MarGasiorek -/CODEOWNERS @markaj-nordic -/Kconfig @markaj-nordic @MarGasiorek -/README.md @markaj-nordic @MarGasiorek -/west.yml @markaj-nordic @MarGasiorek diff --git a/Kconfig b/Kconfig index 6675b996..60702d46 100644 --- a/Kconfig +++ b/Kconfig @@ -6,77 +6,4 @@ rsource "drivers/Kconfig" rsource "lib/Kconfig" - -menuconfig NCS_DOOR_LOCK - bool "NCS Door Lock Application [EXPERIMENTAL]" - depends on (SOC_SERIES_NRF54LX || SOC_SERIES_NRF53X || SOC_SERIES_NRF52X) - select EXPERIMENTAL - imply NFC_T4T_APDU - imply POLL - # storage - imply SETTINGS - imply FLASH - imply FLASH_MAP - imply REQUIRES_FULL_LIBCPP - imply REQUIRES_FULL_LIBC - imply NEWLIB_LIBC_NANO if NEWLIB_LIBC - help - This option enables the Door Lock Application which uses the Aliro reader stack. - -if NCS_DOOR_LOCK - -orsource "lib/aliro/interfaces/logger/Kconfig" - -config DOOR_LOCK_BLE_UWB - bool "Aliro Bluetooth LE (BLE) transport together with ultra wideband (UWB)" - help - Enable the Aliro BLE transport protocol (TP). This is the transport layer - used by the Reader to communicate with the User Device. It is used to send and - receive packets over BLE. Additionally the UWB is enabled in order to manage the - ranging between the Reader and the User Device. - -config DISABLE_ALIRO_NFC_TP - bool "Disable NFC transport protocol for development purposes" - help - Disable the NFC transport protocol. This is useful if you want to use Aliro with BLE only. - -if DOOR_LOCK_BLE_UWB - -config DOOR_LOCK_BLE_UWB_MAX_SESSIONS - int "Maximum number of BLE and UWB sessions" - range 1 BT_MAX_CONN - default BT_MAX_CONN - help - The maximum number of BLE and UWB sessions that can be established. - -endif # DOOR_LOCK_BLE_UWB - -rsource "lib/aliro/Kconfig.defconfig" - -config DOOR_LOCK_DFU_BLE_SMP - bool "Enable DFU over SMP" - depends on !CHIP - help - Enables Device Firmware Upgrade over Bluetooth LE. - -config DOOR_LOCK_DFU_BLE_SMP_STANDALONE - bool - default y if DOOR_LOCK_DFU_BLE_SMP && !DOOR_LOCK_BLE_UWB && !DOOR_LOCK_BLE_NUS - help - Automatically enabled when DFU over SMP is enabled standalone (without DOOR_LOCK_BLE_UWB or DOOR_LOCK_BLE_NUS). - This simplifies conditional compilation checks in the code. - -config DOOR_LOCK_BLE_NUS - bool "Enable Nordic Uart Service (NUS)" - depends on !CHIP - help - Enables the Bluetooth LE Nordic Uart Service (NUS). Using NUS service you can control a door lock using pre-defined BLE commands. - -# Source BLE defconfig when any BLE feature is enabled -if DOOR_LOCK_BLE_UWB || DOOR_LOCK_DFU_BLE_SMP || DOOR_LOCK_BLE_NUS - -rsource "lib/aliro/Kconfig.ble.defconfig" - -endif # DOOR_LOCK_BLE_UWB || DOOR_LOCK_DFU_BLE_SMP || DOOR_LOCK_BLE_NUS - -endif # NCS_DOOR_LOCK +rsource "subsys/Kconfig" diff --git a/app/Kconfig b/app/Kconfig index b856a4ef..f91c8cb0 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -4,65 +4,39 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +mainmenu "nRF Door Lock and Access Control Add-on" + module = DOOR_LOCK_APP module-str = NCS_DOOR_LOCK_APP module-dep = LOG module-help = Enables nRF Connect SDK Door Lock Application log messages. source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" -choice DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION - prompt "Access Manager implementation" - default DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT - - config DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT - bool "Access Manager default implementation" - help - Uses the default implementation of the Access Manager - provided by the Door Lock Reference Application. - - config DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_CUSTOM - bool "Access Manager custom implementation" - help - Uses a custom implementation of the Access Manager. - This implementation is based on provided template - that can be tailored to specific requirements. -endchoice - config DOOR_LOCK_CLI - bool "Door Lock CLI support" - default y + bool "CLI support" + default y if (!DOOR_LOCK_RELEASE || !CHIP) select SHELL select REBOOT help Enable support for Door Lock CLI. -config DOOR_LOCK_PRINT_READER_GROUP_IDENTIFIER - bool "Print reader group identifier that should be provisioned into the User Device" - default y - help - Enable printing of the reader group identifier that should be provisioned into the User Device. - -config DOOR_LOCK_USE_TEST_READER_IDENTIFIER - bool "Use test reader identifier" - default y - help - Initialize the reader Group Identifier and Group Sub Identifier to test values. - -config DOOR_LOCK_USE_TEST_KEYS - bool "Use test keys" - default y - help - Initialize the reader keys to test values. +config DOOR_LOCK_RELEASE + bool "Release configuration" + help + In this configuration: + Aliro stack logs are disabled. + RFAL NFC driver logs are disabled. + Shell commands are disabled. + Device reset automatically on fatal error. -rsource "src/aliro/Kconfig" +rsource "src/Kconfig" -if DOOR_LOCK_BLE_NUS -rsource "src/bt_nus/Kconfig" -endif # DOOR_LOCK_BLE_NUS +rsource "Kconfig.defconfig" -if DOOR_LOCK_DFU_BLE_SMP -rsource "src/dfu_smp/Kconfig" -endif # DOOR_LOCK_DFU_BLE_SMP +# Source BLE defconfig when any BLE feature is enabled +if DOOR_LOCK_BLE_UWB || DOOR_LOCK_DFU_BLE_SMP || DOOR_LOCK_BLE_NUS +rsource "Kconfig.ble.defconfig" +endif # DOOR_LOCK_BLE_UWB || DOOR_LOCK_DFU_BLE_SMP || DOOR_LOCK_BLE_NUS if CHIP diff --git a/lib/aliro/Kconfig.ble.defconfig b/app/Kconfig.ble.defconfig similarity index 85% rename from lib/aliro/Kconfig.ble.defconfig rename to app/Kconfig.ble.defconfig index ce835e78..0adf3c6f 100644 --- a/lib/aliro/Kconfig.ble.defconfig +++ b/app/Kconfig.ble.defconfig @@ -7,9 +7,6 @@ config BT default y -config BT_DEVICE_NAME - default "AliroDL" - config BT_PERIPHERAL default y @@ -48,3 +45,7 @@ config BT_BONDABLE config BT_ID_MAX default 2 if BT_BONDABLE + +config BT_MAX_CONN + default 2 if DOOR_LOCK_BLE_UWB && (DOOR_LOCK_BLE_NUS || DOOR_LOCK_DFU_BLE_SMP) + default 1 diff --git a/lib/aliro/Kconfig.defconfig b/app/Kconfig.defconfig similarity index 55% rename from lib/aliro/Kconfig.defconfig rename to app/Kconfig.defconfig index 48d5a952..07a24eb3 100644 --- a/lib/aliro/Kconfig.defconfig +++ b/app/Kconfig.defconfig @@ -4,60 +4,8 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -# crypto default configuration -config MBEDTLS_PSA_CRYPTO_C - default y - -config MBEDTLS_ENABLE_HEAP - default y - -config MBEDTLS_HEAP_SIZE - default 8192 - -config MBEDTLS_X509_LIBRARY - default y - -config PSA_WANT_ALG_ECDSA - default y - -config PSA_WANT_ALG_ECDH - default y - -config PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE - default y - -config PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT - default y - -config PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT - default y - -config PSA_WANT_ECC_SECP_R1_256 - default y - -config PSA_WANT_ALG_SHA_256 - default y - -config PSA_WANT_ALG_HKDF - default y - -config PSA_WANT_ALG_HMAC - default y - -config PSA_WANT_KEY_TYPE_HMAC - default y - -config PSA_WANT_KEY_TYPE_AES - default y - -config PSA_WANT_ALG_ECB_NO_PADDING - default y - -config PSA_WANT_GENERATE_RANDOM - default y - -config PSA_WANT_ALG_GCM - default y +config BT_DEVICE_NAME + default "AliroDL" config TRUSTED_STORAGE default y @@ -73,10 +21,6 @@ endchoice config HW_UNIQUE_KEY_WRITE_ON_CRYPTO_INIT default y if (SOC_NRF5340_CPUAPP || SOC_SERIES_NRF54LX) -# APDU library -config NFC_T4T_APDU - default y - # required by Aliro persistent storage config FLASH default y @@ -91,39 +35,23 @@ config NVS_LOOKUP_CACHE_SIZE default 512 if NVS config NVS_LOOKUP_CACHE_FOR_SETTINGS - default y if NVS + default y if SETTINGS_NVS config ZMS default y if SOC_FLASH_NRF_RRAM config ZMS_LOOKUP_CACHE_SIZE - default 512 if ZMS + default 512 if SETTINGS_ZMS config ZMS_LOOKUP_CACHE_FOR_SETTINGS - default y if ZMS + default y if SETTINGS_ZMS config SETTINGS default y -# required by Aliro CLI -config REBOOT - default y - -config SHELL - default y - config EVENTS default y -config SMF - default y - -config SMF_ANCESTOR_SUPPORT - default y - -config SMF_INITIAL_TRANSITION - default y - config NRF_SECURITY default y @@ -150,12 +78,6 @@ endchoice config SHELL_CMD_BUFF_SIZE default 256 -config ZCBOR - default y - -config ZCBOR_CANONICAL - default y - if !CHIP config MAIN_STACK_SIZE diff --git a/app/boards/nrf52840dk_nrf52840.overlay b/app/boards/nrf52840dk_nrf52840.overlay index b5573c2c..6d6108cf 100644 --- a/app/boards/nrf52840dk_nrf52840.overlay +++ b/app/boards/nrf52840dk_nrf52840.overlay @@ -63,10 +63,16 @@ }; aliases { - access-decision-indicator = &led1; /* green LED2 on nRF52840DK */ + lock-sim-indicator = &led1; /* green LED2 on nRF52840DK */ }; }; &mx25r64 { status = "okay"; }; + +&gpio0 { + status = "okay"; + /* Use PORT event rather than GPIOTE IN event, to save power */ + sense-edge-mask = <0x00000008>; /* P0.03 */ +}; diff --git a/app/boards/nrf52840dk_nrf52840_release.overlay b/app/boards/nrf52840dk_nrf52840_release.overlay new file mode 100644 index 00000000..ff9a0c25 --- /dev/null +++ b/app/boards/nrf52840dk_nrf52840_release.overlay @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "nrf52840dk_nrf52840.overlay" + +&uart1 { + status = "disabled"; +}; + +&adc { + status = "disabled"; +}; + +&i2c0 { + status = "disabled"; +}; + +&i2c1 { + status = "disabled"; +}; + +&pwm0 { + status = "disabled"; +}; + +&nfct { + status = "disabled"; +}; + +&usbd { + status = "disabled"; +}; + diff --git a/app/boards/nrf5340dk_nrf5340_cpuapp.overlay b/app/boards/nrf5340dk_nrf5340_cpuapp.overlay index 901896ed..0c7fbc23 100644 --- a/app/boards/nrf5340dk_nrf5340_cpuapp.overlay +++ b/app/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -10,7 +10,7 @@ }; aliases { - access-decision-indicator = &led1; /* green LED2 on nRF5340DK */ + lock-sim-indicator = &led1; /* green LED2 on nRF5340DK */ }; }; @@ -54,3 +54,9 @@ &mx25r64 { status = "okay"; }; + +&gpio0 { + status = "okay"; + /* Use PORT event rather than GPIOTE IN event, to save power */ + sense-edge-mask = <0x00000010>; /* P0.04 */ +}; diff --git a/app/boards/nrf5340dk_nrf5340_cpuapp_release.overlay b/app/boards/nrf5340dk_nrf5340_cpuapp_release.overlay new file mode 100644 index 00000000..1d8eb71d --- /dev/null +++ b/app/boards/nrf5340dk_nrf5340_cpuapp_release.overlay @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "nrf5340dk_nrf5340_cpuapp.overlay" + +&uart1 { + status = "disabled"; +}; + +&adc { + status = "disabled"; +}; + +&i2c1 { + status = "disabled"; +}; + +&pwm0 { + status = "disabled"; +}; + +&nfct { + status = "disabled"; +}; + +&usbd { + status = "disabled"; +}; + +&spi4 { + status = "disabled"; +}; + diff --git a/app/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/app/boards/nrf54l15dk_nrf54l15_cpuapp.overlay index bd6b0f17..28d03063 100644 --- a/app/boards/nrf54l15dk_nrf54l15_cpuapp.overlay +++ b/app/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -22,7 +22,7 @@ /delete-property/ led0; /delete-property/ mcuboot-led0; mcuboot-button0 = &button1; - access-decision-indicator = &led2; /* green LED2 on nRF54L15DK */ + lock-sim-indicator = &led2; /* green LED2 on nRF54L15DK */ }; nfc_power_switch: nfc_power_switch { @@ -91,3 +91,9 @@ &mx25r64 { status = "okay"; }; + +&gpio0 { + status = "okay"; + /* Use PORT event rather than GPIOTE IN event, to save power */ + sense-edge-mask = <0x00000010>; /* P0.04 */ +}; diff --git a/app/boards/nrf54l15dk_nrf54l15_cpuapp_release.overlay b/app/boards/nrf54l15dk_nrf54l15_cpuapp_release.overlay new file mode 100644 index 00000000..6a0e9e86 --- /dev/null +++ b/app/boards/nrf54l15dk_nrf54l15_cpuapp_release.overlay @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "nrf54l15dk_nrf54l15_cpuapp.overlay" + +&uart30 { + status = "disabled"; +}; + +&adc { + status = "disabled"; +}; + +&pwm20 { + status = "disabled"; +}; + +&nfct { + status = "disabled"; +}; + +&spi22 { + status = "disabled"; +}; + diff --git a/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay b/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay index c8eb2edd..e2d7e3b4 100644 --- a/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay +++ b/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay @@ -57,7 +57,7 @@ /delete-property/ led0; /delete-property/ mcuboot-led0; mcuboot-button0 = &button1; - access-decision-indicator = &led2; /* green LED2 on nRF54LM20DK */ + lock-sim-indicator = &led2; /* green LED2 on nRF54LM20DK */ }; chosen { @@ -77,3 +77,9 @@ &mx25r64 { status = "okay"; }; + +&gpio1 { + status = "okay"; + /* Use PORT event rather than GPIOTE IN event, to save power */ + sense-edge-mask = <0x00004000>; /* P1.14 */ +}; diff --git a/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp_release.overlay b/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp_release.overlay new file mode 100644 index 00000000..cca7a4e7 --- /dev/null +++ b/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp_release.overlay @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "nrf54lm20dk_nrf54lm20a_cpuapp.overlay" + +&uart30 { + status = "disabled"; +}; + +&pwm20 { + status = "disabled"; +}; + +&nfct { + status = "disabled"; +}; + +&usbhs { + status = "disabled"; +}; + +&spi22 { + status = "disabled"; +}; + diff --git a/app/boards/nrf54lm20dk_spi_nrfutil_config.json b/app/boards/nrf54lm20dk_spi_nrfutil_config.json new file mode 100644 index 00000000..fd111fb4 --- /dev/null +++ b/app/boards/nrf54lm20dk_spi_nrfutil_config.json @@ -0,0 +1,17 @@ +{ + "firmware_config": { + "peripheral": "SPIM00" + }, + "pins": { + "sck": 65, + "csn": 69, + "io0": 66, + "io1": 68, + "io2": 67, + "io3": 64 + }, + "flash_size": 67108864, + "sck_frequency": 8000000, + "page_size": 4096, + "address_mode": "MODE24BIT" +} diff --git a/app/pm_static_nrf52840dk_nrf52840.yml b/app/pm_static_nrf52840dk_nrf52840.yml index 9c4aa3cd..01d94d8d 100644 --- a/app/pm_static_nrf52840dk_nrf52840.yml +++ b/app/pm_static_nrf52840dk_nrf52840.yml @@ -35,8 +35,13 @@ mcuboot_secondary: size: 0xf0000 device: MX25R64 region: external_flash -external_flash: +external_nvs: address: 0xf0000 - size: 0x710000 + size: 0x20000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x110000 + size: 0x6f0000 device: MX25R64 region: external_flash diff --git a/app/pm_static_nrf5340dk_nrf5340_cpuapp.yml b/app/pm_static_nrf5340dk_nrf5340_cpuapp.yml index 0b32f012..a1af2561 100644 --- a/app/pm_static_nrf5340dk_nrf5340_cpuapp.yml +++ b/app/pm_static_nrf5340dk_nrf5340_cpuapp.yml @@ -45,9 +45,14 @@ mcuboot_secondary_1: size: 0x40000 device: MX25R64 region: external_flash -external_flash: +external_nvs: address: 0x12F000 - size: 0x6D1000 + size: 0x20000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x14F000 + size: 0x6B1000 device: MX25R64 region: external_flash pcd_sram: diff --git a/app/pm_static_nrf5340dk_nrf5340_cpuapp_uwb_dfu.yml b/app/pm_static_nrf5340dk_nrf5340_cpuapp_uwb_dfu.yml index 6e470a2a..ead4249f 100644 --- a/app/pm_static_nrf5340dk_nrf5340_cpuapp_uwb_dfu.yml +++ b/app/pm_static_nrf5340dk_nrf5340_cpuapp_uwb_dfu.yml @@ -69,12 +69,17 @@ mcuboot_secondary_2: size: 0x81000 device: MX25R64 region: external_flash -external_flash: +external_nvs: address: 0x231000 - size: 0x5cf000 + size: 0x20000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x251000 + size: 0x5af000 device: MX25R64 region: external_flash pcd_sram: address: 0x20000000 size: 0x2000 - region: sram_primary \ No newline at end of file + region: sram_primary diff --git a/app/pm_static_nrf54l15dk_nrf54l15_cpuapp.yml b/app/pm_static_nrf54l15dk_nrf54l15_cpuapp.yml index a8e64916..7c7368ce 100644 --- a/app/pm_static_nrf54l15dk_nrf54l15_cpuapp.yml +++ b/app/pm_static_nrf54l15dk_nrf54l15_cpuapp.yml @@ -38,19 +38,27 @@ mcuboot_secondary: orig_span: &id003 - mcuboot_secondary_pad - mcuboot_secondary_app + device: MX25R64 region: external_flash size: 0x165000 span: *id003 mcuboot_secondary_pad: + device: MX25R64 region: external_flash address: 0x0 size: 0x800 mcuboot_secondary_app: + device: MX25R64 region: external_flash address: 0x800 size: 0x164800 -external_flash: +external_nvs: address: 0x165000 - size: 0x69B000 + size: 0x20000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x185000 + size: 0x67B000 device: MX25R64 region: external_flash diff --git a/app/pm_static_nrf54lm20dk_nrf54lm20a_cpuapp.yml b/app/pm_static_nrf54lm20dk_nrf54lm20a_cpuapp.yml index 3e9051d5..6589334a 100644 --- a/app/pm_static_nrf54lm20dk_nrf54lm20a_cpuapp.yml +++ b/app/pm_static_nrf54lm20dk_nrf54lm20a_cpuapp.yml @@ -38,19 +38,27 @@ mcuboot_secondary: orig_span: &id003 - mcuboot_secondary_pad - mcuboot_secondary_app + device: MX25R64 region: external_flash size: 0x1E3000 span: *id003 mcuboot_secondary_pad: + device: MX25R64 region: external_flash address: 0x0 size: 0x800 mcuboot_secondary_app: + device: MX25R64 region: external_flash address: 0x800 size: 0x1E2800 -external_flash: +external_nvs: address: 0x1E3000 - size: 0x5DB000 + size: 0x20000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x203000 + size: 0x5FD000 device: MX25R64 region: external_flash diff --git a/app/pm_static_nrf54lm20dk_nrf54lm20a_cpuapp_uwb_dfu.yml b/app/pm_static_nrf54lm20dk_nrf54lm20a_cpuapp_uwb_dfu.yml new file mode 100644 index 00000000..778c1177 --- /dev/null +++ b/app/pm_static_nrf54lm20dk_nrf54lm20a_cpuapp_uwb_dfu.yml @@ -0,0 +1,94 @@ +mcuboot: + address: 0x0 + region: flash_primary + size: 0xD000 +mcuboot_pad: + address: 0xD000 + region: flash_primary + size: 0x800 +app: + address: 0xD800 + region: flash_primary + size: 0x1E2800 +mcuboot_primary: + address: 0xD000 + orig_span: &id001 + - app + - mcuboot_pad + region: flash_primary + size: 0x1E3000 + span: *id001 +mcuboot_primary_app: + address: 0xD800 + orig_span: &id002 + - app + region: flash_primary + size: 0x1E2800 + span: *id002 +factory_data: + address: 0x1F0000 + region: flash_primary + size: 0x1000 +settings_storage: + address: 0x1F1000 + region: flash_primary + size: 0xC000 +mcuboot_secondary: + address: 0x0 + orig_span: &id003 + - mcuboot_secondary_pad + - mcuboot_secondary_app + device: MX25R64 + region: external_flash + size: 0x1E3000 + span: *id003 +mcuboot_secondary_pad: + device: MX25R64 + region: external_flash + address: 0x0 + size: 0x800 +mcuboot_secondary_app: + device: MX25R64 + region: external_flash + address: 0x800 + size: 0x1E2800 +qm35_fw_mcuboot_pad: + address: 0x1E3000 + size: 0x800 + device: MX25R64 + region: external_flash +qm35_fw: + address: 0x1E3800 + size: 0x80000 + device: MX25R64 + region: external_flash +qm35_fw_pad: + address: 0x263800 + size: 0x800 + device: MX25R64 + region: external_flash +mcuboot_primary_1: + orig_span: &id004 + - qm35_fw_mcuboot_pad + - qm35_fw + - qm35_fw_pad + span: *id004 + address: 0x1E3000 + size: 0x81000 + device: MX25R64 + region: external_flash +mcuboot_secondary_1: + address: 0x264000 + size: 0x81000 + device: MX25R64 + region: external_flash +external_nvs: + address: 0x2E5000 + size: 0x20000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x305000 + size: 0x4FB000 + device: MX25R64 + region: external_flash diff --git a/app/prj.conf b/app/prj.conf index 7c27964b..e29f7b12 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -8,7 +8,7 @@ # Aliro # -CONFIG_NCS_DOOR_LOCK=y +CONFIG_NCS_ALIRO=y # Basic system configuration CONFIG_CPP=y @@ -36,6 +36,8 @@ CONFIG_LOG_BACKEND_SHOW_COLOR=n CONFIG_DOOR_LOCK_APP_LOG_LEVEL_INF=y CONFIG_BT_HCI_CORE_LOG_LEVEL_ERR=y CONFIG_BT_HCI_DRIVER_LOG_LEVEL_ERR=y +CONFIG_MATTER_LOG_LEVEL_INF=y +CONFIG_CHIP_APP_LOG_LEVEL=3 # Disable unused shell features CONFIG_SHELL_VT100_COLORS=n @@ -53,9 +55,6 @@ CONFIG_SHELL_BACKEND_SERIAL_TX_RING_BUFFER_SIZE=1024 # Adjust logs queue size CONFIG_SHELL_BACKEND_SERIAL_LOG_MESSAGE_QUEUE_SIZE=2048 -# RFAL worker stack size (TODO: optimize) -CONFIG_RFAL_WORKER_THREAD_STACK_SIZE=8192 - # Workaournd for Murata issue: # https://github.com/csa-access-control/aliro-actuator/issues/114 CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n @@ -69,6 +68,9 @@ CONFIG_BT_L2CAP_TX_MTU=267 # Matter # +CONFIG_CHIP_LIB_SHELL=y +CONFIG_NCS_SAMPLE_MATTER_TEST_SHELL=y + # 3003 CONFIG_CHIP_DEVICE_DISCRIMINATOR=0xBBB CONFIG_CHIP_DEVICE_SPAKE2_PASSCODE=30033003 @@ -78,16 +80,9 @@ CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 # Suspend devices when the CPU goes into sleep CONFIG_PM_DEVICE=y - -# Disable enabling PM runtime for all devices by default, as suspending UART -# causes SHELL to be non-responsive. -CONFIG_PM_DEVICE_RUNTIME_DEFAULT_ENABLE=n +CONFIG_PM_DEVICE_RUNTIME=y # Try to disable unused RAM blocks to reduce power consumption CONFIG_RAM_POWER_DOWN_LIBRARY=y -# Reduce application size -CONFIG_MATTER_LOG_LEVEL_INF=y -CONFIG_CHIP_APP_LOG_LEVEL=3 - CONFIG_LOCK_MAX_CREDENTIAL_LENGTH=65 diff --git a/app/prj_release.conf b/app/prj_release.conf new file mode 100644 index 00000000..79be8984 --- /dev/null +++ b/app/prj_release.conf @@ -0,0 +1,74 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# +# Aliro +# + +CONFIG_NCS_ALIRO=y + +CONFIG_NCS_ALIRO_RELEASE=y + +CONFIG_DOOR_LOCK_RELEASE=y + +# Basic system configuration +CONFIG_CPP=y +CONFIG_STD_CPP17=y +CONFIG_REQUIRES_FULL_LIBCPP=y + +# Stack sizes +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=3072 + +#logging +CONFIG_LOG=n +CONFIG_USE_SEGGER_RTT=n + +# Workaround for Murata issue: +# https://github.com/csa-access-control/aliro-actuator/issues/114 +CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n + +# Override default values to match Aliro requirements for APDU message sizes. +CONFIG_BT_BUF_ACL_RX_SIZE=271 +CONFIG_BT_BUF_ACL_TX_SIZE=271 +CONFIG_BT_L2CAP_TX_MTU=267 + +# +# Matter +# + +# 3003 +CONFIG_CHIP_DEVICE_DISCRIMINATOR=0xBBB +CONFIG_CHIP_DEVICE_SPAKE2_PASSCODE=30033003 + +# 32774 == 0x8006 (example lock-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 + +# Suspend devices when the CPU goes into sleep +CONFIG_PM_DEVICE=y + +# Try to disable unused RAM blocks to reduce power consumption +CONFIG_RAM_POWER_DOWN_LIBRARY=y + +CONFIG_LOCK_MAX_CREDENTIAL_LENGTH=65 + +CONFIG_ASSERT=n +CONFIG_ASSERT_VERBOSE=n +CONFIG_ASSERT_NO_FILE_INFO=y + +CONFIG_RESET_ON_FATAL_ERROR=y + +CONFIG_UART_CONSOLE=n +CONFIG_SERIAL=n +CONFIG_SHELL=n +CONFIG_NCS_SAMPLE_MATTER_TEST_SHELL=n +CONFIG_LOG_MODE_MINIMAL=n + +CONFIG_THREAD_NAME=n +CONFIG_BOOT_BANNER=n +CONFIG_PRINTK=n +CONFIG_PRINTK_SYNC=n +CONFIG_CHIP_LIB_SHELL=n +CONFIG_OPENTHREAD_SHELL=n diff --git a/app/sample.yaml b/app/sample.yaml index 8f096f5d..75127337 100644 --- a/app/sample.yaml +++ b/app/sample.yaml @@ -5,66 +5,105 @@ common: sysbuild: true build_only: true tests: - app.nfc_door_lock: - platform_allow: &platforms_all - - nrf52840dk/nrf52840 - - nrf5340dk/nrf5340/cpuapp - - nrf54l15dk/nrf54l15/cpuapp - - nrf54lm20dk/nrf54lm20a/cpuapp - integration_platforms: *platforms_all - app.door_lock_ble_uwb: - platform_allow: &platforms_uwb - - nrf5340dk/nrf5340/cpuapp - - nrf54lm20dk/nrf54lm20a/cpuapp - integration_platforms: *platforms_uwb + app.door_lock.nfc-ble-uwb.bt_nus-dfu_smp-exp_fast-step_up: + platform_allow: &id001 + - nrf5340dk/nrf5340/cpuapp + integration_platforms: *id001 extra_args: - - app_SNIPPET=uwb_qm35 - app.nfc_door_lock.matter: - platform_allow: *platforms_all - integration_platforms: *platforms_all + - app_SNIPPET="uwb_qm35;dfu_smp;bt_nus" + - CONFIG_DFU_SMP_LOG_LEVEL_DBG=y + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + app.door_lock.nfc-ble-uwb.dfu_smp-matter-exp_fast-step_up: + platform_allow: &id002 + - nrf54lm20dk/nrf54lm20a/cpuapp + integration_platforms: *id002 extra_args: - - SNIPPET=matter - app.door_lock_ble_uwb.matter: - platform_allow: *platforms_uwb - integration_platforms: *platforms_uwb + - app_SNIPPET="uwb_qm35" + - SNIPPET=matter + - CONFIG_CHIP_DFU_OVER_BT_SMP=y + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + app.door_lock.nfc-ble-uwb.dfu_smp-matter-step_up: + platform_allow: *id002 + integration_platforms: *id002 extra_args: - - SNIPPET=matter - - app_SNIPPET=uwb_qm35 - app.door_lock_ble_uwb.matter.bt_nus: - platform_allow: *platforms_uwb - integration_platforms: *platforms_uwb + - app_SNIPPET="uwb_qm35" + - SNIPPET=matter + - CONFIG_CHIP_DFU_OVER_BT_SMP=y + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=n + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + app.door_lock.nfc.dfu_smp-matter-step_up: + platform_allow: &id003 + - nrf54l15dk/nrf54l15/cpuapp + - nrf52840dk/nrf52840 + - nrf5340dk/nrf5340/cpuapp + integration_platforms: *id003 extra_args: - - SNIPPET=matter - - app_SNIPPET=uwb_qm35 - - CONFIG_CHIP_NUS=y - app.door_lock_ble_uwb.exp_fast: - platform_allow: *platforms_uwb - integration_platforms: *platforms_uwb + - SNIPPET=matter + - CONFIG_CHIP_DFU_OVER_BT_SMP=y + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=n + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + app.door_lock.nfc-ble-uwb.dfu_smp-step_up: + platform_allow: *id002 + integration_platforms: *id002 extra_args: - - app_SNIPPET=uwb_qm35 - - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y - app.nfc_door_lock.dfu_smp: - platform_allow: &platforms_5340_54l15 - - nrf5340dk/nrf5340/cpuapp - - nrf54l15dk/nrf54l15/cpuapp - integration_platforms: *platforms_5340_54l15 + - app_SNIPPET="uwb_qm35;dfu_smp" + - CONFIG_DFU_SMP_LOG_LEVEL_DBG=y + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=n + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + app.door_lock.nfc.dfu_smp-step_up: + platform_allow: *id003 + integration_platforms: *id003 extra_args: - - app_SNIPPET=dfu_smp - app.nfc_door_lock_ble_uwb.matter.exp_fast.dfu_smp: - platform_allow: - - nrf5340dk/nrf5340/cpuapp - integration_platforms: - - nrf5340dk/nrf5340/cpuapp + - app_SNIPPET=dfu_smp + - CONFIG_DFU_SMP_LOG_LEVEL_DBG=y + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=n + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + app.door_lock.nfc-ble-uwb.exp_fast-step_up-ca_cert: + platform_allow: &id004 + - nrf5340dk/nrf5340/cpuapp + - nrf54lm20dk/nrf54lm20a/cpuapp + integration_platforms: *id004 extra_args: - - app_SNIPPET=dfu_smp - - SNIPPET=matter - - app_SNIPPET=uwb_qm35 - - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y - app.nfc_door_lock_ble_uwb.step_up: - platform_allow: - - nrf5340dk/nrf5340/cpuapp - integration_platforms: - - nrf5340dk/nrf5340/cpuapp + - app_SNIPPET=uwb_qm35 + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + - CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA=y + - CONFIG_DOOR_LOCK_READER_CERTIFICATE=y + app.door_lock.nfc.exp_fast-step_up-ca_cert: + platform_allow: &id005 + - nrf52840dk/nrf52840 + - nrf5340dk/nrf5340/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + - nrf54lm20dk/nrf54lm20a/cpuapp + integration_platforms: *id005 extra_args: - - app_SNIPPET=uwb_qm35 - - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + - CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA=y + - CONFIG_DOOR_LOCK_READER_CERTIFICATE=y + smoke.door_lock.nfc-ble-uwb.matter-exp_fast-step_up: + platform_allow: *id001 + integration_platforms: *id001 + extra_args: + - app_SNIPPET="uwb_qm35" + - SNIPPET=matter + - CONFIG_DFU_SMP_LOG_LEVEL_DBG=y + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y + smoke.door_lock.nfc-ble-uwb: + platform_allow: *id002 + integration_platforms: *id002 + extra_args: + - app_SNIPPET=uwb_qm35 + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=n + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=n + smoke.door_lock.nfc.bt_nus-step_up: + platform_allow: &id006 + - nrf52840dk/nrf52840 + integration_platforms: *id006 + extra_args: + - app_SNIPPET=bt_nus + - CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=n + - CONFIG_DOOR_LOCK_STEP_UP_PHASE=y diff --git a/app/src/Kconfig b/app/src/Kconfig new file mode 100644 index 00000000..c40fa51b --- /dev/null +++ b/app/src/Kconfig @@ -0,0 +1,10 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +rsource "aliro/Kconfig" +rsource "bt_nus/Kconfig" +rsource "dfu_smp/Kconfig" + diff --git a/app/src/aliro/CMakeLists.txt b/app/src/aliro/CMakeLists.txt index 68ae0da0..bef4bc4d 100644 --- a/app/src/aliro/CMakeLists.txt +++ b/app/src/aliro/CMakeLists.txt @@ -4,16 +4,25 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -file(GLOB app_sources CONFIGURE_DEPENDS *.cpp) +target_sources(app PRIVATE + init.cpp +) +if(NOT CONFIG_CHIP) + set(ALIRO_STANDALONE TRUE) +endif() + +target_sources_ifdef(ALIRO_STANDALONE app PRIVATE + aliro_state_control.cpp +) + +add_subdirectory(access_manager) +add_subdirectory(aliro_work) +add_subdirectory(interface_impl) +add_subdirectory(lock_sim) add_subdirectory(platform) add_subdirectory(storage) add_subdirectory_ifdef(CONFIG_DOOR_LOCK_STEP_UP_PHASE access_document) -add_subdirectory_ifdef(CONFIG_DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT access_manager_impl_default) -add_subdirectory_ifdef(CONFIG_DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_CUSTOM access_manager_impl_custom) add_subdirectory_ifdef(CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE kpersistent_manager) add_subdirectory_ifdef(CONFIG_DOOR_LOCK_CLI cli) -add_subdirectory_ifndef(CONFIG_CHIP lock_sim) - -target_sources(app PRIVATE ${app_sources}) diff --git a/app/src/aliro/Kconfig b/app/src/aliro/Kconfig index 7214c0f5..15a21510 100644 --- a/app/src/aliro/Kconfig +++ b/app/src/aliro/Kconfig @@ -4,6 +4,28 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +menu "Aliro features" + +config DOOR_LOCK_BLE_UWB + bool "Aliro Bluetooth LE (BLE) transport together with ultra wideband (UWB)" + select NCS_ALIRO_BLE_UWB + help + Enable the Aliro BLE transport protocol (TP). This is the transport layer + used by the Reader to communicate with the User Device. It is used to send and + receive packets over BLE. Additionally the UWB is enabled in order to manage the + ranging between the Reader and the User Device. + +if DOOR_LOCK_BLE_UWB + +config DOOR_LOCK_BLE_UWB_MAX_SESSIONS + int "Maximum number of BLE and UWB sessions" + range 1 BT_MAX_CONN + default BT_MAX_CONN + help + The maximum number of BLE and UWB sessions that can be established. + +endif # DOOR_LOCK_BLE_UWB + config DOOR_LOCK_EXPEDITED_FAST_PHASE bool "Support for Aliro Expedited-fast Phase" default y if CHIP @@ -14,13 +36,14 @@ config DOOR_LOCK_EXPEDITED_FAST_PHASE config DOOR_LOCK_STEP_UP_PHASE bool "Support for Aliro Step-up Phase" default y if CHIP + select DOOR_LOCK_EXTERNAL_NVS if !SOC_SERIES_NRF52X help If enabled, the Step-up phase is supported. The user authentication is based on the Access Document. config DOOR_LOCK_CREDENTIAL_ISSUER_CA bool "Support for Credential Issuer CA public key" - default y if DOOR_LOCK_STEP_UP_PHASE + default y if DOOR_LOCK_STEP_UP_PHASE && !CHIP help If enabled, the Credential Issuer CA public key is supported. The Credential Issuer CA public key is used to verify the signature of the Credential Issuer certificate. @@ -42,16 +65,16 @@ config DOOR_LOCK_READER_CERTIFICATE_MAX_SIZE endif # DOOR_LOCK_READER_CERTIFICATE +endmenu # Aliro features + rsource "platform/Kconfig" if DOOR_LOCK_CLI rsource "cli/Kconfig" endif -if DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT -rsource "access_manager_impl_default/Kconfig" -endif - +rsource "access_manager/Kconfig" rsource "kpersistent_manager/Kconfig" rsource "lock_sim/Kconfig" rsource "storage/Kconfig" +rsource "aliro_work/Kconfig" diff --git a/app/src/aliro/access_manager_impl_custom/CMakeLists.txt b/app/src/aliro/access_manager/CMakeLists.txt similarity index 100% rename from app/src/aliro/access_manager_impl_custom/CMakeLists.txt rename to app/src/aliro/access_manager/CMakeLists.txt diff --git a/app/src/aliro/access_manager_impl_default/Kconfig b/app/src/aliro/access_manager/Kconfig similarity index 98% rename from app/src/aliro/access_manager_impl_default/Kconfig rename to app/src/aliro/access_manager/Kconfig index b0fd1d35..a193b009 100644 --- a/app/src/aliro/access_manager_impl_default/Kconfig +++ b/app/src/aliro/access_manager/Kconfig @@ -4,6 +4,8 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +menu "Access Manager" + config DOOR_LOCK_ACCESS_MANAGER_CREDENTIAL_ISSUER_MAX_STORED_KEYS int "Maximum number of stored Credential Issuer public keys" default 5 if DOOR_LOCK_STEP_UP_PHASE @@ -95,3 +97,5 @@ config DOOR_LOCK_ACCESS_MANAGER_SESSION_TIMEOUT_MS endif endif # DOOR_LOCK_BLE_UWB + +endmenu # Access Manager diff --git a/app/src/aliro/access_manager/access_manager.cpp b/app/src/aliro/access_manager/access_manager.cpp new file mode 100644 index 00000000..86a2d654 --- /dev/null +++ b/app/src/aliro/access_manager/access_manager.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause + */ + +#include "access_manager.h" + +namespace Aliro { + +AccessManagerImpl *AccessManager::Impl() +{ + return static_cast(this); +} + +const AccessManagerImpl *AccessManager::Impl() const +{ + return static_cast(this); +} + +void AccessManager::SetApplicationCallbacks(const ApplicationCallbacks &callbacks) +{ + Impl()->_SetApplicationCallbacks(callbacks); +} + +std::optional +AccessManager::ShouldRequestAccessDocument(const CryptoTypes::PublicKey &publicKey, + const std::optional &credentialSignedTimestamp) +{ + return Impl()->_ShouldRequestAccessDocument(publicKey, credentialSignedTimestamp); +} + +AliroError +AccessManager::VerifyAccessCredential(const CryptoTypes::PublicKey &userPublicKey, SessionContext sessionContext, + CryptoTypes::KeyId kpersistentKeyId, + const std::optional &accessDocument) +{ + return Impl()->_VerifyAccessCredential(userPublicKey, sessionContext, kpersistentKeyId, accessDocument); +} + +AliroError AccessManager::VerifyKPersistentKey(CryptoTypes::KeyId kpersistentKeyId, SessionContext sessionContext) +{ + return Impl()->_VerifyKPersistentKey(kpersistentKeyId, sessionContext); +} + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB +AliroError AccessManager::StartRangingSession(uint32_t rangingSessionId, const CryptoTypes::Ursk &ursk, + ProtocolVersion protocolVersion, SessionContext sessionContext) +{ + return Impl()->_StartRangingSession(rangingSessionId, ursk, protocolVersion, sessionContext); +} +#endif // CONFIG_NCS_ALIRO_BLE_UWB + +AliroError AccessManager::AddPublicKey(const CryptoTypes::PublicKey &publicKey, PublicKeyType publicKeyType, + size_t keyIndex) +{ + return Impl()->_AddPublicKey(publicKey, publicKeyType, keyIndex); +} + +bool AccessManager::IsPublicKeyStored(const CryptoTypes::PublicKey &publicKey, size_t *keyIndex) +{ + return Impl()->_IsPublicKeyStored(publicKey, keyIndex); +} + +AliroError AccessManager::GetPublicKey(size_t keyIndex, CryptoTypes::PublicKey &publicKey) +{ + return Impl()->_GetPublicKey(keyIndex, publicKey); +} + +AliroError AccessManager::RemovePublicKey(PublicKeyType publicKeyType, size_t keyIndex) +{ + return Impl()->_RemovePublicKey(publicKeyType, keyIndex); +} + +AliroError AccessManager::GetCredentialIssuerPublicKey(const CryptoTypes::KeyIdentifier &keyIdentifier, + CryptoTypes::PublicKey &publicKey) const +{ + return Impl()->_GetCredentialIssuerPublicKey(keyIdentifier, publicKey); +} + +void AccessManager::ClearStoredKeys() +{ + return Impl()->_ClearStoredKeys(); +} + +void AccessManager::SetMaxAllowedDistance(uint32_t maxDistance) +{ + return Impl()->_SetMaxAllowedDistance(maxDistance); +} + +uint32_t AccessManager::GetMaxAllowedDistance() +{ + return Impl()->_GetMaxAllowedDistance(); +} + +void AccessManager::HandleRangingSessionData(SessionContext sessionContext, const UwbRangingData &uwbData) +{ + return Impl()->_HandleRangingSessionData(sessionContext, uwbData); +} + +void AccessManager::HandleRangingSessionStateChanged(SessionContext sessionContext, RangingSessionState state) +{ + return Impl()->_HandleRangingSessionStateChanged(sessionContext, state); +} + +void AccessManager::HandleSessionTermination(SessionContext sessionContext) +{ + return Impl()->_HandleSessionTermination(sessionContext); +} + +} // namespace Aliro diff --git a/lib/aliro/interfaces/access_manager/access_manager.h b/app/src/aliro/access_manager/access_manager.h similarity index 85% rename from lib/aliro/interfaces/access_manager/access_manager.h rename to app/src/aliro/access_manager/access_manager.h index 726958d0..5a618618 100644 --- a/lib/aliro/interfaces/access_manager/access_manager.h +++ b/app/src/aliro/access_manager/access_manager.h @@ -6,7 +6,9 @@ #pragma once +#include "aliro/connection_handle.h" #include "aliro/errors.h" +#include "aliro/interface.h" #include "aliro/protocol_version.h" #include "aliro/types.h" @@ -24,7 +26,7 @@ class AccessManagerImpl; */ class AccessManager { public: - using SessionContext = const void *; + using SessionContext = ConnectionHandle; using LockIndicatorCallback = void (*)(OperationSource source); using AccessIndicatorCallback = void (*)(bool isAccessGranted, bool isNfcSession); using TerminateSessionCallback = void (*)(SessionContext sessionContext); @@ -62,19 +64,6 @@ class AccessManager { AccessIndicatorCallback mAccessIndicatorClb{ nullptr }; }; - /** - * @brief Stack callbacks. - * - */ - struct StackCallbacks { - /** - * @brief Callback for terminating the Aliro session. - * - * This callback is called when the session should be terminated. - */ - TerminateSessionCallback mTerminateSessionClb{ nullptr }; - }; - /** * @brief Set the application callbacks for the AccessManager. * @@ -82,26 +71,10 @@ class AccessManager { */ void SetApplicationCallbacks(const ApplicationCallbacks &callbacks); - /** - * @brief Set the stack callbacks. - * - * @param callbacks Stack callbacks. - */ - void SetStackCallbacks(const StackCallbacks &callbacks); - /** * @brief Parameters for the Access Document request. */ - struct AccessDocumentRequestParams { - /** - * @brief The data element identifier of the Access Document to be requested. - */ - ConstData mElementIdentifier; - /** - * @brief Indicates the intent to store the Access Document. - */ - bool mIntentToStore; - }; + using AccessDocumentRequestParams = Interface::Access::AccessDocumentRequestParams; /** * @brief Checks if the Access Document for a parameters should be requested. @@ -120,30 +93,28 @@ class AccessManager { * @brief Verifies the Access Credential based on provided inputs. * * @param userPublicKey The User Device public key to verify. - * @param isNfcSession Indicates if the session is a NFC session. * @param sessionContext A pointer to the session context. + * @param kpersistentKeyId The volatile Kpersistent key generated during the Expedited-standard phase. * @param accessDocument The access document provided by the User Device. * * @return ALIRO_NO_ERROR on success, error code otherwise. */ AliroError - VerifyAccessCredential(const CryptoTypes::PublicKey &userPublicKey, bool isNfcSession, - SessionContext sessionContext, + VerifyAccessCredential(const CryptoTypes::PublicKey &userPublicKey, SessionContext sessionContext, + CryptoTypes::KeyId kpersistentKeyId, const std::optional &accessDocument = std::nullopt); /** * @brief Verifies the Kpersistent key based on provided inputs. * * @param kpersistentKeyId The Kpersistent key ID to verify. - * @param isNfcSession Indicates if the session is a NFC session. * @param sessionContext A pointer to the session context. * * @return ALIRO_NO_ERROR on success, error code otherwise. */ - AliroError VerifyKPersistentKey(CryptoTypes::KeyId kpersistentKeyId, bool isNfcSession, - SessionContext sessionContext); + AliroError VerifyKPersistentKey(CryptoTypes::KeyId kpersistentKeyId, SessionContext sessionContext); -#ifdef CONFIG_DOOR_LOCK_BLE_UWB +#ifdef CONFIG_NCS_ALIRO_BLE_UWB /** * @brief Starts a ranging session based on provided inputs. * @@ -156,7 +127,7 @@ class AccessManager { */ AliroError StartRangingSession(uint32_t rangingSessionId, const CryptoTypes::Ursk &ursk, ProtocolVersion protocolVersion, SessionContext sessionContext); -#endif // CONFIG_DOOR_LOCK_BLE_UWB +#endif // CONFIG_NCS_ALIRO_BLE_UWB /** * @brief Add a new public key to the AccessManager. @@ -249,9 +220,8 @@ class AccessManager { * @brief Handles the session termination. * * @param sessionContext The session context. - * @param isNfcSession Indicates if the session is a NFC session. */ - void HandleSessionTermination(SessionContext sessionContext, bool isNfcSession); + void HandleSessionTermination(SessionContext sessionContext); private: AccessManagerImpl *Impl(); diff --git a/app/src/aliro/access_manager_impl_default/access_manager_impl.cpp b/app/src/aliro/access_manager/access_manager_impl.cpp similarity index 88% rename from app/src/aliro/access_manager_impl_default/access_manager_impl.cpp rename to app/src/aliro/access_manager/access_manager_impl.cpp index cf60780f..49e4a8ee 100644 --- a/app/src/aliro/access_manager_impl_default/access_manager_impl.cpp +++ b/app/src/aliro/access_manager/access_manager_impl.cpp @@ -7,15 +7,19 @@ #include "access_manager_impl.h" #include "access_document.h" #include "aliro/aliro.h" -#include "aliro/mutex_guard.h" +#include "aliro/interface.h" #include "aliro/time.h" #include "aliro/utils.h" -#include "crypto/crypto.h" +#include "crypto/utils.h" +#include "mutex_guard.h" #include "storage.h" #include "storage_keys.h" #ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE #include "cbor/access_document_decode.h" +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 +#include "access_document.h" +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 #endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE #ifdef CONFIG_DOOR_LOCK_BLE_UWB @@ -53,7 +57,7 @@ constexpr std::array kElementIdentifier{ 'f', 'l', 'o', 'o', 'r', '1 #endif // CONFIG_CHIP -constexpr AccessManager::AccessDocumentRequestParams kAccessDocumentRequestParams{ +constexpr Interface::Access::AccessDocumentRequestParams kAccessDocumentRequestParams{ .mElementIdentifier = { kElementIdentifier.data(), kElementIdentifier.size() }, .mIntentToStore = true, }; @@ -165,18 +169,20 @@ void LogAccessData(const AccessData &accessData) } } -bool ValidateAccessData(const ConstData &accessDataBytes) +AliroError ValidateAccessData(const ConstData &accessDataBytes) { - VerifyOrReturnFalse(accessDataBytes.mData && accessDataBytes.mLength, LOG_ERR("AccessData is empty")); + VerifyOrReturnStatus(accessDataBytes.mData && accessDataBytes.mLength, ALIRO_INVALID_ARGUMENT, + LOG_ERR("AccessData is empty")); LOG_HEXDUMP_DBG(accessDataBytes.mData, accessDataBytes.mLength, "AccessData"); AccessData accessData{}; const auto err = cbor_decode_AccessData(accessDataBytes.mData, accessDataBytes.mLength, &accessData, nullptr); - VerifyOrReturnFalse(err == ZCBOR_SUCCESS, LOG_ERR("Failed to decode AccessData: %d", err)); + VerifyOrReturnStatus(err == ZCBOR_SUCCESS, ALIRO_INVALID_DATA_FORMAT, + LOG_ERR("Failed to decode AccessData: %d", err)); - VerifyOrReturnFalse(accessData.AccessData_Version == 1, - LOG_ERR("Unsupported AccessData Version: %d", accessData.AccessData_Version)); + VerifyOrReturnStatus(accessData.AccessData_Version == 1, ALIRO_INVALID_DATA_FORMAT, + LOG_ERR("Unsupported AccessData Version: %d", accessData.AccessData_Version)); if (accessData.AccessData_AccessRules_present) { bool accessRuleValid{ false }; @@ -201,12 +207,14 @@ bool ValidateAccessData(const ConstData &accessDataBytes) } } - VerifyOrReturnFalse(accessRuleValid, LOG_ERR("AccessData AccessRules, no valid rule found")); + VerifyOrReturnStatus(accessRuleValid, ALIRO_INVALID_DATA_CONTENT, + LOG_ERR("AccessData AccessRules, no valid rule found")); } - VerifyOrReturnFalse(!accessData.AccessData_Schedules_present, LOG_ERR("AccessData Schedules not supported")); - VerifyOrReturnFalse(!accessData.AccessData_ReaderRuleIds_present, - LOG_ERR("AccessData ReaderRuleIds not supported")); + VerifyOrReturnStatus(!accessData.AccessData_Schedules_present, ALIRO_INVALID_DATA_CONTENT, + LOG_ERR("AccessData Schedules not supported")); + VerifyOrReturnStatus(!accessData.AccessData_ReaderRuleIds_present, ALIRO_INVALID_DATA_CONTENT, + LOG_ERR("AccessData ReaderRuleIds not supported")); if (accessData.AccessData_AccessExtensions_present) { const auto &accessExtensions = accessData.AccessData_AccessExtensions; @@ -226,8 +234,8 @@ bool ValidateAccessData(const ConstData &accessDataBytes) const auto criticalExtension = !IS_BIT_SET(accessExtension.AccessExtension_Criticality, Criticality_Bits::Criticality_Bits_Critical_c); - VerifyOrReturnFalse( - !criticalExtension, + VerifyOrReturnStatus( + !criticalExtension, ALIRO_INVALID_DATA_CONTENT, LOG_ERR("AccessData AccessExtensions, critical extensions are not supported")); } } @@ -235,7 +243,7 @@ bool ValidateAccessData(const ConstData &accessDataBytes) LogAccessData(accessData); - return true; + return ALIRO_NO_ERROR; } AliroError GetCurrentValidityIterations(size_t credentialIssuerKeyIndex, ValidityIterations &iterations) @@ -261,6 +269,8 @@ bool VerifyValidityIteration(const ValidityIterations ¤tIterations, Validi return true; } +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + AliroError StoreAccessDocument(size_t keyIndex, size_t credentialIssuerKeyIndex, const AccessDocumentTypes::AccessDocument &accessDocument) { @@ -304,21 +314,32 @@ bool IsCurrentAccessDocumentUpToDate(size_t keyIndex, const Timestamp &credentia return savedTimestamp.value() >= newTimestamp.value(); } +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + #endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE } // namespace -void AccessManagerImpl::_SetApplicationCallbacks(const ApplicationCallbacks &callbacks) +#ifdef CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT + +void AccessManagerImpl::RangingSessionContext::RangingSessionTimerCallback(Timer::Context ctx) { - mCallbacks = callbacks; + auto rangingSessionCtx = static_cast(ctx); + auto sessionContextOpt = AccessManagerImpl::Instance().FindSessionContext(rangingSessionCtx); + + if (sessionContextOpt.has_value()) { + AccessManagerImpl::Instance().TerminateAliroSession(sessionContextOpt.value()); + } } -void AccessManagerImpl::_SetStackCallbacks(const StackCallbacks &callbacks) +#endif // CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT + +void AccessManagerImpl::_SetApplicationCallbacks(const ApplicationCallbacks &callbacks) { - mStackCallbacks = callbacks; + mCallbacks = callbacks; } -std::optional AccessManagerImpl::_ShouldRequestAccessDocument( +std::optional AccessManagerImpl::_ShouldRequestAccessDocument( [[maybe_unused]] const CryptoTypes::PublicKey &publicKey, [[maybe_unused]] const std::optional &credentialSignedTimestamp) { @@ -328,6 +349,7 @@ std::optional AccessManagerImpl::_Sh !IsPublicKeyStored(mAcKeys, publicKey), std::nullopt, LOG_INF("Provided User Device public key found in Access Manager database, not requesting Access Document")); +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 size_t keyIndex{}; if (IsPublicKeyStored(mAdKeys, publicKey, &keyIndex)) { if (credentialSignedTimestamp.has_value()) { @@ -345,6 +367,7 @@ std::optional AccessManagerImpl::_Sh LOG_INF("Provided User Device public key found in Access Manager database, not requesting Access Document"); return std::nullopt; } +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 LOG_INF("Provided User Device public key not found in Access Manager database, requesting Access Document"); return kAccessDocumentRequestParams; @@ -354,13 +377,13 @@ std::optional AccessManagerImpl::_Sh } AliroError AccessManagerImpl::_VerifyAccessCredential( - const CryptoTypes::PublicKey &userPublicKey, bool isNfcSession, SessionContext, + const CryptoTypes::PublicKey &userPublicKey, SessionContext sessionContext, CryptoTypes::KeyId kpersistentKeyId, [[maybe_unused]] const std::optional &accessDocument) { AliroError status{ ALIRO_NO_ERROR }; { MutexGuard lock{ sMutex }; - status = VerifyPublicKey(userPublicKey) ? ALIRO_NO_ERROR : ALIRO_INVALID_PUBLIC_KEY; + status = VerifyPublicKey(userPublicKey) ? ALIRO_NO_ERROR : ALIRO_PUBLIC_KEY_NOT_FOUND; } #ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE @@ -370,19 +393,28 @@ AliroError AccessManagerImpl::_VerifyAccessCredential( } #endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE - HandleAccessGranted(isNfcSession, status == ALIRO_NO_ERROR); +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + if (status == ALIRO_NO_ERROR && mKpersistentManager) { + const auto preserveStatus = mKpersistentManager->PreserveKpersistent(userPublicKey, kpersistentKeyId); + if (preserveStatus != ALIRO_NO_ERROR) { + LOG_ERR("Failed to preserve Kpersistent key: %d", preserveStatus.ToInt()); + } + } +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + + HandleAccessGranted(sessionContext.IsNfc(), status == ALIRO_NO_ERROR); return status; } AliroError AccessManagerImpl::_VerifyKPersistentKey([[maybe_unused]] CryptoTypes::KeyId kpersistentKeyId, - [[maybe_unused]] bool isNfcSession, SessionContext) + [[maybe_unused]] SessionContext sessionContext) { #ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE CryptoTypes::PublicKey publicKey{}; VerifyOrReturnStatus(mKpersistentManager, ALIRO_INVALID_STATE, LOG_ERR("Kpersistent manager not set")); AliroError status = mKpersistentManager->GetAccessCredentialPublicKey(kpersistentKeyId, publicKey); - HandleAccessGranted(isNfcSession, status == ALIRO_NO_ERROR); + HandleAccessGranted(sessionContext.IsNfc(), status == ALIRO_NO_ERROR); return status; #else @@ -446,7 +478,9 @@ AliroError AccessManagerImpl::_RemovePublicKey(PublicKeyType publicKeyType, size else if (publicKeyType == PublicKeyType::CredentialIssuer) { LOG_DBG("Removing Credential Issuer public key from storage"); ReturnErrorOnFailure(RemoveKeyFromContainer(mCiKeys, keyIndex)); +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 ReturnErrorOnFailure(RemoveAccessCredentials(keyIndex)); +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 return ALIRO_NO_ERROR; } #endif // CONFIG_DOOR_LOCK_ACCESS_MANAGER_CREDENTIAL_ISSUER_MAX_STORED_KEYS > 0 @@ -530,20 +564,20 @@ void AccessManagerImpl::_HandleRangingSessionStateChanged(SessionContext session #ifdef CONFIG_DOOR_LOCK_BLE_UWB switch (state) { case RangingSessionState::Ranging: - LOG_INF("Ranging state changed to Ranging (session: %p)", sessionContext); + LOG_INF("Ranging state changed to Ranging (session: %p)", sessionContext.GetRaw()); break; case RangingSessionState::RangingSuspended: - LOG_INF("Ranging state changed to Ranging Suspended (session: %p)", sessionContext); + LOG_INF("Ranging state changed to Ranging Suspended (session: %p)", sessionContext.GetRaw()); // To prevent rapid toggling after Suspend event, only update ReaderState if no other sessions are in // range SetInRangeState(sessionContext, false, !IsUserDeviceInRange()); break; case RangingSessionState::RangingResumed: - LOG_INF("Ranging state changed to Ranging Resumed (session: %p)", sessionContext); + LOG_INF("Ranging state changed to Ranging Resumed (session: %p)", sessionContext.GetRaw()); break; case RangingSessionState::Destroyed: - LOG_INF("Ranging state changed to Destroyed (session: %p)", sessionContext); + LOG_INF("Ranging state changed to Destroyed (session: %p)", sessionContext.GetRaw()); // Only update ReaderState if no other sessions are in range SetInRangeState(sessionContext, false); break; @@ -559,7 +593,7 @@ void AccessManagerImpl::_HandleRangingSessionStateChanged(SessionContext session void AccessManagerImpl::_HandleRangingSessionData(SessionContext sessionContext, const UwbRangingData &uwbData) { - LOG_DBG("Handling ranging session data - length: %u for session: %p", uwbData.mLength, sessionContext); + LOG_DBG("Handling ranging session data - length: %u for session: %p", uwbData.mLength, sessionContext.GetRaw()); #ifdef CONFIG_DOOR_LOCK_BLE_UWB const auto currentSessionInRange = AnalyzeUwbRangingData(uwbData, sessionContext); @@ -567,14 +601,12 @@ void AccessManagerImpl::_HandleRangingSessionData(SessionContext sessionContext, #endif // CONFIG_DOOR_LOCK_BLE_UWB } -void AccessManagerImpl::_HandleSessionTermination(SessionContext sessionContext, [[maybe_unused]] bool isNfcSession) +void AccessManagerImpl::_HandleSessionTermination(SessionContext sessionContext) { - VerifyOrReturn(sessionContext, LOG_ERR("Session context is null")); - LOG_INF("Handling session termination"); #ifdef CONFIG_DOOR_LOCK_BLE_UWB - if (isNfcSession) { + if (sessionContext.IsNfc()) { return; } @@ -738,7 +770,7 @@ bool AccessManagerImpl::AnalyzeUwbRangingData(const UwbRangingData &uwbData, Ses // Find the session auto *sessionCtx = FindRangingSession(sessionContext); - VerifyOrReturnFalse(sessionCtx, LOG_ERR("Session context not found for handle: %p", sessionContext)); + VerifyOrReturnFalse(sessionCtx, LOG_ERR("Session context not found for handle: %p", sessionContext.GetRaw())); // Apply exit margin logic based on session's previous state: // - Unlock (enter range): distance <= mMaxAllowedDistance (when session was out of range) @@ -776,15 +808,10 @@ AliroError AccessManagerImpl::AddRangingSession(uint32_t rangingSessionId, const auto *newCtx = Aliro::new_nothrow( #ifdef CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT CONFIG_DOOR_LOCK_ACCESS_MANAGER_SESSION_TIMEOUT_MS, - [](Timer::Context ctx) { AccessManagerImpl::Instance().TerminateAliroSession(ctx); }, - const_cast(sessionCtx) #endif // CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT - ); + sessionCtx); VerifyOrReturnStatus(newCtx, ALIRO_NO_MEMORY, LOG_ERR("Cannot allocate context for UWB session.")); - newCtx->mInRange = false; - newCtx->mSessionContext = sessionCtx; - MutexGuard lock{ sMutex }; AliroError status = Uwb::UltraWideBandImpl::Instance().ConfigureRangingSession(rangingSessionId, ursk, @@ -844,6 +871,21 @@ AccessManagerImpl::RangingSessionContext *AccessManagerImpl::FindRangingSession( return nullptr; } +std::optional +AccessManagerImpl::FindSessionContext(RangingSessionContext *rangingSessionCtx) +{ + MutexGuard lock{ sMutex }; + RangingSessionContext *currentSessionCtx{ nullptr }; + + SYS_SLIST_FOR_EACH_CONTAINER (&mActiveSessions, currentSessionCtx, mNode) { + if (currentSessionCtx == rangingSessionCtx) { + return currentSessionCtx->mSessionContext; + } + } + + return std::nullopt; +} + bool AccessManagerImpl::IsUserDeviceInRange() const { RangingSessionContext *rangingSessionCtx{}; @@ -867,7 +909,8 @@ void AccessManagerImpl::SetInRangeState(SessionContext sessionContext, bool sess MutexGuard lock{ sMutex }; // Find the session sessionCtx = FindRangingSession(sessionContext); - VerifyOrReturn(sessionCtx, LOG_ERR("Session context not found for handle: %p", sessionContext)); + VerifyOrReturn(sessionCtx, + LOG_ERR("Session context not found for handle: %p", sessionContext.GetRaw())); // Early return if has not changed if (sessionCtx->mInRange == sessionInRange) { @@ -905,7 +948,9 @@ void AccessManagerImpl::SetInRangeState(SessionContext sessionContext, bool sess TerminateAliroSession(sessionContext); #endif // CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_ACCESS_GRANTED } else { +#ifndef CONFIG_DOOR_LOCK_LOCK_SIM_AUTO_RELOCK LockAction(false); +#endif // CONFIG_DOOR_LOCK_LOCK_SIM_AUTO_RELOCK } } #endif // CONFIG_DOOR_LOCK_BLE_UWB @@ -913,8 +958,8 @@ void AccessManagerImpl::SetInRangeState(SessionContext sessionContext, bool sess void AccessManagerImpl::TerminateAliroSession(SessionContext sessionContext) { - LOG_DBG("Terminating Aliro session for context: %p", sessionContext); - VerifyAndCall(mStackCallbacks.mTerminateSessionClb, sessionContext); + LOG_DBG("Terminating Aliro session for context: %p", sessionContext.GetRaw()); + AliroStack::Instance().DestroySession(sessionContext); } #endif // CONFIG_DOOR_LOCK_BLE_UWB @@ -977,7 +1022,7 @@ AliroError AccessManagerImpl::_GetCredentialIssuerPublicKey(const CryptoTypes::K std::copy(key.value().begin(), key.value().end(), input.begin() + kKeyIdentifierStringLength); - AliroError error = CryptoInstance().Sha256(input.data(), input.size(), sha256Output); + AliroError error = Interface::Crypto::Sha256(input.data(), input.size(), sha256Output); VerifyOrReturnStatus(error == ALIRO_NO_ERROR, ALIRO_ERROR_INTERNAL, LOG_ERR("SHA256 hash computation failed")); @@ -999,17 +1044,20 @@ AliroError AccessManagerImpl::ProcessAccessDocument(const CryptoTypes::PublicKey { ReturnErrorOnFailure(ProcessValidityIteration(ad.mCredentialIssuerPublicKey, ad.mValidityIteration)); - VerifyOrReturnStatus(ValidateAccessData(ad.mDataElement), ALIRO_INVALID_ACCESS_DOCUMENT, + auto error = ValidateAccessData(ad.mDataElement); + VerifyOrReturnStatus(error == ALIRO_NO_ERROR, error, LOG_WRN("Access Document is not valid, ignoring it for access decision")); LOG_DBG("Verify Access Credential based on Access Document"); - VerifyOrReturnStatus(ad.mPublicKey == userPublicKey, ALIRO_INVALID_PUBLIC_KEY, LOG_WRN("Public key mismatch")); + VerifyOrReturnStatus(ad.mPublicKey == userPublicKey, ALIRO_PUBLIC_KEY_NOT_TRUSTED, + LOG_WRN("Public key mismatch")); +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 MutexGuard lock{ sMutex }; // Check if there is a free index in the Access Document container to store the Access Document. size_t keyIndex = 0; - auto error = GetFirstFreeIndex(mAdKeys, keyIndex); + error = GetFirstFreeIndex(mAdKeys, keyIndex); if (error == ALIRO_NO_MEMORY) { LOG_DBG("No free index in the Access Document container, removing oldest credential"); @@ -1029,10 +1077,13 @@ AliroError AccessManagerImpl::ProcessAccessDocument(const CryptoTypes::PublicKey } } } +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 return ALIRO_NO_ERROR; } +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + AliroError AccessManagerImpl::RemoveOldestCredential(size_t &keyIndex) { std::optional oldestKeyIndex; @@ -1069,6 +1120,8 @@ AliroError AccessManagerImpl::RemoveOldestCredential(size_t &keyIndex) return ALIRO_NO_MEMORY; } +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + AliroError AccessManagerImpl::ProcessValidityIteration(const CryptoTypes::PublicKey &credentialIssuerPublicKey, const std::optional &validityIteration) { @@ -1085,8 +1138,7 @@ AliroError AccessManagerImpl::ProcessValidityIteration(const CryptoTypes::Public ValidityIterations iterations{}; ReturnErrorOnFailure(GetCurrentValidityIterations(keyIndex, iterations)); - VerifyOrReturnStatus(VerifyValidityIteration(iterations, validityIteration.value()), - ALIRO_INVALID_ACCESS_DOCUMENT, + VerifyOrReturnStatus(VerifyValidityIteration(iterations, validityIteration.value()), ALIRO_PUBLIC_KEY_EXPIRED, LOG_WRN("Validity Iteration is not valid, ignoring Access Document for access decision")); ReturnErrorOnFailure(UpdateValidityIteration(keyIndex, iterations, validityIteration.value())); @@ -1111,11 +1163,15 @@ AliroError AccessManagerImpl::UpdateValidityIteration(size_t credentialIssuerKey ReturnErrorOnFailure(StoreValidityIterations(credentialIssuerKeyIndex, iterations)); } +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 ReturnErrorOnFailure(RemoveOldCredentials(credentialIssuerKeyIndex, iterations.mAccessIteration)); +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 return ALIRO_NO_ERROR; } +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + AliroError AccessManagerImpl::RemoveOldCredentials(size_t credentialIssuerKeyIndex, ValidityIteration validityIteration) { for (size_t i = 0; i < mAdKeys.mKeys.size(); i++) { @@ -1170,6 +1226,8 @@ AliroError AccessManagerImpl::RemoveAccessCredentials(size_t credentialIssuerKey return ALIRO_NO_ERROR; } +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + #endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE } // namespace Aliro diff --git a/app/src/aliro/access_manager_impl_default/access_manager_impl.h b/app/src/aliro/access_manager/access_manager_impl.h similarity index 91% rename from app/src/aliro/access_manager_impl_default/access_manager_impl.h rename to app/src/aliro/access_manager/access_manager_impl.h index b97e3d99..583afdad 100644 --- a/app/src/aliro/access_manager_impl_default/access_manager_impl.h +++ b/app/src/aliro/access_manager/access_manager_impl.h @@ -7,12 +7,12 @@ #pragma once -#include "access_manager/access_manager.h" +#include "../kpersistent_manager/kpersistent_manager.h" +#include "access_manager.h" #include "aliro/types.h" -#include "kpersistent_manager/kpersistent_manager.h" #ifdef CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT -#include "aliro/timer.h" +#include "timer.h" #endif // CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT #ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE @@ -64,13 +64,6 @@ class AccessManagerImpl final : public AccessManager { */ void _SetApplicationCallbacks(const ApplicationCallbacks &callbacks); - /** - * @brief Set the stack callbacks. - * - * @param callbacks Stack callbacks. - */ - void _SetStackCallbacks(const StackCallbacks &callbacks); - /** * @brief Checks if the Access Document for a parameters should be requested. * @@ -88,27 +81,26 @@ class AccessManagerImpl final : public AccessManager { * @brief Verifies the Access Credential based on provided inputs. * * @param userPublicKey The User Device public key to verify. - * @param isNfcSession Indicates if the session is a NFC session. * @param sessionContext A pointer to the session context. + * @param kpersistentKeyId The volatile Kpersistent key generated during the Expedited-standard phase. * @param accessDocument The access document provided by the User Device. * * @return ALIRO_NO_ERROR on success, error code otherwise. */ AliroError _VerifyAccessCredential( - const CryptoTypes::PublicKey &userPublicKey, bool isNfcSession, SessionContext sessionContext, + const CryptoTypes::PublicKey &userPublicKey, SessionContext sessionContext, + CryptoTypes::KeyId kpersistentKeyId, const std::optional &accessDocument = std::nullopt); /** * @brief Verifies the Kpersistent key based on provided inputs. * * @param kpersistentKeyId The Kpersistent key ID to verify. - * @param isNfcSession Indicates if the session is a NFC session. * @param sessionContext A pointer to the session context. * * @return ALIRO_NO_ERROR on success, error code otherwise. */ - AliroError _VerifyKPersistentKey(CryptoTypes::KeyId kpersistentKeyId, bool isNfcSession, - SessionContext sessionContext); + AliroError _VerifyKPersistentKey(CryptoTypes::KeyId kpersistentKeyId, SessionContext sessionContext); #ifdef CONFIG_DOOR_LOCK_BLE_UWB /** @@ -216,9 +208,8 @@ class AccessManagerImpl final : public AccessManager { * @brief Handles the session termination. * * @param sessionContext The session context. - * @param isNfcSession Indicates if the session is a NFC session. */ - void _HandleSessionTermination(SessionContext sessionContext, bool isNfcSession); + void _HandleSessionTermination(SessionContext sessionContext); AccessManagerImpl() = default; ~AccessManagerImpl() = default; @@ -310,22 +301,32 @@ class AccessManagerImpl final : public AccessManager { const std::optional &validityIteration); AliroError UpdateValidityIteration(size_t credentialIssuerKeyIndex, const ValidityIterations ¤tIterations, ValidityIteration validityIteration); +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 AliroError RemoveOldCredentials(size_t credentialIssuerKeyIndex, ValidityIteration validityIteration); AliroError RemoveAccessCredentials(size_t credentialIssuerKeyIndex); +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 #endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE #ifdef CONFIG_DOOR_LOCK_BLE_UWB struct RangingSessionContext { - sys_snode_t mNode{}; #ifdef CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT - RangingSessionContext(uint32_t timeoutMs, Timer::Callback callback, Timer::Context userData) - : mRangingSessionTimer(timeoutMs, callback, userData) + RangingSessionContext(uint32_t timeoutMs, SessionContext sessionContext) + : mSessionContext(sessionContext), + mRangingSessionTimer(timeoutMs, RangingSessionTimerCallback, this) { } - Timer mRangingSessionTimer; + + static void RangingSessionTimerCallback(Timer::Context ctx); +#else + RangingSessionContext(SessionContext sessionContext) : mSessionContext(sessionContext) {} #endif // CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT - SessionContext mSessionContext{}; + + sys_snode_t mNode{}; + SessionContext mSessionContext; bool mInRange{ false }; +#ifdef CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT + Timer mRangingSessionTimer; +#endif // CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT }; bool AnalyzeUwbRangingData(const UwbRangingData &uwbData, SessionContext sessionContext); @@ -334,6 +335,7 @@ class AccessManagerImpl final : public AccessManager { ProtocolVersion protocolVersion, const SessionContext sessionCtx); void RemoveRangingSession(SessionContext sessionCtx); RangingSessionContext *FindRangingSession(const SessionContext sessionCtx); + std::optional FindSessionContext(RangingSessionContext *rangingSessionCtx); bool IsUserDeviceInRange() const; void TerminateAliroSession(SessionContext sessionContext); void SetInRangeState(SessionContext sessionContext, bool sessionInRange, bool updateReaderState = true); @@ -352,7 +354,6 @@ class AccessManagerImpl final : public AccessManager { #endif // CONFIG_DOOR_LOCK_BLE_UWB ApplicationCallbacks mCallbacks{}; - StackCallbacks mStackCallbacks{}; KpersistentManager *mKpersistentManager{ nullptr }; diff --git a/app/src/aliro/access_manager_impl_custom/access_manager_impl.cpp b/app/src/aliro/access_manager_impl_custom/access_manager_impl.cpp deleted file mode 100644 index 9e8f4fe4..00000000 --- a/app/src/aliro/access_manager_impl_custom/access_manager_impl.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#include "access_manager_impl.h" - -#include - -LOG_MODULE_REGISTER(access_manager_impl_custom, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); - -namespace Aliro { - -void AccessManagerImpl::_SetApplicationCallbacks(const ApplicationCallbacks &) -{ - LOG_INF("Custom %s function", __FUNCTION__); -} - -void AccessManagerImpl::_SetStackCallbacks(const StackCallbacks &) -{ - LOG_INF("Custom %s function", __FUNCTION__); -} - -std::optional -AccessManagerImpl::_ShouldRequestAccessDocument(const CryptoTypes::PublicKey &, const std::optional &) -{ - LOG_INF("Custom %s function", __FUNCTION__); - return std::nullopt; -} - -AliroError AccessManagerImpl::_VerifyAccessCredential(const CryptoTypes::PublicKey &, bool, SessionContext, - const std::optional &) -{ - LOG_INF("Custom %s function", __FUNCTION__); - return ALIRO_ERROR_NOT_IMPLEMENTED; -} - -AliroError AccessManagerImpl::_VerifyKPersistentKey(CryptoTypes::KeyId, bool, SessionContext) -{ - LOG_INF("Custom %s function", __FUNCTION__); - return ALIRO_ERROR_NOT_IMPLEMENTED; -} - -#ifdef CONFIG_DOOR_LOCK_BLE_UWB -AliroError AccessManagerImpl::_StartRangingSession(uint32_t, const CryptoTypes::Ursk &, ProtocolVersion, SessionContext) -{ - LOG_INF("Custom %s function", __FUNCTION__); - return ALIRO_ERROR_NOT_IMPLEMENTED; -} -#endif // CONFIG_DOOR_LOCK_BLE_UWB - -AliroError AccessManagerImpl::_AddPublicKey(const CryptoTypes::PublicKey &, PublicKeyType, size_t) -{ - LOG_INF("Custom %s function", __FUNCTION__); - return ALIRO_ERROR_NOT_IMPLEMENTED; -} - -AliroError AccessManagerImpl::_GetCredentialIssuerPublicKey(const CryptoTypes::KeyIdentifier &, - CryptoTypes::PublicKey &) const -{ - LOG_INF("Custom %s function", __FUNCTION__); - return ALIRO_ERROR_NOT_IMPLEMENTED; -} - -bool AccessManagerImpl::_IsPublicKeyStored(const CryptoTypes::PublicKey &, size_t *) -{ - LOG_INF("Custom %s function", __FUNCTION__); - return false; -} - -AliroError AccessManagerImpl::_GetPublicKey(size_t, CryptoTypes::PublicKey &) -{ - LOG_INF("Custom %s function", __FUNCTION__); - return ALIRO_ERROR_NOT_IMPLEMENTED; -} - -AliroError AccessManagerImpl::_RemovePublicKey(PublicKeyType, size_t) -{ - LOG_INF("Custom %s function", __FUNCTION__); - return ALIRO_ERROR_NOT_IMPLEMENTED; -} - -void AccessManagerImpl::_ClearStoredKeys() -{ - LOG_INF("Custom %s function", __FUNCTION__); -} - -void AccessManagerImpl::_SetMaxAllowedDistance(uint32_t) -{ - LOG_INF("Custom %s function", __FUNCTION__); -} - -uint32_t AccessManagerImpl::_GetMaxAllowedDistance() -{ - LOG_INF("Custom %s function", __FUNCTION__); - return 0; -} - -void AccessManagerImpl::_HandleRangingSessionData(SessionContext, const UwbRangingData &) -{ - LOG_INF("Custom %s function", __FUNCTION__); -} - -void AccessManagerImpl::_HandleRangingSessionStateChanged(SessionContext, RangingSessionState) -{ - LOG_INF("Custom %s function", __FUNCTION__); -} - -void AccessManagerImpl::_HandleSessionTermination(SessionContext, bool) -{ - LOG_INF("Custom %s function", __FUNCTION__); -} - -} // namespace Aliro diff --git a/app/src/aliro/access_manager_impl_custom/access_manager_impl.h b/app/src/aliro/access_manager_impl_custom/access_manager_impl.h deleted file mode 100644 index 22104c2c..00000000 --- a/app/src/aliro/access_manager_impl_custom/access_manager_impl.h +++ /dev/null @@ -1,58 +0,0 @@ - -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -#include "access_manager/access_manager.h" - -namespace Aliro { - -/** - * @brief A template for custom access manager implementation. - * - * This class is a template for custom Access Manager implementation. - * It can be used as a base to implement customized Access Manager. - */ -class AccessManagerImpl : public AccessManager { -private: - friend class AccessManager; - - void _SetApplicationCallbacks(const ApplicationCallbacks &callbacks); - void _SetStackCallbacks(const StackCallbacks &callbacks); - std::optional - _ShouldRequestAccessDocument(const CryptoTypes::PublicKey &publicKey, - const std::optional &credentialSignedTimestamp); - AliroError _VerifyAccessCredential( - const CryptoTypes::PublicKey &userPublicKey, bool isNfcSession, SessionContext sessionContext, - const std::optional &accessDocument = std::nullopt); - AliroError _VerifyKPersistentKey(CryptoTypes::KeyId kpersistentKeyId, bool isNfcSession, - SessionContext sessionContext); -#ifdef CONFIG_DOOR_LOCK_BLE_UWB - AliroError _StartRangingSession(uint32_t rangingSessionId, const CryptoTypes::Ursk &ursk, - ProtocolVersion protocolVersion, SessionContext sessionContext); -#endif // CONFIG_DOOR_LOCK_BLE_UWB - AliroError _AddPublicKey(const CryptoTypes::PublicKey &publicKey, PublicKeyType publicKeyType, size_t keyIndex); - bool _IsPublicKeyStored(const CryptoTypes::PublicKey &publicKey, size_t *keyIndex); - AliroError _GetPublicKey(size_t keyIndex, CryptoTypes::PublicKey &publicKey); - AliroError _RemovePublicKey(PublicKeyType publicKeyType, size_t keyIndex); - AliroError _GetCredentialIssuerPublicKey(const CryptoTypes::KeyIdentifier &keyIdentifier, - CryptoTypes::PublicKey &publicKey) const; - void _ClearStoredKeys(); - void _SetMaxAllowedDistance(uint32_t maxDistance); - uint32_t _GetMaxAllowedDistance(); - void _HandleRangingSessionData(SessionContext sessionContext, const UwbRangingData &uwbData); - void _HandleRangingSessionStateChanged(SessionContext sessionContext, RangingSessionState state); - void _HandleSessionTermination(SessionContext sessionContext, bool isNfcSession); -}; - -inline AccessManager &AccessManagerInstance() -{ - static AccessManagerImpl sInstance{}; - return sInstance; -} - -} // namespace Aliro diff --git a/app/src/aliro/aliro_state_control.cpp b/app/src/aliro/aliro_state_control.cpp new file mode 100644 index 00000000..f1be7269 --- /dev/null +++ b/app/src/aliro/aliro_state_control.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro_state_control.h" + +#include "aliro/crypto_key_ids.h" +#include "aliro/init.h" +#include "crypto/utils.h" +#include "reader_cache.h" + +#include +#include + +#include + +#include +LOG_MODULE_DECLARE(aliro); + +namespace { + +bool IsReaderIdentifierProvisioned() +{ + return Aliro::ReaderCache::Instance().IsIdentifierSet(); +} + +bool IsReaderPrivateKeyProvisioned() +{ + return DoorLock::Crypto::IsKeyAvailable(Aliro::kPrivateKeyId) == ALIRO_NO_ERROR; +} + +bool IsProvisioningComplete() +{ + return IsReaderPrivateKeyProvisioned() && IsReaderIdentifierProvisioned(); +} + +} // anonymous namespace + +namespace DoorLock::AliroStateControl { + +AliroError UpdateAliroState() +{ + if (IsProvisioningComplete()) { + if (!IsAliroRunning()) { + const int startRc = AliroStart(); + VerifyOrReturnStatus(startRc == EXIT_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_ERR("Failed to start Aliro: %d", startRc)); +#ifdef CONFIG_DOOR_LOCK_BLE_UWB + } else { + ReturnErrorOnFailure(StartAliroAdvertising()); +#endif // CONFIG_DOOR_LOCK_BLE_UWB + } + return ALIRO_NO_ERROR; + } + + if (IsAliroRunning()) { + const int rc = AliroStop(); + VerifyOrReturnStatus(rc == EXIT_SUCCESS, ALIRO_ERROR_INTERNAL, LOG_ERR("Failed to stop Aliro: %d", rc)); + } + return ALIRO_NO_ERROR; +} + +} // namespace DoorLock::AliroStateControl diff --git a/app/src/aliro/aliro_state_control.h b/app/src/aliro/aliro_state_control.h new file mode 100644 index 00000000..b26820d1 --- /dev/null +++ b/app/src/aliro/aliro_state_control.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +#pragma once + +#include + +namespace DoorLock::AliroStateControl { + +/** + * @brief Updates Aliro runtime state: start when provisioned, otherwise stop. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError UpdateAliroState(); + +} // namespace DoorLock::AliroStateControl diff --git a/app/src/aliro/aliro_work/CMakeLists.txt b/app/src/aliro/aliro_work/CMakeLists.txt new file mode 100644 index 00000000..7bc2fa3c --- /dev/null +++ b/app/src/aliro/aliro_work/CMakeLists.txt @@ -0,0 +1,8 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +target_sources(app PRIVATE aliro_work.cpp) + diff --git a/app/src/aliro/aliro_work/Kconfig b/app/src/aliro/aliro_work/Kconfig new file mode 100644 index 00000000..56284f64 --- /dev/null +++ b/app/src/aliro/aliro_work/Kconfig @@ -0,0 +1,19 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "Aliro workqueue" + +config DOOR_LOCK_ALIRO_WORKQUEUE_STACK_SIZE + int "Aliro workqueue stack size" + default 5120 + help + Stack size for the dedicated Aliro workqueue. + +config DOOR_LOCK_ALIRO_WORKQUEUE_PRIORITY + int "Aliro workqueue priority" + default 10 + +endmenu # Aliro workqueue diff --git a/app/src/aliro/aliro_work/aliro_work.cpp b/app/src/aliro/aliro_work/aliro_work.cpp new file mode 100644 index 00000000..68b8ae4a --- /dev/null +++ b/app/src/aliro/aliro_work/aliro_work.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro_work.h" + +#include + +namespace { + +k_work_q sAliroWorkQ; +K_THREAD_STACK_DEFINE(sAliroWorkQStack, CONFIG_DOOR_LOCK_ALIRO_WORKQUEUE_STACK_SIZE); +bool sAliroWorkQStarted = false; + +} // namespace + +extern "C" { + +int AliroWorkSubmit(struct k_work *work) +{ + return k_work_submit_to_queue(&sAliroWorkQ, work); +} + +int AliroWorkReschedule(struct k_work_delayable *work, k_timeout_t delay) +{ + return k_work_reschedule_for_queue(&sAliroWorkQ, work, delay); +} + +int AliroWorkInit(void) +{ + if (sAliroWorkQStarted) { + return 0; + } + + constexpr k_work_queue_config config{ + .name = "aliroworkq", + .no_yield = false, + .essential = true, + .work_timeout_ms = 0, + }; + + k_work_queue_start(&sAliroWorkQ, sAliroWorkQStack, K_THREAD_STACK_SIZEOF(sAliroWorkQStack), + CONFIG_DOOR_LOCK_ALIRO_WORKQUEUE_PRIORITY, &config); + sAliroWorkQStarted = true; + return 0; +} + +} // extern "C" diff --git a/app/src/aliro/aliro_work/aliro_work.h b/app/src/aliro/aliro_work/aliro_work.h new file mode 100644 index 00000000..bea2a05e --- /dev/null +++ b/app/src/aliro/aliro_work/aliro_work.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize and start the dedicated Aliro workqueue. + * + * @return 0 on success. + */ +int AliroWorkInit(void); + +/** + * @brief Submit a work item to the dedicated Aliro workqueue. + * + * @param work Work item to submit. + * + * @return Return 0 on success, negative errno on failure. + */ +int AliroWorkSubmit(struct k_work *work); + +/** + * @brief Reschedule a delayable work item on the dedicated Aliro workqueue. + * + * @param work Delayable work item to schedule. + * @param delay Delay before the work should run. + * + * @return Return 0 on success, negative errno on failure. + */ +int AliroWorkReschedule(struct k_work_delayable *work, k_timeout_t delay); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/aliro/cli/CMakeLists.txt b/app/src/aliro/cli/CMakeLists.txt index 25de9ea9..e318457c 100644 --- a/app/src/aliro/cli/CMakeLists.txt +++ b/app/src/aliro/cli/CMakeLists.txt @@ -4,7 +4,18 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -file(GLOB src CONFIGURE_DEPENDS ./*.cpp) +zephyr_library_sources( + info.cpp + shell.c + shell_private.cpp +) + +zephyr_library_sources_ifdef(CONFIG_BT btaddr.cpp) +zephyr_library_sources_ifdef(CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE kpersistent.cpp) +zephyr_library_sources_ifndef(CONFIG_CHIP + factory_reset.cpp + install.cpp + provisioning.cpp +) -zephyr_library_sources(${src}) zephyr_include_directories(.) diff --git a/app/src/aliro/cli/btaddr.cpp b/app/src/aliro/cli/btaddr.cpp new file mode 100644 index 00000000..156a99b2 --- /dev/null +++ b/app/src/aliro/cli/btaddr.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include + +namespace { + +int ShellCmdHandleBtAddr(const struct shell *shell, size_t, char **) +{ + bt_addr_le_t address[1]; + size_t count{ ARRAY_SIZE(address) }; + bt_id_get(address, &count); + + char addr_str[BT_ADDR_LE_STR_LEN]; + bt_addr_le_to_str(address, addr_str, sizeof(addr_str)); + + shell_print(shell, "%s", addr_str); + return 0; +} + +} // namespace + +SHELL_SUBCMD_ADD((dl), btaddr, NULL, "Show BLE address", ShellCmdHandleBtAddr, 0, 0); diff --git a/app/src/aliro/cli/factory_reset.cpp b/app/src/aliro/cli/factory_reset.cpp new file mode 100644 index 00000000..e8a0a815 --- /dev/null +++ b/app/src/aliro/cli/factory_reset.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "shell_private.h" + +#include +#include + +#ifdef CONFIG_SETTINGS_NVS +#include +#else // CONFIG_SETTINGS_ZMS +#include +#endif // CONFIG_SETTINGS_NVS || CONFIG_SETTINGS_ZMS + +#include +#include + +namespace { + +K_WORK_DELAYABLE_DEFINE(sRebootWork, [](k_work *) { sys_reboot(SYS_REBOOT_WARM); }); + +int ShellCmdHandleFactoryReset(const struct shell *shell, size_t, char **) +{ + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); + + void *storage{ nullptr }; + int status = settings_storage_get(&storage); + VerifyOrReturnStatus(status == 0, -EIO, shell_warn(shell, "Cannot get storage\n")); + +#ifdef CONFIG_SETTINGS_NVS + status = nvs_clear(static_cast(storage)); +#else // CONFIG_SETTINGS_ZMS + status = zms_clear(static_cast(storage)); +#endif // CONFIG_SETTINGS_NVS || CONFIG_SETTINGS_ZMS + + VerifyOrReturnStatus(status == 0, -EIO, shell_warn(shell, "Cannot clear storage\n")); + k_work_reschedule(&sRebootWork, K_MSEC(250)); + return 0; +} + +} // namespace + +SHELL_SUBCMD_ADD((dl), factory_reset, NULL, "Factory reset", ShellCmdHandleFactoryReset, 0, 0); diff --git a/app/src/aliro/cli/info.cpp b/app/src/aliro/cli/info.cpp new file mode 100644 index 00000000..43f90714 --- /dev/null +++ b/app/src/aliro/cli/info.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include + +namespace { + +const char *GetReaderChipName(void) +{ +#if defined(CONFIG_ST25R200_DRV) + return "ST25R100"; +#elif defined(CONFIG_ST25R500_DRV) + return "ST25R300"; +#else + return "Unknown NFC reader driver"; +#endif +} + +int ShellCmdHandleInfo(const struct shell *shell, size_t, char **) +{ + shell_print(shell, "Aliro version: %s", Aliro::AliroStack::GetLibraryVersion()); + shell_print(shell, "NFC reader: %s", GetReaderChipName()); + return 0; +} + +} // namespace + +SHELL_SUBCMD_ADD((dl), info, NULL, "Show Aliro lib version and NFC reader chip name", ShellCmdHandleInfo, 0, 0); diff --git a/app/src/aliro/cli/install.cpp b/app/src/aliro/cli/install.cpp new file mode 100644 index 00000000..fbe9be2a --- /dev/null +++ b/app/src/aliro/cli/install.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "shell_private.h" + +#include +#include + +#include "../aliro_state_control.h" +#include "aliro/utils/hex_string.h" +#include "reader_cache.h" +#include "storage.h" +#include "storage_keys.h" + +#include +#include + +namespace { + +constexpr char kCmdReaderIdentifier[] = "identifier"; +constexpr char kCmdReaderGroupIdentifier[] = "group_id"; +constexpr char kCmdReaderGroupSubIdentifier[] = "group_sub_id"; + +int ShellCmdHandleIdentifiers(const struct shell *shell, size_t argc, char **argv) +{ + VerifyOrReturnStatus(IN_RANGE(argc, 1, 2), -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); + + Aliro::Identifier identifier{}; + int status = KeyValueStorage::Instance().Get(Aliro::StorageKeys::kStorageKeyNameIdentifier, identifier.data(), + identifier.size()); + + size_t offset{ 0 }; + size_t length{ 0 }; + + if (CmdMatch(argv[0], kCmdReaderIdentifier)) { + offset = 0; + length = identifier.size(); + } else if (CmdMatch(argv[0], kCmdReaderGroupIdentifier)) { + offset = 0; + length = Aliro::kReaderGroupIdentifierLength; + } else if (CmdMatch(argv[0], kCmdReaderGroupSubIdentifier)) { + offset = Aliro::kReaderGroupIdentifierLength; + length = Aliro::kReaderGroupSubIdentifierLength; + } else { + return -EINVAL; + } + + if (argc == 1) { + VerifyOrReturnStatus(status == 0, status, + shell_warn(shell, "Cannot get %s, error: %d\n", + Aliro::StorageKeys::kStorageKeyNameIdentifier, status)); + + DoorLock::Utils::HexStringBuffer identifierHex{}; + VerifyOrReturnStatus(DoorLock::Utils::ArrayToHexString(identifierHex, identifier), -EINVAL, + shell_warn(shell, "Cannot format %s\n", argv[0])); + shell_print(shell, "%.*s", static_cast(length * 2), identifierHex.data() + offset * 2); + return 0; + } + + const size_t argLength = strlen(argv[1]); + VerifyOrReturnStatus(argLength == length * 2, -EINVAL, shell_warn(shell, "Invalid %s length!\n", argv[0])); + + hex2bin(argv[1], argLength, identifier.data() + offset, length); + + VerifyOrReturnStatus(!KeyValueStorage::Instance().Save(Aliro::StorageKeys::kStorageKeyNameIdentifier, + identifier.data(), identifier.size()), + -EINVAL, + shell_warn(shell, "Cannot update %s\n", Aliro::StorageKeys::kStorageKeyNameIdentifier)); + + AliroError aliroError = Aliro::ReaderCache::Instance().SetIdentifier(identifier); + VerifyOrReturnStatus(aliroError == ALIRO_NO_ERROR, -EINVAL, + shell_warn(shell, "Failed to set reader identifier\n")); + + AliroError err = DoorLock::AliroStateControl::UpdateAliroState(); + VerifyOrReturnValue(err == ALIRO_NO_ERROR, -EIO, + shell_warn(shell, "Failed to update Aliro state: %d\n", err.ToInt())); + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE( + install_cmd, + SHELL_CMD(identifier, NULL, + "Set or get reader identifier\n" + " Usage: dl install identifier <32-byte reader_identifier in hex without 0x>", + ShellCmdHandleIdentifiers), + SHELL_CMD(group_id, NULL, + "Set or get group ID\n" + " Usage: dl install group_id <16-byte reader_group_identifier in hex without 0x>", + ShellCmdHandleIdentifiers), + SHELL_CMD(group_sub_id, NULL, + "Set or get group sub ID\n" + " Usage: dl install group_sub_id <16-byte reader_group_sub_identifier in hex without 0x>", + ShellCmdHandleIdentifiers), + SHELL_SUBCMD_SET_END); + +} // namespace + +SHELL_SUBCMD_ADD((dl), install, &install_cmd, "Installation commands", NULL, 0, 0); diff --git a/app/src/aliro/cli/kpersistent.cpp b/app/src/aliro/cli/kpersistent.cpp new file mode 100644 index 00000000..6e1a0dfd --- /dev/null +++ b/app/src/aliro/cli/kpersistent.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "shell_private.h" + +#include +#include +#include + +#include "aliro/utils/hex_string.h" + +#include + +#include + +namespace { + +int ParseIndex(const struct shell *shell, const char *indexStr, size_t &index) +{ + char *endPtr{ nullptr }; + errno = 0; + unsigned long val = strtoul(indexStr, &endPtr, 10); + VerifyOrReturnStatus(errno == 0 && endPtr != indexStr, -EINVAL, shell_warn(shell, "Invalid index!\n")); + + index = val; + return 0; +} + +int PrintKpersistentKeys(const struct shell *shell, Aliro::CryptoTypes::KeyId *kpersistentKeyIds, + size_t kpersistentCount) +{ + shell_print(shell, "Number of Kpersistent keys: %u", kpersistentCount); + shell_print(shell, "Index ID Public Key"); + shell_print(shell, "--------------------------------"); + for (size_t i = 0; i < kpersistentCount; i++) { + const Aliro::CryptoTypes::KeyId kpersistentKeyId = kpersistentKeyIds[i]; + const size_t kpersistentKeyIndex = kpersistentKeyId - Aliro::kKpersistentRangeBegin; + + Aliro::CryptoTypes::PublicKey publicKey{}; + auto *kpersistentManager = GetShellKpersistentManager(); + VerifyOrReturnValue(kpersistentManager->GetAccessCredentialPublicKey(kpersistentKeyId, publicKey) == + ALIRO_NO_ERROR, + -EINVAL, shell_warn(shell, "Cannot get Access Credential public key\n")); + DoorLock::Utils::HexStringBuffer hexString{}; + VerifyOrReturnValue(DoorLock::Utils::ArrayToHexString(hexString, publicKey), -EINVAL, + shell_warn(shell, "Cannot convert Access Credential public key to hex\n")); + shell_print(shell, "%-2u 0x%08x %s", kpersistentKeyIndex, kpersistentKeyId, hexString.data()); + } + + return 0; +} + +int ShellCmdHandleKpersistentList(const struct shell *shell, size_t argc, char **argv) +{ + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); + auto *kpersistentManager = GetShellKpersistentManager(); + VerifyOrReturnValue(kpersistentManager, -EIO, shell_warn(shell, "Kpersistent manager not initialized\n")); + + VerifyOrReturnValue(argc == 1, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); + + size_t kpersistentCount{}; + VerifyOrReturnValue(kpersistentManager->GetKpersistentCount(kpersistentCount) == ALIRO_NO_ERROR, -EINVAL, + shell_warn(shell, "Cannot get Kpersistent count\n")); + VerifyOrReturnValue(kpersistentCount > 0, 0, shell_print(shell, "No Kpersistent keys found\n")); + + auto kpersistentKeyIds = Aliro::make_unique_array_nothrow(kpersistentCount); + VerifyOrReturnValue(kpersistentKeyIds, -ENOMEM, + shell_warn(shell, "Cannot allocate memory for Kpersistent key IDs\n")); + + VerifyOrReturnValue(kpersistentManager->GetKpersistentKeyIds(kpersistentKeyIds.get(), kpersistentCount) == + ALIRO_NO_ERROR, + -EINVAL, shell_warn(shell, "Cannot get Kpersistent key IDs\n")); + + return PrintKpersistentKeys(shell, kpersistentKeyIds.get(), kpersistentCount); +} + +int ShellCmdHandleKpersistentClear(const struct shell *shell, size_t argc, char **argv) +{ + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); + auto *kpersistentManager = GetShellKpersistentManager(); + VerifyOrReturnValue(kpersistentManager, -EIO, shell_warn(shell, "Kpersistent manager not initialized\n")); + + VerifyOrReturnValue(argc == 2, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); + + if (strcmp(argv[1], "all") == 0) { + shell_print(shell, "Removing all Kpersistent keys"); + kpersistentManager->RemoveAllKpersistent(); + return 0; + } + + size_t index{}; + VerifyOrReturnValue(ParseIndex(shell, argv[1], index) == 0, -EINVAL); + shell_print(shell, "Removing Kpersistent key with index: %u", index); + VerifyOrReturnValue(kpersistentManager->RemoveKpersistent(index) == ALIRO_NO_ERROR, -EINVAL, + shell_warn(shell, "Cannot remove Kpersistent key\n")); + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(kpersistent_cmd, + SHELL_CMD(list, NULL, + "List Kpersistent keys\n" + " Usage: dl kpersistent list", + ShellCmdHandleKpersistentList), + SHELL_CMD(clear, NULL, + "Clear Kpersistent key\n" + " Usage: dl kpersistent clear \n" + " dl kpersistent clear all", + ShellCmdHandleKpersistentClear), + SHELL_SUBCMD_SET_END); + +} // namespace + +SHELL_SUBCMD_ADD((dl), kpersistent, &kpersistent_cmd, "Manage Kpersistent keys", NULL, 0, 0); diff --git a/app/src/aliro/cli/shell.cpp b/app/src/aliro/cli/provisioning.cpp similarity index 57% rename from app/src/aliro/cli/shell.cpp rename to app/src/aliro/cli/provisioning.cpp index 801372ae..65932235 100644 --- a/app/src/aliro/cli/shell.cpp +++ b/app/src/aliro/cli/provisioning.cpp @@ -1,31 +1,23 @@ /* - * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright (c) 2026 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#include "shell.h" +#include "shell_private.h" #include -#include #include -#ifdef CONFIG_DOOR_LOCK_READER_CERTIFICATE -#include "reader_certificate_cache.h" -#endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE - #include "aliro/crypto_key_ids.h" +#include "aliro/utils/hex_string.h" +#include "crypto/utils.h" +#include "reader_cache.h" #include -#ifdef CONFIG_DOOR_LOCK_DFU_BLE_SMP_STANDALONE -#include "dfu_smp_shell.h" -#endif // CONFIG_DOOR_LOCK_DFU_BLE_SMP_STANDALONE - -#ifndef CONFIG_CHIP - -#include "access_manager/access_manager.h" -#include "crypto/crypto.h" +#include "../aliro_state_control.h" +#include "access_manager.h" #include "storage.h" #include "storage_keys.h" @@ -33,53 +25,24 @@ #include "validity_iterations.h" #endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE -#ifdef CONFIG_SETTINGS_NVS -#include -#else // CONFIG_SETTINGS_ZMS -#include -#endif // CONFIG_SETTINGS_NVS || CONFIG_SETTINGS_ZMS - -#include -#include #include -#endif // CONFIG_CHIP - +#include #include namespace { using namespace Aliro; -constexpr size_t kPublicKeyStringLength{ 2 * CryptoTypes::kEccP256PublicKeyLength }; - -// Flag to check if shell is already initialized. -bool isInitialized{ false }; - -#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE -KpersistentManager *sKpersistentManager{ nullptr }; -#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - -template constexpr size_t CStrLen(const char (&)[N]) -{ - return N; -} - -template bool CmdMatch(const char *cmd, const char (&cmdStr)[N]) -{ - return strncmp(cmd, cmdStr, CStrLen(cmdStr)) == 0; -} - -#ifndef CONFIG_CHIP +#ifdef CONFIG_DOOR_LOCK_READER_CERTIFICATE +using CertificateData = std::array; +#endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE using PublicKeyType = AccessManager::PublicKeyType; -constexpr char kCmdReaderIdentifier[] = "identifier"; -constexpr char kCmdReaderGroupIdentifier[] = "group_id"; -constexpr char kCmdReaderGroupSubIdentifier[] = "group_sub_id"; constexpr size_t kAcMaxKeys{ CONFIG_DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS }; [[maybe_unused]] constexpr size_t kCiMaxKeys{ CONFIG_DOOR_LOCK_ACCESS_MANAGER_CREDENTIAL_ISSUER_MAX_STORED_KEYS }; - -K_WORK_DELAYABLE_DEFINE(sRebootWork, [](k_work *) { sys_reboot(SYS_REBOOT_WARM); }); +constexpr size_t kPublicKeyStringLength{ 2 * CryptoTypes::kEccP256PublicKeyLength }; +constexpr size_t kPrivateKeyStringLength{ 2 * CryptoTypes::kEccP256KeyPrivateKeyLength }; constexpr PublicKeyType GetPublicKeyTypeFromStorageKey(KeyValueStorage::KeyIdString keyName) { @@ -87,55 +50,55 @@ constexpr PublicKeyType GetPublicKeyTypeFromStorageKey(KeyValueStorage::KeyIdStr PublicKeyType::CredentialIssuer; } -int ShellCmdHandleIdentifiers(const struct shell *shell, size_t argc, char **argv) +int ShellCmdHandleReaderPrivateKeySet(const struct shell *shell, size_t argc, char **argv) { - VerifyOrReturnStatus(IN_RANGE(argc, 1, 2), -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); - - Identifier identifier{}; - std::array identifierStr{}; - - int status = KeyValueStorage::Instance().Get(StorageKeys::kStorageKeyNameIdentifier, identifier.data(), - identifier.size()); - - size_t offset{ 0 }; - size_t length{ 0 }; - - if (CmdMatch(argv[0], kCmdReaderIdentifier)) { - offset = 0; - length = identifier.size(); - } else if (CmdMatch(argv[0], kCmdReaderGroupIdentifier)) { - offset = 0; - length = kReaderGroupIdentifierLength; - } else if (CmdMatch(argv[0], kCmdReaderGroupSubIdentifier)) { - offset = kReaderGroupIdentifierLength; - length = kReaderGroupSubIdentifierLength; - } else { - return -EINVAL; - } + VerifyOrReturnStatus(argc == 2, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); - if (argc == 1) { - VerifyOrReturnStatus(status == 0, status, - shell_warn(shell, "Cannot get %s, error: %d\n", - StorageKeys::kStorageKeyNameIdentifier, status)); + const char *keyStr{ argv[1] }; + size_t len = strlen(keyStr); + VerifyOrReturnStatus(len == kPrivateKeyStringLength, -EINVAL, + shell_warn(shell, "Invalid key length (must be %zu hex chars)\n", + kPrivateKeyStringLength)); - bin2hex(identifier.data() + offset, length, identifierStr.data(), identifierStr.size()); - shell_print(shell, "%s", identifierStr.data()); + CryptoTypes::PrivateKey privateKey{}; + const size_t decodedLen = hex2bin(keyStr, len, privateKey.data(), privateKey.size()); + VerifyOrReturnStatus(decodedLen == privateKey.size(), -EINVAL, shell_warn(shell, "Invalid key hex string!\n")); - return 0; - } + CryptoTypes::KeyId keyId{ kPrivateKeyId }; + const auto err = DoorLock::Crypto::ImportPrivateKey(privateKey, true, keyId); + VerifyOrReturnStatus(err == ALIRO_NO_ERROR, -EIO, shell_warn(shell, "Failed to import private key\n")); + + CryptoTypes::PublicKey publicKey{}; + const auto exportErr = DoorLock::Crypto::ExportPublicKey(keyId, publicKey); + VerifyOrReturnStatus(exportErr == ALIRO_NO_ERROR, -EIO, shell_warn(shell, "Failed to export public key\n")); - size_t argLength = strlen(argv[1]); + AliroError cacheErr = ReaderCache::Instance().SetPublicKey(publicKey); + VerifyOrReturnStatus(cacheErr == ALIRO_NO_ERROR, -EIO, shell_warn(shell, "Failed to cache public key\n")); - VerifyOrReturnStatus(argLength == length * 2, -EINVAL, shell_warn(shell, "Invalid %s length!\n", argv[0])); + AliroError ec = DoorLock::AliroStateControl::UpdateAliroState(); + VerifyOrReturnValue(ec == ALIRO_NO_ERROR, -EIO, + shell_warn(shell, "Failed to update Aliro state: %d\n", ec.ToInt())); - hex2bin(argv[1], argLength, identifier.data() + offset, length); + return 0; +} - VerifyOrReturnStatus(!KeyValueStorage::Instance().Save(StorageKeys::kStorageKeyNameIdentifier, - identifier.data(), identifier.size()), - -EINVAL, shell_warn(shell, "Cannot update %s\n", StorageKeys::kStorageKeyNameIdentifier)); +int ShellCmdHandleReaderPrivateKeyClear(const struct shell *shell, size_t argc, char **) +{ + VerifyOrReturnStatus(argc == 1, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); - AliroStack::Instance().SetReaderIdentifier(identifier); + CryptoTypes::KeyId keyId{ kPrivateKeyId }; + const auto err = DoorLock::Crypto::DestroyKey(keyId); + VerifyOrReturnStatus(err == ALIRO_NO_ERROR, -EIO, shell_warn(shell, "Failed to destroy private key\n")); + + ReaderCache::Instance().ClearPublicKey(); + + shell_print(shell, "Reader private key cleared"); + + AliroError ec = DoorLock::AliroStateControl::UpdateAliroState(); + VerifyOrReturnValue(ec == ALIRO_NO_ERROR, -EIO, + shell_warn(shell, "Failed to update Aliro state: %d\n", ec.ToInt())); return 0; } @@ -203,14 +166,16 @@ int ParseKeyId(const struct shell *shell, KeyValueStorage::KeyIdString keyIdStr, int CmdHandleAndListKey(const struct shell *shell, KeyValueStorage::KeyIdString keyIdStr, size_t numSlots) { - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); - std::array hexString{}; + DoorLock::Utils::HexStringBuffer hexString{}; CryptoTypes::PublicKey publicKey{}; for (size_t keyId = 0; keyId < numSlots; keyId++) { if (GetPublicKeyFromStorage(keyIdStr, keyId, publicKey) == 0) { - bin2hex(publicKey.data(), publicKey.size(), hexString.data(), hexString.size()); + if (!DoorLock::Utils::ArrayToHexString(hexString, publicKey)) { + snprintf(hexString.data(), hexString.size(), "(invalid)"); + } } else { snprintf(hexString.data(), hexString.size(), "(null)"); } @@ -225,7 +190,7 @@ int CmdHandleAndClearKey(const struct shell *shell, size_t argc, char **argv, Ke size_t numSlots, size_t &keyId) { VerifyOrReturnStatus(argc == 2, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); constexpr char kAll[] = "all"; if (strncmp(argv[1], kAll, CStrLen(kAll)) == 0) { @@ -249,7 +214,7 @@ int CmdHandleAndSetKey(const struct shell *shell, size_t argc, char **argv, KeyV size_t numSlots, size_t &keyId) { VerifyOrReturnStatus(argc == 3, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); int status = ParseKeyId(shell, argv[1], keyId, numSlots); VerifyOrReturnStatus(status == 0, status); @@ -294,15 +259,21 @@ int ShellCmdHandleAccessCredentialList(const struct shell *shell, size_t, char * int ShellCmdHandleAccessCredentialSet(const struct shell *shell, size_t argc, char **argv) { size_t keyId{}; - return CmdHandleAndSetKey(shell, argc, argv, StorageKeys::kStorageKeyNameAccessCredentialPublicKey, kAcMaxKeys, - keyId); + const int rc = CmdHandleAndSetKey(shell, argc, argv, StorageKeys::kStorageKeyNameAccessCredentialPublicKey, + kAcMaxKeys, keyId); + VerifyOrReturnValue(rc == 0, rc); + + return 0; } int ShellCmdHandleAccessCredentialClear(const struct shell *shell, size_t argc, char **argv) { size_t keyId{}; - return CmdHandleAndClearKey(shell, argc, argv, StorageKeys::kStorageKeyNameAccessCredentialPublicKey, - kAcMaxKeys, keyId); + const int rc = CmdHandleAndClearKey(shell, argc, argv, StorageKeys::kStorageKeyNameAccessCredentialPublicKey, + kAcMaxKeys, keyId); + VerifyOrReturnValue(rc == 0, rc); + + return 0; } #if CONFIG_DOOR_LOCK_ACCESS_MANAGER_CREDENTIAL_ISSUER_MAX_STORED_KEYS > 0 @@ -338,16 +309,17 @@ int ShellCmdHandleCredentialIssuerClear(const struct shell *shell, size_t argc, int ShellCmdHandleCredentialIssuerCAGet(const struct shell *shell, size_t argc, char **) { VerifyOrReturnStatus(argc == 1, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); CryptoTypes::PublicKey publicKey{}; const auto error = - CryptoInstance().ExportKey(kCredentialIssuerCAPublicKeyId, publicKey.data(), publicKey.size()); + DoorLock::Crypto::ExportKey(kCredentialIssuerCAPublicKeyId, publicKey.data(), publicKey.size()); VerifyOrReturnStatus(error == ALIRO_NO_ERROR, -EINVAL, shell_warn(shell, "Cannot export Credential Issuer CA public key\n")); - std::array hexString{}; - bin2hex(publicKey.data(), publicKey.size(), hexString.data(), hexString.size()); + DoorLock::Utils::HexStringBuffer hexString{}; + VerifyOrReturnStatus(DoorLock::Utils::ArrayToHexString(hexString, publicKey), -EINVAL, + shell_warn(shell, "Cannot convert Credential Issuer CA public key to hex\n")); shell_print(shell, "%s", hexString.data()); return 0; @@ -356,7 +328,7 @@ int ShellCmdHandleCredentialIssuerCAGet(const struct shell *shell, size_t argc, int ShellCmdHandleCredentialIssuerCASet(const struct shell *shell, size_t argc, char **argv) { VerifyOrReturnStatus(argc == 2, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); const char *pubkeyStr{ argv[1] }; size_t len = strlen(pubkeyStr); @@ -370,174 +342,42 @@ int ShellCmdHandleCredentialIssuerCASet(const struct shell *shell, size_t argc, shell_warn(shell, "Invalid key prefix!\n")); CryptoTypes::KeyId keyId{ kCredentialIssuerCAPublicKeyId }; - const auto error = CryptoInstance().ImportPublicKey(publicKey, keyId, true); + const auto error = DoorLock::Crypto::ImportPublicKey(publicKey, true, keyId); VerifyOrReturnStatus(error == ALIRO_NO_ERROR, -EINVAL, shell_warn(shell, "Cannot import Credential Issuer CA public key\n")); - AliroStack::Instance().SetCredentialIssuerCAPublicKeyId(keyId); - return 0; } int ShellCmdHandleCredentialIssuerCAClear(const struct shell *shell, size_t argc, char **argv) { VerifyOrReturnStatus(argc == 1, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); CryptoTypes::KeyId keyId{ kCredentialIssuerCAPublicKeyId }; - const auto error = CryptoInstance().DestroyKey(keyId); + const auto error = DoorLock::Crypto::DestroyKey(keyId); VerifyOrReturnStatus(error == ALIRO_NO_ERROR, -EINVAL, shell_warn(shell, "Cannot remove Credential Issuer CA public key\n")); - AliroStack::Instance().SetCredentialIssuerCAPublicKeyId(keyId); - return 0; } #endif // CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA -int FactoryReset(const struct shell *shell, size_t, char **) -{ - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); - - void *storage{ nullptr }; - int status = settings_storage_get(&storage); - VerifyOrReturnStatus(status == 0, -EIO, shell_warn(shell, "Cannot get storage\n")); - -#ifdef CONFIG_SETTINGS_NVS - status = nvs_clear(static_cast(storage)); -#else // CONFIG_SETTINGS_ZMS - status = zms_clear(static_cast(storage)); -#endif // CONFIG_SETTINGS_NVS || CONFIG_SETTINGS_ZMS - - VerifyOrReturnStatus(status == 0, -EIO, shell_warn(shell, "Cannot clear storage\n")); - k_work_reschedule(&sRebootWork, K_MSEC(250)); - return 0; -} - -#endif // CONFIG_CHIP - -const char *GetReaderChipName(void) -{ -#if defined(CONFIG_ST25R200_DRV) - return "ST25R100"; -#elif defined(CONFIG_ST25R3911_DRV) - return "ST25R3911"; -#elif defined(CONFIG_ST25R3916_DRV) - return "ST25R3916"; -#elif defined(CONFIG_ST25R3916B_DRV) - return "ST25R3916B"; -#else - return "Unknown NFC reader driver"; -#endif -} - -int ShellCmdHandleInfo(const struct shell *shell, size_t, char **) -{ - shell_print(shell, "Aliro version: %s", AliroStack::GetLibraryVersion()); - shell_print(shell, "NFC reader: %s", GetReaderChipName()); - return 0; -} - -#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - -int ParseIndex(const struct shell *shell, const char *indexStr, size_t &index) -{ - char *endPtr{ nullptr }; - errno = 0; - unsigned long val = strtoul(indexStr, &endPtr, 10); - VerifyOrReturnStatus(errno == 0 && endPtr != indexStr, -EINVAL, shell_warn(shell, "Invalid index!\n")); - - index = val; - - return 0; -} - -int PrintKpersistentKeys(const struct shell *shell, CryptoTypes::KeyId *kpersistentKeyIds, size_t kpersistentCount) -{ - shell_print(shell, "Number of Kpersistent keys: %u", kpersistentCount); - shell_print(shell, "Index ID Public Key"); - shell_print(shell, "--------------------------------"); - for (size_t i = 0; i < kpersistentCount; i++) { - const CryptoTypes::KeyId kpersistentKeyId = kpersistentKeyIds[i]; - const size_t kpersistentKeyIndex = kpersistentKeyId - kKpersistentRangeBegin; - - CryptoTypes::PublicKey publicKey{}; - VerifyOrReturnValue(sKpersistentManager->GetAccessCredentialPublicKey(kpersistentKeyId, publicKey) == - ALIRO_NO_ERROR, - -EINVAL, shell_warn(shell, "Cannot get Access Credential public key\n")); - std::array hexString{}; - bin2hex(publicKey.data(), publicKey.size(), hexString.data(), hexString.size()); - shell_print(shell, "%-2u 0x%08x %s", kpersistentKeyIndex, kpersistentKeyId, hexString.data()); - } - - return 0; -} - -int ShellCmdHandleKpersistentList(const struct shell *shell, size_t argc, char **argv) -{ - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); - VerifyOrReturnValue(sKpersistentManager, -EIO, shell_warn(shell, "Kpersistent manager not initialized\n")); - VerifyOrReturnValue(argc == 1, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - - constexpr char kList[] = "list"; - VerifyOrReturnValue(CmdMatch(argv[0], kList), -EINVAL, shell_warn(shell, "Invalid command!\n")); - - size_t kpersistentCount{}; - VerifyOrReturnValue(sKpersistentManager->GetKpersistentCount(kpersistentCount) == ALIRO_NO_ERROR, -EINVAL, - shell_warn(shell, "Cannot get Kpersistent count\n")); - VerifyOrReturnValue(kpersistentCount > 0, 0, shell_print(shell, "No Kpersistent keys found\n")); - - auto kpersistentKeyIds = Aliro::make_unique_array_nothrow(kpersistentCount); - VerifyOrReturnValue(kpersistentKeyIds, -ENOMEM, - shell_warn(shell, "Cannot allocate memory for Kpersistent key IDs\n")); - - VerifyOrReturnValue(sKpersistentManager->GetKpersistentKeyIds(kpersistentKeyIds.get(), kpersistentCount) == - ALIRO_NO_ERROR, - -EINVAL, shell_warn(shell, "Cannot get Kpersistent key IDs\n")); - - return PrintKpersistentKeys(shell, kpersistentKeyIds.get(), kpersistentCount); -} - -int ShellCmdHandleKpersistentClear(const struct shell *shell, size_t argc, char **argv) -{ - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); - VerifyOrReturnValue(sKpersistentManager, -EIO, shell_warn(shell, "Kpersistent manager not initialized\n")); - VerifyOrReturnValue(argc == 2, -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - - constexpr char kRemove[] = "clear"; - VerifyOrReturnValue(CmdMatch(argv[0], kRemove), -EINVAL, shell_warn(shell, "Invalid command!\n")); - - constexpr char kAll[] = "all"; - if (strncmp(argv[1], kAll, CStrLen(kAll)) == 0) { - shell_print(shell, "Removing all Kpersistent keys"); - sKpersistentManager->RemoveAllKpersistent(); - return 0; - } - - size_t index{}; - VerifyOrReturnValue(ParseIndex(shell, argv[1], index) == 0, -EINVAL); - shell_print(shell, "Removing Kpersistent key with index: %u", index); - VerifyOrReturnValue(sKpersistentManager->RemoveKpersistent(index) == ALIRO_NO_ERROR, -EINVAL, - shell_warn(shell, "Cannot remove Kpersistent key\n")); - return 0; -} - -#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - #ifdef CONFIG_DOOR_LOCK_READER_CERTIFICATE int ShellCmdHandleReaderCertList(const struct shell *shell, size_t, char **) { - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); ConstData cert{}; - const auto error = ReaderCertificateCache::Instance().GetCertificate(cert); + const auto error = ReaderCache::Instance().GetCertificate(cert); if (error == ALIRO_NO_ERROR) { - std::array hexString{ 0 }; - size_t len = bin2hex(cert.mData, cert.mLength, hexString.data(), hexString.size()); - VerifyOrReturnStatus(len == cert.mLength * 2, -EINVAL, - shell_warn(shell, "Invalid certificate hex string!\n")); + DoorLock::Utils::HexStringBuffer hexString{}; + CertificateData certificateData{}; + std::copy_n(cert.mData, cert.mLength, certificateData.begin()); + VerifyOrReturnStatus(DoorLock::Utils::ArrayToHexString(hexString, certificateData, cert.mLength), + -EINVAL, shell_warn(shell, "Invalid certificate hex string!\n")); shell_print(shell, "Reader certificate (%zu bytes): %s", cert.mLength, hexString.data()); } else { shell_print(shell, "Reader certificate: (not set)"); @@ -549,30 +389,26 @@ int ShellCmdHandleReaderCertList(const struct shell *shell, size_t, char **) int ShellCmdHandleReaderCertSet(const struct shell *shell, size_t argc, char **argv) { VerifyOrReturnStatus(IN_RANGE(argc, 2, 2), -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); const char *certStr = argv[1]; size_t certStrLen = strlen(certStr); - VerifyOrReturnStatus(certStrLen > 0 && certStrLen % 2 == 0, -EINVAL, shell_warn(shell, "Invalid certificate length (must be even)!\n")); - std::array certData{ 0 }; + CertificateData certData{}; size_t decodedLen = hex2bin(certStr, certStrLen, certData.data(), certData.size()); VerifyOrReturnStatus(decodedLen == certStrLen / 2, -EINVAL, shell_warn(shell, "Invalid certificate hex string!\n")); - // save to cache - AliroError error = ReaderCertificateCache::Instance().SetCertificate({ certData.data(), decodedLen }); + AliroError error = ReaderCache::Instance().SetCertificate({ certData.data(), decodedLen }); VerifyOrReturnStatus(error == ALIRO_NO_ERROR, -EINVAL, shell_warn(shell, "Failed to set certificate: %d\n", error.ToInt())); - // save certificate data to persistent storage VerifyOrReturnStatus(!KeyValueStorage::Instance().Save(StorageKeys::kStorageKeyNameReaderCertificate, certData.data(), decodedLen), -EINVAL, shell_warn(shell, "Cannot save certificate to persistent storage!\n")); - // save certificate length to persistent storage uint16_t certLength = static_cast(decodedLen); VerifyOrReturnStatus(!KeyValueStorage::Instance().Save(StorageKeys::kStorageKeyNameReaderCertificateLength, reinterpret_cast(&certLength), @@ -586,16 +422,13 @@ int ShellCmdHandleReaderCertSet(const struct shell *shell, size_t argc, char **a int ShellCmdHandleReaderCertClear(const struct shell *shell, size_t argc, char **) { VerifyOrReturnStatus(IN_RANGE(argc, 1, 1), -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Not initialized yet\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Not initialized yet\n")); - // clear cache - ReaderCertificateCache::Instance().ClearCertificate(); + ReaderCache::Instance().ClearCertificate(); - // clear certificate data from persistent storage VerifyOrReturnStatus(!KeyValueStorage::Instance().Clear(StorageKeys::kStorageKeyNameReaderCertificate), -EIO, shell_warn(shell, "Cannot clear certificate from persistent storage!\n")); - // clear certificate length from persistent storage VerifyOrReturnStatus(!KeyValueStorage::Instance().Clear(StorageKeys::kStorageKeyNameReaderCertificateLength), -EIO, shell_warn(shell, "Cannot clear certificate length from persistent storage!\n")); @@ -605,14 +438,13 @@ int ShellCmdHandleReaderCertClear(const struct shell *shell, size_t argc, char * int ShellCmdHandleIssuerPublicKeyList(const struct shell *shell, size_t, char **) { - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Shell not initialized\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Shell not initialized\n")); CryptoTypes::PublicKey publicKey{}; - const auto error = ReaderCertificateCache::Instance().GetIssuerPublicKey(publicKey); + const auto error = ReaderCache::Instance().GetIssuerPublicKey(publicKey); if (error == ALIRO_NO_ERROR) { - std::array hexString{ 0 }; - size_t len = bin2hex(publicKey.data(), publicKey.size(), hexString.data(), hexString.size()); - VerifyOrReturnStatus(len == publicKey.size() * 2, -EINVAL, + DoorLock::Utils::HexStringBuffer hexString{}; + VerifyOrReturnStatus(DoorLock::Utils::ArrayToHexString(hexString, publicKey), -EINVAL, shell_warn(shell, "Invalid public key hex string!\n")); shell_print(shell, "Issuer public key (%zu bytes): %s", publicKey.size(), hexString.data()); } else { @@ -625,11 +457,10 @@ int ShellCmdHandleIssuerPublicKeyList(const struct shell *shell, size_t, char ** int ShellCmdHandleIssuerPublicKeySet(const struct shell *shell, size_t argc, char **argv) { VerifyOrReturnStatus(IN_RANGE(argc, 2, 2), -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Shell not initialized\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Shell not initialized\n")); const char *keyStr = argv[1]; size_t keyStrLen = strlen(keyStr); - VerifyOrReturnStatus(keyStrLen == CryptoTypes::kEccP256PublicKeyLength * 2, -EINVAL, shell_warn(shell, "Invalid key length (must be %zu hex characters)!\n", CryptoTypes::kEccP256PublicKeyLength * 2)); @@ -642,15 +473,13 @@ int ShellCmdHandleIssuerPublicKeySet(const struct shell *shell, size_t argc, cha VerifyOrReturnStatus(publicKey[0] == CryptoTypes::kEccP256PublicKeyPrefix, -EINVAL, shell_warn(shell, "Invalid key prefix (must be 0x04)!\n")); - // save to persistent storage VerifyOrReturnStatus( !KeyValueStorage::Instance().Save(StorageKeys::kStorageKeyNameReaderSystemIssuerCAPublicKey, publicKey.data(), publicKey.size()), -EINVAL, shell_warn(shell, "Cannot save issuer public key to persistent storage!\n")); - // save to cache - VerifyOrReturnStatus(ReaderCertificateCache::Instance().SetIssuerPublicKey(publicKey) == ALIRO_NO_ERROR, - -EINVAL, shell_warn(shell, "Failed to set issuer public key!\n")); + VerifyOrReturnStatus(ReaderCache::Instance().SetIssuerPublicKey(publicKey) == ALIRO_NO_ERROR, -EINVAL, + shell_warn(shell, "Failed to set issuer public key!\n")); shell_print(shell, "Issuer public key set successfully (%zu bytes)", publicKey.size()); @@ -660,15 +489,13 @@ int ShellCmdHandleIssuerPublicKeySet(const struct shell *shell, size_t argc, cha int ShellCmdHandleIssuerPublicKeyClear(const struct shell *shell, size_t argc, char **) { VerifyOrReturnStatus(IN_RANGE(argc, 1, 1), -EINVAL, shell_warn(shell, "Invalid number of arguments!\n")); - VerifyOrReturnValue(isInitialized, -EIO, shell_warn(shell, "Shell not initialized\n")); + VerifyOrReturnValue(IsShellInitialized(), -EIO, shell_warn(shell, "Shell not initialized\n")); - // clear from persistent storage VerifyOrReturnStatus( !KeyValueStorage::Instance().Clear(StorageKeys::kStorageKeyNameReaderSystemIssuerCAPublicKey), -EIO, shell_warn(shell, "Cannot clear issuer public key from persistent storage!\n")); - // clear from cache - ReaderCertificateCache::Instance().ClearIssuerPublicKey(); + ReaderCache::Instance().ClearIssuerPublicKey(); shell_print(shell, "Issuer public key cleared successfully"); return 0; @@ -676,35 +503,6 @@ int ShellCmdHandleIssuerPublicKeyClear(const struct shell *shell, size_t argc, c #endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE -} // namespace - -void InitShellCommands([[maybe_unused]] Aliro::KpersistentManager *kpersistentManager) -{ -#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - sKpersistentManager = kpersistentManager; -#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - - isInitialized = true; -} - -#ifndef CONFIG_CHIP - -SHELL_STATIC_SUBCMD_SET_CREATE( - install_cmd, - SHELL_CMD(identifier, NULL, - "Set or get reader identifier\n" - " Usage: dl install identifier <32-byte reader_identifier in hex without 0x>", - ShellCmdHandleIdentifiers), - SHELL_CMD(group_id, NULL, - "Set or get group ID\n" - " Usage: dl install group_id <16-byte reader_group_identifier in hex without 0x>", - ShellCmdHandleIdentifiers), - SHELL_CMD(group_sub_id, NULL, - "Set or get group sub ID\n" - " Usage: dl install group_sub_id <16-byte reader_group_sub_identifier in hex without 0x>", - ShellCmdHandleIdentifiers), - SHELL_SUBCMD_SET_END); - SHELL_STATIC_SUBCMD_SET_CREATE(AC_key_cmd, SHELL_CMD(list, NULL, "List Access Credential public keys\n" @@ -794,7 +592,19 @@ SHELL_STATIC_SUBCMD_SET_CREATE(issuer_pk_cmd, #endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE +SHELL_STATIC_SUBCMD_SET_CREATE(reader_prv_cmd, + SHELL_CMD(set, NULL, + "Set Reader private signing key (32 bytes)\n" + " Usage: dl provisioning reader_prv set <64-hex-chars>", + ShellCmdHandleReaderPrivateKeySet), + SHELL_CMD(clear, NULL, + "Clear Reader private signing key\n" + " Usage: dl provisioning reader_prv clear", + ShellCmdHandleReaderPrivateKeyClear), + SHELL_SUBCMD_SET_END); + SHELL_STATIC_SUBCMD_SET_CREATE(provisioning_cmd, + SHELL_CMD(reader_prv, &reader_prv_cmd, "Manage Reader private signing key", NULL), SHELL_CMD(AC_key, &AC_key_cmd, "Manage Access Credential public keys", NULL), #if CONFIG_DOOR_LOCK_ACCESS_MANAGER_CREDENTIAL_ISSUER_MAX_STORED_KEYS > 0 @@ -812,48 +622,6 @@ SHELL_STATIC_SUBCMD_SET_CREATE(provisioning_cmd, SHELL_SUBCMD_SET_END); -#endif // CONFIG_CHIP - -#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - -SHELL_STATIC_SUBCMD_SET_CREATE(kpersistent_cmd, - SHELL_CMD(list, NULL, - "List Kpersistent keys\n" - " Usage: dl kpersistent list", - ShellCmdHandleKpersistentList), - SHELL_CMD(clear, NULL, - "Clear Kpersistent key\n" - " Usage: dl kpersistent clear \n" - " dl kpersistent clear all", - ShellCmdHandleKpersistentClear), - SHELL_SUBCMD_SET_END); - -#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - -SHELL_STATIC_SUBCMD_SET_CREATE(door_lock_cmd, - - SHELL_CMD(info, NULL, "Show Aliro lib version and NFC reader chip name", - ShellCmdHandleInfo), - -#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - - SHELL_CMD(kpersistent, &kpersistent_cmd, "Manage Kpersistent keys", NULL), - -#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE - -#ifndef CONFIG_CHIP - -#ifdef CONFIG_DOOR_LOCK_DFU_BLE_SMP_STANDALONE - SHELL_CMD(dfu_smp, NULL, "Enable/disable DFU BLE SMP: dl dfu_smp ", - ShellCmdHandleDfuSmp), -#endif // CONFIG_DOOR_LOCK_DFU_BLE_SMP_STANDALONE - - SHELL_CMD(install, &install_cmd, "Installation commands", NULL), - SHELL_CMD(provisioning, &provisioning_cmd, "Provisioning commands", NULL), - SHELL_CMD(factory_reset, NULL, "Factory reset", FactoryReset), - -#endif // CONFIG_CHIP - - SHELL_SUBCMD_SET_END); +} // namespace -SHELL_CMD_REGISTER(dl, &door_lock_cmd, "Door lock commands", NULL); +SHELL_SUBCMD_ADD((dl), provisioning, &provisioning_cmd, "Provisioning commands", NULL, 0, 0); diff --git a/app/src/aliro/cli/shell.c b/app/src/aliro/cli/shell.c new file mode 100644 index 00000000..39b6bfc0 --- /dev/null +++ b/app/src/aliro/cli/shell.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +SHELL_SUBCMD_SET_CREATE(door_lock_cmd, (dl)); +SHELL_CMD_REGISTER(dl, &door_lock_cmd, "Door lock commands", NULL); diff --git a/app/src/aliro/cli/shell.h b/app/src/aliro/cli/shell.h index bd40580b..df89f7d6 100644 --- a/app/src/aliro/cli/shell.h +++ b/app/src/aliro/cli/shell.h @@ -6,7 +6,7 @@ #pragma once -#include "kpersistent_manager/kpersistent_manager.h" +#include "aliro/kpersistent_manager/kpersistent_manager.h" /** * @brief Initializes shell commands for managing a door lock system. diff --git a/app/src/aliro/cli/shell_private.cpp b/app/src/aliro/cli/shell_private.cpp new file mode 100644 index 00000000..0ca98c1b --- /dev/null +++ b/app/src/aliro/cli/shell_private.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "shell_private.h" + +#include "aliro/kpersistent_manager/kpersistent_manager.h" + +namespace { + +bool sShellInitialized{ false }; + +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +Aliro::KpersistentManager *sKpersistentManager{ nullptr }; +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +} // namespace + +bool IsShellInitialized() +{ + return sShellInitialized; +} + +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +Aliro::KpersistentManager *GetShellKpersistentManager() +{ + return sKpersistentManager; +} + +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +void InitShellCommands([[maybe_unused]] Aliro::KpersistentManager *kpersistentManager) +{ +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + sKpersistentManager = kpersistentManager; +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + + sShellInitialized = true; +} diff --git a/app/src/aliro/cli/shell_private.h b/app/src/aliro/cli/shell_private.h new file mode 100644 index 00000000..e7112bf1 --- /dev/null +++ b/app/src/aliro/cli/shell_private.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include +#include + +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +#include "aliro/kpersistent_manager/kpersistent_manager.h" +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +bool IsShellInitialized(); + +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +Aliro::KpersistentManager *GetShellKpersistentManager(); + +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +template constexpr size_t CStrLen(const char (&)[N]) +{ + return N; +} + +template bool CmdMatch(const char *cmd, const char (&cmdStr)[N]) +{ + return strncmp(cmd, cmdStr, CStrLen(cmdStr)) == 0; +} diff --git a/app/src/aliro/features.h b/app/src/aliro/features.h new file mode 100644 index 00000000..362c3381 --- /dev/null +++ b/app/src/aliro/features.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include + +namespace Aliro { + +/** + * @brief Aliro application feature bitmap bit positions. + * + */ +static constexpr uint8_t kFeatureCredentialIssuerCaPublicKeySupported = static_cast(BIT(0)); +static constexpr uint8_t kFeatureReaderCertificateSupported = static_cast(BIT(1)); +static constexpr uint8_t kFeatureMatterSupported = static_cast(BIT(2)); + +} // namespace Aliro diff --git a/app/src/aliro/init.cpp b/app/src/aliro/init.cpp index 5631dc48..2ab1d397 100644 --- a/app/src/aliro/init.cpp +++ b/app/src/aliro/init.cpp @@ -3,22 +3,21 @@ * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ + #include "aliro/aliro.h" +#include "aliro/features.h" #include "aliro/types.h" #include "aliro/utils.h" -#include "crypto/crypto.h" -#include "reader_certificate_cache.h" - -#ifdef CONFIG_ACCESS_DECISION_INDICATOR -#include "access_decision_indicator.h" -#endif // CONFIG_ACCESS_DECISION_INDICATOR +#include "crypto/utils.h" +#include "nfc/nfc_transport_rfal.h" +#include "reader_cache.h" -#ifdef CONFIG_DOOR_LOCK_USE_TEST_KEYS -#include "test_key.h" -#endif // CONFIG_DOOR_LOCK_USE_TEST_KEYS +#ifdef CONFIG_BT +#include "ble_manager.h" +#endif // CONFIG_BT #ifdef CONFIG_DOOR_LOCK_BLE_UWB -#include "ble_manager_impl.h" +#include "aliro/ble_types.h" #include "uwb_impl.h" #endif // CONFIG_DOOR_LOCK_BLE_UWB @@ -29,18 +28,25 @@ #include "kpersistent_manager/kpersistent_manager_impl.h" #endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE -#ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE +#if defined(CONFIG_DOOR_LOCK_STEP_UP_PHASE) && CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 #include "access_document.h" -#endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE +#endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE AND CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS +#include +#include +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS #ifdef CONFIG_DOOR_LOCK_CLI #include "shell.h" #endif // CONFIG_DOOR_LOCK_CLI #ifndef CONFIG_CHIP -#include "lock_sim/lock_sim.h" +#include "aliro_state_control.h" +#include "lock_sim/lock_sim_instance.h" #endif // CONFIG_CHIP +#include "aliro_work/aliro_work.h" #include "storage.h" #include "storage_keys.h" @@ -49,24 +55,23 @@ #include #include +#include LOG_MODULE_REGISTER(aliro, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); using namespace Aliro; -namespace { +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE -#ifndef CONFIG_CHIP +KpersistentManagerImpl sKpersistentManagerImpl; -LockSim sLockSim; +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE -#ifdef CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER +namespace { -constexpr Identifier kTestIdentifier{ 0x37, 0x65, 0x20, 0x39, 0x31, 0x20, 0x61, 0x65, 0x20, 0x31, 0x64, - 0x20, 0x33, 0x64, 0x20, 0x65, 0x63, 0x20, 0x38, 0x36, 0x20, 0x31, - 0x62, 0x20, 0x33, 0x39, 0x20, 0x31, 0x66, 0x20, 0x33, 0x34 }; +bool sAliroRunning{ false }; -#endif // CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER +#ifndef CONFIG_CHIP AliroError LoadCredentials(KeyValueStorage::KeyIdString pubKeyName, size_t KeyNum, size_t &keyCount) { @@ -105,23 +110,6 @@ AliroError LoadAccessCredentials() return ALIRO_NO_ERROR; } -void LoadCredentialIssuerCA(CryptoTypes::KeyId &credentialIssuerCAPublicKeyId) -{ - credentialIssuerCAPublicKeyId = 0; - -#ifdef CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA - - CryptoTypes::PublicKey publicKey{}; - - const auto error = - CryptoInstance().ExportKey(kCredentialIssuerCAPublicKeyId, publicKey.data(), publicKey.size()); - VerifyOrReturn(error == ALIRO_NO_ERROR, LOG_DBG("Credential Issuer CA Public Key is not provisioned")); - - credentialIssuerCAPublicKeyId = kCredentialIssuerCAPublicKeyId; - -#endif // CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA -} - AliroError LoadIssuerCredentials() { #if CONFIG_DOOR_LOCK_ACCESS_MANAGER_CREDENTIAL_ISSUER_MAX_STORED_KEYS > 0 @@ -169,7 +157,7 @@ AliroError LoadReaderCertificate() if (ec == 0) { // Cache the certificate - AliroError err = ReaderCertificateCache::Instance().SetCertificate({ certData.data(), certLength }); + AliroError err = ReaderCache::Instance().SetCertificate({ certData.data(), certLength }); VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot set Reader certificate.")); LOG_INF("Loaded Reader certificate: %u bytes", certLength); @@ -193,7 +181,7 @@ AliroError LoadIssuerPublicKey() LOG_ERR("Invalid Issuer public key format (expected prefix 0x04)")); // Cache the public key - AliroError err = ReaderCertificateCache::Instance().SetIssuerPublicKey(publicKey); + AliroError err = ReaderCache::Instance().SetIssuerPublicKey(publicKey); VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot set Issuer public key.")); LOG_INF("Loaded Issuer public key: %zu bytes", publicKey.size()); @@ -204,82 +192,41 @@ AliroError LoadIssuerPublicKey() #endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE -AliroError LoadReaderKeys(CryptoTypes::KeyId &privateKeyId, [[maybe_unused]] CryptoTypes::KeyId &groupResolvingKeyId) +AliroError LoadReaderKeys() { - CryptoTypes::PrivateKey privateKey{}; + CryptoTypes::KeyId privateKeyId{ kPrivateKeyId }; CryptoTypes::PublicKey publicKey{}; - privateKeyId = kPrivateKeyId; - AliroError ec = CryptoInstance().ExportPublicKey(privateKeyId, publicKey); - if (ec != ALIRO_NO_ERROR) { -#ifdef CONFIG_DOOR_LOCK_USE_TEST_KEYS - - LOG_WRN("\n### WARNING: Tests keys are used (NOT allowed for production!) ###\n"); - privateKey = mPrivateKey; - -#else /* CONFIG_DOOR_LOCK_USE_TEST_KEYS */ - - LOG_DBG("\n### Production keys are used ###\n"); - return ALIRO_ERROR_NOT_IMPLEMENTED; - -#endif /* CONFIG_DOOR_LOCK_USE_TEST_KEYS */ - - AliroError err = CryptoInstance().ImportPrivateKey(privateKey, privateKeyId, true); - VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot import reader private key")); - } - -#ifdef CONFIG_DOOR_LOCK_BLE_UWB - - groupResolvingKeyId = kGroupResolvingKeyId; - - CryptoTypes::GroupResolvingKey groupResolvingKey{}; - ec = CryptoInstance().ExportKey(kGroupResolvingKeyId, groupResolvingKey.data(), groupResolvingKey.size()); - if (ec != ALIRO_NO_ERROR) { - LOG_DBG("Group Resolving Key is not provisioned, all-zero key will be used"); - AliroError err = CryptoInstance().ProvisionSymmetricKey( - groupResolvingKey.data(), groupResolvingKey.size(), groupResolvingKeyId, true); - VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot provision group resolving key")); + auto err = DoorLock::Crypto::ExportPublicKey(privateKeyId, publicKey); + if (err != ALIRO_NO_ERROR) { + LOG_INF("Reader private key is not provisioned"); + return ALIRO_INVALID_STATE; } -#endif // CONFIG_DOOR_LOCK_BLE_UWB + ReturnErrorOnFailure(ReaderCache::Instance().SetPublicKey(publicKey)); return ALIRO_NO_ERROR; } -AliroError LoadReaderIdentifier(Identifier &identifier) +AliroError LoadReaderIdentifier() { + Identifier identifier{}; + int ec = KeyValueStorage::Instance().Get(StorageKeys::kStorageKeyNameIdentifier, identifier.data(), identifier.size()); - [[maybe_unused]] bool identifierAvailable = ec == 0; + bool identifierAvailable = ec == 0; if (ec == -ENODATA) { -#ifdef CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER - identifier = kTestIdentifier; - ec = KeyValueStorage::Instance().Save(StorageKeys::kStorageKeyNameIdentifier, identifier.data(), - identifier.size()); - VerifyOrReturnStatus(ec == 0, ALIRO_ERROR_INTERNAL, - LOG_ERR("Cannot save reader identifier, error: %d", ec)); - identifierAvailable = true; -#else // CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER - LOG_INF("No reader identifier available"); -#endif // CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER + LOG_INF("Reader identifier is not provisioned"); + return ALIRO_INVALID_STATE; } else if (ec) { LOG_ERR("Cannot get reader identifier, error code: %d", ec); return ALIRO_ERROR_INTERNAL; } -#ifdef CONFIG_DOOR_LOCK_PRINT_READER_GROUP_IDENTIFIER if (identifierAvailable) { - // First 16 bytes of the Reader Identifier constitute the Reader Group Identifier - char hexString[kReaderGroupIdentifierLength * 2 + 1]; - size_t resLen = bin2hex(identifier.data(), kReaderGroupIdentifierLength, hexString, sizeof(hexString)); - VerifyOrReturnStatus(resLen == kReaderGroupIdentifierLength * 2, ALIRO_ERROR_INTERNAL, - LOG_ERR("Cannot convert buffer to hex string")); - - LOG_INF("\nProvision the Test Harness with the following Reader Group Identifier:"); - LOG_INF("%s\n", hexString); + ReturnErrorOnFailure(ReaderCache::Instance().SetIdentifier(identifier)); } -#endif return ALIRO_NO_ERROR; } @@ -289,9 +236,6 @@ AliroError StorageInit() AliroError err = LoadAccessCredentials(); VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot load Access Credentials")); - CryptoTypes::KeyId credentialIssuerCAPublicKeyId{ 0 }; - LoadCredentialIssuerCA(credentialIssuerCAPublicKeyId); - err = LoadIssuerCredentials(); VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot load Issuer Credentials")); @@ -303,65 +247,145 @@ AliroError StorageInit() VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot load Issuer public key")); #endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE - CryptoTypes::KeyId privateKeyId{ 0 }; - CryptoTypes::KeyId groupResolvingKeyId{ 0 }; - err = LoadReaderKeys(privateKeyId, groupResolvingKeyId); - VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot load reader keys")); - - Identifier identifier{}; - err = LoadReaderIdentifier(identifier); - VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot load reader identifier")); + err = LoadReaderKeys(); + if (err != ALIRO_NO_ERROR && err != ALIRO_INVALID_STATE) { + LOG_ERR("Cannot load reader keys"); + return err; + } - err = AliroStack::Instance().Provision(privateKeyId, groupResolvingKeyId, identifier, - credentialIssuerCAPublicKeyId); - VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Cannot provision Aliro stack")); + err = LoadReaderIdentifier(); + if (err != ALIRO_NO_ERROR && err != ALIRO_INVALID_STATE) { + LOG_ERR("Cannot load reader identifier"); + return err; + } return ALIRO_NO_ERROR; } #endif // CONFIG_CHIP -#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +#ifdef CONFIG_DOOR_LOCK_BLE_UWB -KpersistentManagerImpl sKpersistentManagerImpl; +void PrintUwbInfo() +{ + using namespace Aliro::Uwb; -#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + VerifyOrReturn(UltraWideBandImpl::Instance().IsInitialized(), LOG_INF("[UWB] Not initialized yet")); -} // namespace + const char *fwVersion = UltraWideBandImpl::Instance().GetQm35FirmwareVersion(); + const auto *caps = UltraWideBandImpl::Instance().GetCccCapabilities(); -int AliroInit() + LOG_INF("[UWB] QM35 FW: %s", fwVersion ? fwVersion : "N/A"); + + if (caps) { + LOG_INF("[UWB] CCC: slots=0x%02x ch=0x%02x hop=0x%02x sync=0x%08x ran_min=%u", caps->mSlotBitmask, + caps->mChannelBitmask, caps->mHoppingConfigBitmask, caps->mSyncCodeIndexBitmask, + caps->mMinimumRanMultiplier); + } else { + LOG_INF("[UWB] CCC: N/A"); + } +} + +#endif // CONFIG_DOOR_LOCK_BLE_UWB + +constexpr uint8_t GetApplicationFeatures() { - AliroError ec{}; - LOG_INF("Starting nRF Door Lock Reference Application for the nRF Connect SDK"); + uint8_t features = 0; -#ifdef CONFIG_ACCESS_DECISION_INDICATOR - VerifyOrReturnValue(Access::Indicator::InitAccessDecisionIndicator() == ALIRO_NO_ERROR, EXIT_FAILURE, - LOG_ERR("Failed to initialize access decision indicator")); -#endif // CONFIG_ACCESS_DECISION_INDICATOR +#ifdef CONFIG_NCS_ALIRO_CREDENTIAL_ISSUER_CA_PUBLIC_KEY + features |= kFeatureCredentialIssuerCaPublicKeySupported; +#endif // CONFIG_NCS_ALIRO_CREDENTIAL_ISSUER_CA_PUBLIC_KEY - const AliroConfig config{ -#ifdef CONFIG_DISABLE_ALIRO_NFC_TP - .mEnableNfc = false, -#endif // CONFIG_DISABLE_ALIRO_NFC_TP +#ifdef CONFIG_DOOR_LOCK_READER_CERTIFICATE + features |= kFeatureReaderCertificateSupported; +#endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE -#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +#ifdef CONFIG_CHIP + features |= kFeatureMatterSupported; +#endif // CONFIG_CHIP - .mKpersistentManager = &sKpersistentManagerImpl, + return features; +} -#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +void PrintAliroFeatures(uint8_t stackFeatures, uint8_t applicationFeatures) +{ + const auto logStackFeature = [](const char *name, bool enabled) { + LOG_INF("[Aliro] %s: %u", name, enabled ? 1U : 0U); + }; + + const auto logAppFeature = [](const char *name, bool enabled) { + LOG_INF("[Doorlock] %s: %u", name, enabled ? 1U : 0U); + }; + + LOG_INF("[Aliro] Stack features mask: 0x%02x", static_cast(stackFeatures)); + logStackFeature("ExpFast", (stackFeatures & kFeatureExpeditedFastPhaseSupported) != 0); + logStackFeature("StepUp", (stackFeatures & kFeatureStepUpPhaseSupported) != 0); + logStackFeature("UWB", (stackFeatures & kFeatureBleUwbSupported) != 0); + + LOG_INF("[Doorlock] Application features mask: 0x%02x", static_cast(applicationFeatures)); + logAppFeature("CredCA", (applicationFeatures & kFeatureCredentialIssuerCaPublicKeySupported) != 0); + logAppFeature("ReaderCert", (applicationFeatures & kFeatureReaderCertificateSupported) != 0); + logAppFeature("Matter", (applicationFeatures & kFeatureMatterSupported) != 0); +} #ifdef CONFIG_DOOR_LOCK_BLE_UWB +AliroError StartAliroAdvertisingImpl(BleManager &bleManager) +{ + Identifier readerIdentifier{}; + AliroError ec = ReaderCache::Instance().GetIdentifier(readerIdentifier); + VerifyOrReturnStatus(ec == ALIRO_NO_ERROR, ec, LOG_ERR("Failed to get reader identifier")); + + BleTypes::BleAddress address{}; + ec = bleManager.GetAddress(address); + VerifyOrReturnStatus(ec == ALIRO_NO_ERROR, ec, LOG_ERR("Failed to get BLE address")); + + BleTypes::TxPowerLevel txPower{}; + ec = bleManager.GetTxPowerLevel(txPower); + VerifyOrReturnStatus(ec == ALIRO_NO_ERROR, ec, LOG_ERR("Failed to get TX power level")); - .mBle = &BleInterface::BleManagerImpl::Instance(), + BleTypes::AdvertisingServiceData advData{}; + ec = AliroStack::Instance().GenerateAdvertisingData(advData, address, txPower, readerIdentifier); + VerifyOrReturnStatus(ec == ALIRO_NO_ERROR, ec, LOG_ERR("Failed to get advertising data")); + ec = bleManager.StartAdvertising(advData); + VerifyOrReturnStatus(ec == ALIRO_NO_ERROR, ec, LOG_ERR("Failed to start BLE advertising")); + + return ALIRO_NO_ERROR; +} #endif // CONFIG_DOOR_LOCK_BLE_UWB - }; - ec = AliroStack::Instance().Init( - { .mOnError = [](AliroError error) { LOG_ERR("Aliro error: %s", error.ToString()); } }, config); +} // namespace + +#ifdef CONFIG_DOOR_LOCK_BLE_UWB + +AliroError StartAliroAdvertising() +{ + return StartAliroAdvertisingImpl(BleManager::Instance()); +} + +#endif // CONFIG_DOOR_LOCK_BLE_UWB + +int AliroInit() +{ + AliroError ec{}; + LOG_INF("Starting nRF Door Lock Reference Application for the nRF Connect SDK"); + + std::ignore = AliroWorkInit(); + +#ifdef CONFIG_BT + // Initialize BLE manager first (app-controlled BLE stack) + ec = BleManager::Instance().Init(); + VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("BLE manager initialization failed")); +#endif // CONFIG_BT + ec = AliroStack::Instance().Init(); VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Aliro stack initialization failed")); + ec = Aliro::NfcTransportRfal::Instance().Init(); + if (ec != ALIRO_NO_ERROR) { + LOG_ERR("NFC transport initialization failed"); + } + KpersistentManager *kpersistentManager{ nullptr }; #ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE @@ -371,9 +395,18 @@ int AliroInit() #endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE #ifndef CONFIG_CHIP - sLockSim.Init([]([[maybe_unused]] OperationSource source, [[maybe_unused]] ReaderStateByte state) { + LockSimInstance().Init([]([[maybe_unused]] OperationSource source, [[maybe_unused]] ReaderStateByte state) { #ifdef CONFIG_DOOR_LOCK_BLE_UWB Aliro::AliroStack::Instance().SendReaderStatusChangedMessage(source, state); + auto &bleManager = BleManager::Instance(); + AliroError ec{}; + if (state == ReaderStateByte::Unsecured) { + ec = bleManager.StopAdvertising(); + VerifyOrReturn(ec == ALIRO_NO_ERROR, LOG_ERR("Failed to stop Aliro advertising")); + } else if (state == ReaderStateByte::EnteringSecured) { + ec = StartAliroAdvertisingImpl(bleManager); + VerifyOrReturn(ec == ALIRO_NO_ERROR, LOG_ERR("Failed to start Aliro advertising")); + } #endif // CONFIG_DOOR_LOCK_BLE_UWB }); @@ -382,10 +415,7 @@ int AliroInit() [](OperationSource source) { const auto isNfcSession = source == OperationSource::ThisUserDeviceInNfc; LOG_DBG("Door unlocked via %s session", isNfcSession ? "NFC" : "BLE/UWB"); -#ifdef CONFIG_ACCESS_DECISION_INDICATOR - Access::Indicator::SignalAccessGranted(); -#endif // CONFIG_ACCESS_DECISION_INDICATOR - if (!sLockSim.Unlock(source)) { + if (!LockSimInstance().Unlock(source)) { #ifdef CONFIG_DOOR_LOCK_BLE_UWB // The lock is already unlocked, so we can send the Unsecured state Aliro::AliroStack::Instance().SendReaderStatusChangedMessage( @@ -397,7 +427,7 @@ int AliroInit() [](OperationSource source) { const auto isNfcSession = source == OperationSource::ThisUserDeviceInNfc; LOG_DBG("Door locked via %s session", isNfcSession ? "NFC" : "BLE/UWB"); - sLockSim.Lock(source); + LockSimInstance().Lock(source); }, .mAccessIndicatorClb = [](bool isAccessGranted, bool isNfcSession) { @@ -408,19 +438,30 @@ int AliroInit() ec = StorageInit(); VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Storage initialization failed")); - ec = AliroStack::Instance().Start(); - VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Aliro stack start failed")); + ec = DoorLock::AliroStateControl::UpdateAliroState(); + VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, + LOG_ERR("Failed to update Aliro state: %d", ec.ToInt())); + #endif // CONFIG_CHIP -#ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS + auto initRc = DoorLock::ExternalNvs::Init(FIXED_PARTITION_ID(external_nvs)); + VerifyOrReturnValue(initRc == 0, EXIT_FAILURE, LOG_ERR("External NVS init failed: %d", initRc)); +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS + +#if defined(CONFIG_DOOR_LOCK_STEP_UP_PHASE) && CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 ec = LoadAccessDocuments(); VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Cannot load Access Documents")); -#endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE +#endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE && CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 #ifdef CONFIG_DOOR_LOCK_CLI InitShellCommands(kpersistentManager); #endif // CONFIG_DOOR_LOCK_CLI + PrintAliroFeatures(AliroStack::Instance().GetFeatures(), GetApplicationFeatures()); +#ifdef CONFIG_DOOR_LOCK_BLE_UWB + PrintUwbInfo(); +#endif // CONFIG_DOOR_LOCK_BLE_UWB LOG_INF("Aliro stack initialized"); return EXIT_SUCCESS; @@ -428,25 +469,65 @@ int AliroInit() int AliroStart() { - AliroError ec = AliroStack::Instance().Start(); + AliroError ec = NfcTransportRfal::Instance().Start(); + if (ec != ALIRO_NO_ERROR) { + LOG_ERR("NFC transport start failed"); + return EXIT_FAILURE; + } + +#ifdef CONFIG_DOOR_LOCK_BLE_UWB + // Ensure Group Resolving Key exists before starting BLE advertising + ec = DoorLock::Crypto::IsKeyAvailable(kGroupResolvingKeyId); + if (ec != ALIRO_NO_ERROR) { + LOG_DBG("Group Resolving Key is not provisioned, all-zero key will be used"); + CryptoTypes::GroupResolvingKey groupResolvingKey{}; + CryptoTypes::KeyId groupResolvingKeyId = kGroupResolvingKeyId; + ec = DoorLock::Crypto::ImportGroupResolvingKey(groupResolvingKey, true, groupResolvingKeyId); + VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Cannot import group resolving key")); + } - VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Aliro stack start failed")); + auto &bleManager = BleManager::Instance(); + ec = StartAliroAdvertisingImpl(bleManager); + VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Failed to start Aliro advertising")); +#endif // CONFIG_DOOR_LOCK_BLE_UWB + sAliroRunning = true; return EXIT_SUCCESS; } int AliroStop() { - AliroError ec = AliroStack::Instance().Stop(); + int rc = EXIT_SUCCESS; - VerifyOrReturnValue(ec == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Aliro stack stop failed")); + AliroError ec = NfcTransportRfal::Instance().Stop(); + if (ec != ALIRO_NO_ERROR) { + LOG_ERR("NFC transport stop failed"); + } - return EXIT_SUCCESS; +#ifdef CONFIG_DOOR_LOCK_BLE_UWB + ec = BleManager::Instance().StopAdvertising(); + if (ec != ALIRO_NO_ERROR) { + LOG_ERR("Failed to stop BLE advertising: %d", ec.ToInt()); + } + ec = BleManager::Instance().DisconnectAll(); + if (ec != ALIRO_NO_ERROR) { + LOG_ERR("Failed to disconnect all BLE connections"); + rc = EXIT_FAILURE; + } +#endif // CONFIG_DOOR_LOCK_BLE_UWB + + sAliroRunning = false; + return rc; +} + +bool IsAliroRunning() +{ + return sAliroRunning; } #ifdef CONFIG_CHIP -void ClearStorageAliro() +void ClearStorageAliro(bool reinitializeStorage) { int ec = KeyValueStorage::Instance().Clear(StorageKeys::kStorageKeyNameIdentifier); if (!ec) { @@ -455,7 +536,7 @@ void ClearStorageAliro() } CryptoTypes::KeyId keyId{ kPrivateKeyId }; - AliroError err = CryptoInstance().DestroyKey(keyId); + AliroError err = DoorLock::Crypto::DestroyKey(keyId); if (err != ALIRO_NO_ERROR) { LOG_ERR("Failed to destroy Reader Private Key: %d", err.ToInt()); } @@ -463,7 +544,7 @@ void ClearStorageAliro() #ifdef CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA keyId = kCredentialIssuerCAPublicKeyId; - err = CryptoInstance().DestroyKey(keyId); + err = DoorLock::Crypto::DestroyKey(keyId); if (err != ALIRO_NO_ERROR) { LOG_ERR("Failed to destroy Credential Issuer CA Public Key: %d", err.ToInt()); } @@ -473,7 +554,7 @@ void ClearStorageAliro() #ifdef CONFIG_DOOR_LOCK_BLE_UWB keyId = kGroupResolvingKeyId; - err = CryptoInstance().DestroyKey(keyId); + err = DoorLock::Crypto::DestroyKey(keyId); if (err != ALIRO_NO_ERROR) { LOG_ERR("Failed to destroy Group Resolving Key: %d", err.ToInt()); } @@ -485,6 +566,15 @@ void ClearStorageAliro() sKpersistentManagerImpl.RemoveAllKpersistent(); #endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS + DoorLock::ExternalNvs::Clear(); + if (reinitializeStorage) { + DoorLock::ExternalNvs::Init(FIXED_PARTITION_ID(external_nvs)); + } +#else // CONFIG_DOOR_LOCK_EXTERNAL_NVS + ARG_UNUSED(reinitializeStorage); +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS } #endif // CONFIG_CHIP diff --git a/app/src/aliro/init.h b/app/src/aliro/init.h index 45d334ce..cc64c36e 100644 --- a/app/src/aliro/init.h +++ b/app/src/aliro/init.h @@ -5,6 +5,8 @@ */ #pragma once +#include + /** * @brief Initializes the Aliro stack. * @@ -26,12 +28,31 @@ int AliroStart(); */ int AliroStop(); +#ifdef CONFIG_DOOR_LOCK_BLE_UWB + +/** + * @brief Starts or refreshes Aliro BLE advertising with current reader identity data. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError StartAliroAdvertising(); + +#endif // CONFIG_DOOR_LOCK_BLE_UWB + +/** + * @brief Check if Aliro stack is currently running. + * + * @return true if Aliro stack is running, false otherwise. + */ +bool IsAliroRunning(); + #ifdef CONFIG_CHIP /** * @brief Clears Aliro storage. * + * @param reinitializeStorage Whether to reinitialize the storage after clearing. */ -void ClearStorageAliro(); +void ClearStorageAliro(bool reinitializeStorage); #endif // CONFIG_CHIP diff --git a/app/src/aliro/interface.cpp b/app/src/aliro/interface.cpp deleted file mode 100644 index 455bc5c4..00000000 --- a/app/src/aliro/interface.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#include "aliro/interface.h" - -#include "reader_certificate_cache.h" - -namespace Aliro::Interface { - -namespace ReaderCertificate { - -bool IsProvisioned() -{ - return ReaderCertificateCache::Instance().IsCertificateSet() && - ReaderCertificateCache::Instance().IsIssuerPublicKeySet(); -} - -AliroError GetIssuerPublicKey(CryptoTypes::PublicKey &publicKey) -{ - return ReaderCertificateCache::Instance().GetIssuerPublicKey(publicKey); -} - -AliroError GetCertificate(ConstData &certificate) -{ - return ReaderCertificateCache::Instance().GetCertificate(certificate); -} - -} // namespace ReaderCertificate - -} // namespace Aliro::Interface diff --git a/app/src/aliro/interface_impl/CMakeLists.txt b/app/src/aliro/interface_impl/CMakeLists.txt new file mode 100644 index 00000000..dcb5a4be --- /dev/null +++ b/app/src/aliro/interface_impl/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +target_sources(app PRIVATE + access_document.cpp + access.cpp + ci_cert.cpp + crypto.cpp + os_mutex.cpp + os_timer.cpp + os.cpp + reader.cpp + session.cpp +) + +target_sources_ifdef(CONFIG_NCS_ALIRO_BLE_UWB app PRIVATE + ble.cpp + uwb.cpp +) + +if(CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE GREATER 0) + target_sources(app PRIVATE log.cpp) +endif() diff --git a/app/src/aliro/interface_impl/access.cpp b/app/src/aliro/interface_impl/access.cpp new file mode 100644 index 00000000..c81bb17d --- /dev/null +++ b/app/src/aliro/interface_impl/access.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/interface.h" + +#include "aliro/access_manager/access_manager.h" + +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +#include "aliro/kpersistent_manager/kpersistent_manager_impl.h" +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +extern Aliro::KpersistentManagerImpl sKpersistentManagerImpl; +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + +namespace Aliro::Interface::Access { + +std::optional +GetAccessDocumentRequestParameters(const CryptoTypes::PublicKey &publicKey, + const std::optional &credentialSignedTimestamp) +{ + return AccessManagerInstance().ShouldRequestAccessDocument(publicKey, credentialSignedTimestamp); +} + +AliroError ProcessAccessRequest(ConnectionHandle handle, const CryptoTypes::PublicKey &userPublicKey, + CryptoTypes::KeyId kpersistentKeyId, + const std::optional &accessDocument) +{ + return AccessManagerInstance().VerifyAccessCredential(userPublicKey, handle, kpersistentKeyId, accessDocument); +} + +AliroError ProcessAccessRequest(ConnectionHandle handle, CryptoTypes::KeyId kpersistentKeyId) +{ + return AccessManagerInstance().VerifyKPersistentKey(kpersistentKeyId, handle); +} + +AliroError GetCredentialIssuerPublicKey(const CryptoTypes::KeyIdentifier &keyIdentifier, + CryptoTypes::PublicKey &publicKey) +{ + return AccessManagerInstance().GetCredentialIssuerPublicKey(keyIdentifier, publicKey); +} + +AliroError GetKpersistentCount(size_t &count) +{ +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + return sKpersistentManagerImpl.GetKpersistentCount(count); +#else + ARG_UNUSED(count); + return ALIRO_ERROR_NOT_IMPLEMENTED; +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +} + +AliroError GetKpersistentKeyIds(CryptoTypes::KeyId *keyIds, size_t &count) +{ +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + return sKpersistentManagerImpl.GetKpersistentKeyIds(keyIds, count); +#else + ARG_UNUSED(keyIds); + ARG_UNUSED(count); + return ALIRO_ERROR_NOT_IMPLEMENTED; +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +} + +AliroError GetAccessCredentialPublicKey(CryptoTypes::KeyId kpersistentKeyId, CryptoTypes::PublicKey &publicKey) +{ +#ifdef CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE + return sKpersistentManagerImpl.GetAccessCredentialPublicKey(kpersistentKeyId, publicKey); +#else + ARG_UNUSED(kpersistentKeyId); + ARG_UNUSED(publicKey); + return ALIRO_ERROR_NOT_IMPLEMENTED; +#endif // CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE +} + +} // namespace Aliro::Interface::Access diff --git a/app/src/aliro/interface_impl/access_document.cpp b/app/src/aliro/interface_impl/access_document.cpp new file mode 100644 index 00000000..baade056 --- /dev/null +++ b/app/src/aliro/interface_impl/access_document.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/interface.h" + +namespace Aliro::Interface::AccessDocument { + +std::optional VerifyValidityPeriod(const Time &, const Time &) +{ + return std::nullopt; +} + +} // namespace Aliro::Interface::AccessDocument diff --git a/app/src/aliro/interface_impl/ble.cpp b/app/src/aliro/interface_impl/ble.cpp new file mode 100644 index 00000000..f5b91bc9 --- /dev/null +++ b/app/src/aliro/interface_impl/ble.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + +#include "ble_manager.h" + +/** + * @file ble_interface_impl.cpp + * @brief BLE interface helpers for protocol metadata. + */ + +namespace Aliro::Interface::Ble { + +/** + * @brief Get the maximum number of concurrent BLE sessions. + */ +size_t GetMaxSessions() +{ + return BleManager::Instance().GetMaxSessions(); +} + +/** + * @brief Get the protocol version for a connection. + */ +ProtocolVersion GetProtocolVersion(ConnectionHandle handle) +{ + return BleManager::Instance().GetProtocolVersion(handle); +} + +} // namespace Aliro::Interface::Ble + +#endif // CONFIG_NCS_ALIRO_BLE_UWB diff --git a/app/src/aliro/interface_impl/ci_cert.cpp b/app/src/aliro/interface_impl/ci_cert.cpp new file mode 100644 index 00000000..28a725e8 --- /dev/null +++ b/app/src/aliro/interface_impl/ci_cert.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/errors.h" +#include "aliro/interface.h" + +#ifdef CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA + +#include "aliro/crypto_key_ids.h" +#include "aliro/interface.h" +#include "aliro/utils.h" +#include "crypto/utils.h" + +#include + +extern "C" { +#include "mbedtls/psa_util.h" +} + +#include "mbedtls/oid.h" +#include "mbedtls/x509_crt.h" + +#include + +LOG_MODULE_REGISTER(interface_ci_cert, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); + +#endif // CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA + +namespace Aliro::Interface::CredentialIssuerCertificate { + +#ifdef CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA + +namespace { + +AliroError VerifyKeyType(const mbedtls_pk_context *pk) +{ + constexpr size_t kEccP256KeyBits{ CryptoTypes::kEccP256KeyPrivateKeyLength * BITS_PER_BYTE }; + psa_key_attributes_t attributes{}; + mbedtls_pk_get_psa_attributes(pk, PSA_KEY_USAGE_VERIFY_HASH, &attributes); + + psa_key_type_t type = psa_get_key_type(&attributes); + VerifyOrReturnStatus(type == PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1), ALIRO_INVALID_DATA_CONTENT, + LOG_ERR("Invalid public key type")); + VerifyOrReturnStatus(psa_get_key_bits(&attributes) == kEccP256KeyBits, ALIRO_INVALID_DATA_CONTENT, + LOG_ERR("Invalid public key bit length")); + + return ALIRO_NO_ERROR; +} + +AliroError VerifyCertificateSignature(const mbedtls_x509_crt &crt) +{ + constexpr size_t kCoordinateBits{ CryptoTypes::kEccP256KeySingleCoordinateLength * BITS_PER_BYTE }; + CryptoTypes::Signature signatureArray{}; + size_t rawLength{}; + + auto result = + mbedtls_ecdsa_der_to_raw(kCoordinateBits, crt.MBEDTLS_PRIVATE(sig).p, crt.MBEDTLS_PRIVATE(sig).len, + signatureArray.data(), signatureArray.size(), &rawLength); + VerifyOrReturnStatus(result == 0, ALIRO_ERROR_INTERNAL, LOG_ERR("Failed to convert signature from DER to raw")); + VerifyOrReturnStatus(rawLength == signatureArray.size(), ALIRO_INVALID_DATA_FORMAT, + LOG_ERR("Invalid signature length")); + + return DoorLock::Crypto::VerifySignature(kCredentialIssuerCAPublicKeyId, crt.tbs.p, crt.tbs.len, + signatureArray); +} + +AliroError VerifyCertificate(const mbedtls_x509_crt &crt) +{ + VerifyOrReturnStatus(crt.version == 3, ALIRO_INVALID_DATA_FORMAT, LOG_ERR("Invalid certificate version")); + + auto result = mbedtls_x509_crt_has_ext_type(&crt, MBEDTLS_X509_EXT_KEY_USAGE); + VerifyOrReturnStatus(result != 0, ALIRO_INVALID_DATA_CONTENT, LOG_ERR("Key usage extension is not present")); + result = mbedtls_x509_crt_check_key_usage(&crt, MBEDTLS_X509_KU_DIGITAL_SIGNATURE); + VerifyOrReturnStatus(result == 0, ALIRO_INVALID_DATA_CONTENT, LOG_ERR("Digital Signature bit not set")); + + auto error = VerifyKeyType(&crt.pk); + VerifyOrReturnStatus(error == ALIRO_NO_ERROR, error, LOG_ERR("Failed to verify key type")); + + VerifyOrReturnStatus(crt.MBEDTLS_PRIVATE(sig_md) == MBEDTLS_MD_SHA256, ALIRO_INVALID_DATA_CONTENT, + LOG_ERR("Invalid signature algorithm")); + VerifyOrReturnStatus(crt.MBEDTLS_PRIVATE(sig_pk) == MBEDTLS_PK_ECDSA, ALIRO_INVALID_DATA_CONTENT, + LOG_ERR("Invalid signature algorithm")); + + error = VerifyCertificateSignature(crt); + VerifyOrReturnStatus(error == ALIRO_NO_ERROR, error, LOG_ERR("Failed to verify certificate signature")); + + return ALIRO_NO_ERROR; +} + +} // namespace + +AliroError Validate(const ConstData &certificate, CryptoTypes::PublicKey &publicKey, + std::optional ×tamps) +{ +#ifndef MBEDTLS_PK_USE_PSA_EC_DATA + + mbedtls_ecp_keypair *keypair{ nullptr }; + size_t pubkey_size{ 0 }; + +#endif // MBEDTLS_PK_USE_PSA_EC_DATA + + VerifyOrReturnStatus(certificate.mData != nullptr, ALIRO_INVALID_ARGUMENT, LOG_ERR("Certificate data is null")); + VerifyOrReturnStatus(certificate.mLength != 0, ALIRO_INVALID_ARGUMENT, LOG_ERR("Certificate length is zero")); + + mbedtls_x509_crt crt; + mbedtls_x509_crt_init(&crt); + + auto result = mbedtls_x509_crt_parse_der_nocopy(&crt, certificate.mData, certificate.mLength); + AliroError error = (result == 0) ? ALIRO_NO_ERROR : ALIRO_INVALID_DATA_FORMAT; + VerifyOrExit(result == 0, LOG_ERR("Failed to parse certificate")); + + error = VerifyCertificate(crt); + VerifyOrExit(error == ALIRO_NO_ERROR, LOG_ERR("Failed to verify certificate")); + +#ifndef MBEDTLS_PK_USE_PSA_EC_DATA + + keypair = mbedtls_pk_ec(crt.pk); + result = mbedtls_ecp_point_write_binary(&keypair->MBEDTLS_PRIVATE(grp), &keypair->MBEDTLS_PRIVATE(Q), + MBEDTLS_ECP_PF_UNCOMPRESSED, &pubkey_size, publicKey.data(), + publicKey.size()); + VerifyOrExit(result == 0, error = ALIRO_INVALID_ARGUMENT; LOG_ERR("Failed to extract public key")); + VerifyOrExit(pubkey_size == CryptoTypes::kEccP256PublicKeyLength, error = ALIRO_INVALID_ARGUMENT; + LOG_ERR("Invalid public key length")); + +#else // MBEDTLS_PK_USE_PSA_EC_DATA + + VerifyOrExit(crt.pk.MBEDTLS_PRIVATE(pub_raw_len) == CryptoTypes::kEccP256PublicKeyLength, + error = ALIRO_INVALID_DATA_FORMAT; + LOG_ERR("Invalid public key length")); + + std::copy_n(crt.pk.MBEDTLS_PRIVATE(pub_raw), crt.pk.MBEDTLS_PRIVATE(pub_raw_len), publicKey.data()); + +#endif // MBEDTLS_PK_USE_PSA_EC_DATA + + timestamps.emplace( + CertificateTimestamps{ .mValidFrom = Time(crt.valid_from.year, crt.valid_from.mon, crt.valid_from.day, + crt.valid_from.hour, crt.valid_from.min, crt.valid_from.sec), + .mValidUntil = Time(crt.valid_to.year, crt.valid_to.mon, crt.valid_to.day, + crt.valid_to.hour, crt.valid_to.min, crt.valid_to.sec) }); + +exit: + mbedtls_x509_crt_free(&crt); + return error; +} + +#else + +AliroError Validate(const ConstData &, CryptoTypes::PublicKey &, std::optional &) +{ + return ALIRO_ERROR_NOT_IMPLEMENTED; +} + +#endif // CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA + +} // namespace Aliro::Interface::CredentialIssuerCertificate diff --git a/app/src/aliro/interface_impl/crypto.cpp b/app/src/aliro/interface_impl/crypto.cpp new file mode 100644 index 00000000..7a4bfd03 --- /dev/null +++ b/app/src/aliro/interface_impl/crypto.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/errors.h" +#include "aliro/interface.h" + +#include "aliro/crypto_key_ids.h" +#include "aliro/utils.h" +#include "crypto/utils.h" + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(crypto_psa, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); + +namespace Aliro::Interface::Crypto { + +namespace { + +psa_key_attributes_t GetSharedKeyAttributes() +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_type(&attributes, PSA_KEY_TYPE_DERIVE); + psa_set_key_algorithm(&attributes, PSA_ALG_HKDF(PSA_ALG_SHA_256)); + psa_set_key_bits(&attributes, PSA_BYTES_TO_BITS(CryptoTypes::kEccP256KeyPrivateKeyLength)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DERIVE | PSA_KEY_USAGE_COPY); + + return attributes; +} + +psa_key_attributes_t GetSymmetricKeyAttributes() +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); + psa_set_key_algorithm(&attributes, PSA_ALG_GCM); + psa_set_key_bits(&attributes, PSA_BYTES_TO_BITS(CryptoTypes::kSymmetricKeyLength)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + + return attributes; +} + +psa_key_attributes_t GetEphemeralKeyAttributes() +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_algorithm(&attributes, PSA_ALG_ECDH); + psa_set_key_bits(&attributes, PSA_BYTES_TO_BITS(CryptoTypes::kEccP256KeyPrivateKeyLength)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DERIVE); + + return attributes; +} + +psa_key_attributes_t GetRawKeyAttributes(size_t outputKeyLength) +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_type(&attributes, PSA_KEY_TYPE_DERIVE); + psa_set_key_algorithm(&attributes, PSA_ALG_HKDF(PSA_ALG_SHA_256)); + psa_set_key_bits(&attributes, PSA_BYTES_TO_BITS(outputKeyLength)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_EXPORT); + + return attributes; +} + +AliroError DeriveKey(CryptoTypes::KeyId inputKeyId, const uint8_t *info, size_t infoLength, const uint8_t *salt, + size_t saltLength, const psa_key_attributes_t &attributes, CryptoTypes::KeyId &outputKeyId) +{ + psa_key_derivation_operation_t operation = PSA_KEY_DERIVATION_OPERATION_INIT; + + // Set derivation algorithm. + psa_status_t status = psa_key_derivation_setup(&operation, PSA_ALG_HKDF(PSA_ALG_SHA_256)); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("Cannot setup key derivation [Error: %d]", status)); + + // Set salt for the operation. + status = psa_key_derivation_input_bytes(&operation, PSA_KEY_DERIVATION_INPUT_SALT, salt, saltLength); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("Cannot import salt [Error: %d]", status)); + + // Set a key for the operation. + status = psa_key_derivation_input_key(&operation, PSA_KEY_DERIVATION_INPUT_SECRET, inputKeyId); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("Cannot set input key id [Error: %d]", status)); + + // Set an additional info for the operation. + status = psa_key_derivation_input_bytes(&operation, PSA_KEY_DERIVATION_INPUT_INFO, info, infoLength); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("Cannot import info [Error: %d]", status)); + + status = psa_key_derivation_output_key(&attributes, &operation, &outputKeyId); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("Cannot output derived key [Error: %d]", status)); + +exit: + /* From PSA API spec: Key derivation does not finish in the same way as other multi-part + * operations. Call psa_key_derivation_abort() to release the key derivation operation + * memory when the object is no longer required. */ + psa_status_t abortStatus = psa_key_derivation_abort(&operation); + + VerifyOrReturnStatus(abortStatus == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Key derivation abort failed [Error: %d]", status)); + + return (status == PSA_SUCCESS) ? ALIRO_NO_ERROR : ALIRO_ERROR_INTERNAL; +} + +} // namespace + +AliroError GenerateRandom(uint8_t *buffer, size_t bufferLength) +{ + VerifyOrReturnStatus(buffer && bufferLength > 0, ALIRO_INVALID_ARGUMENT); + + return psa_generate_random(buffer, bufferLength) == PSA_SUCCESS ? ALIRO_NO_ERROR : ALIRO_ERROR_INTERNAL; +} + +AliroError GenerateEphemeralKeyPair(CryptoTypes::KeyId &keyId, CryptoTypes::PublicKey &ephemeralPubKey) +{ + const auto attributes = GetEphemeralKeyAttributes(); + + auto status = psa_generate_key(&attributes, &keyId); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Cannot generate ephemeral keys [error: %d]", status)); + + return DoorLock::Crypto::ExportPublicKey(keyId, ephemeralPubKey); +} + +AliroError ImportSharedKey(const uint8_t *key, size_t keyLength, CryptoTypes::KeyId &keyId) +{ + VerifyOrReturnStatus(key && keyLength > 0, ALIRO_INVALID_ARGUMENT); + + const auto attributes = GetSharedKeyAttributes(); + + psa_status_t status = psa_import_key(&attributes, key, keyLength, &keyId); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Could not import key [Error: %d]", status)); + + return ALIRO_NO_ERROR; +} + +AliroError ImportSymmetricKey(const uint8_t *key, size_t keyLength, CryptoTypes::KeyId &keyId) +{ + VerifyOrReturnStatus(key && keyLength > 0, ALIRO_INVALID_ARGUMENT); + + const auto attributes = GetSymmetricKeyAttributes(); + + psa_status_t status = psa_import_key(&attributes, key, keyLength, &keyId); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Could not import key [Error: %d]", status)); + + return ALIRO_NO_ERROR; +} + +AliroError DestroyKey(CryptoTypes::KeyId &keyId) +{ + return DoorLock::Crypto::DestroyKey(keyId); +} + +AliroError GenerateSignature(const uint8_t *msg, const size_t msgLength, CryptoTypes::Signature &signature) +{ + VerifyOrReturnStatus(msg && msgLength > 0, ALIRO_INVALID_ARGUMENT); + + psa_status_t status = PSA_SUCCESS; + size_t outputLen{}; + + status = psa_sign_message(kPrivateKeyId, PSA_ALG_ECDSA(PSA_ALG_SHA_256), msg, msgLength, signature.data(), + signature.size(), &outputLen); + + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Cannot sign message [Error: %d]", status)); + VerifyOrReturnStatus(outputLen == CryptoTypes::kEccP256SignatureLength, ALIRO_ERROR_INTERNAL, + LOG_WRN("Invalid signature length")); + + return ALIRO_NO_ERROR; +} + +AliroError VerifySignature(const CryptoTypes::PublicKey &publicKey, const uint8_t *msg, const size_t msgLength, + const CryptoTypes::Signature &signature) +{ + CryptoTypes::KeyId pubKeyId{ 0 }; + ReturnErrorOnFailure(DoorLock::Crypto::ImportPublicKey(publicKey, false, pubKeyId)); + + const auto status = DoorLock::Crypto::VerifySignature(pubKeyId, msg, msgLength, signature); + + DoorLock::Crypto::DestroyKey(pubKeyId); + + return status; +} + +AliroError RawKeyAgreement(CryptoTypes::KeyId keyId, const CryptoTypes::PublicKey &peerPublicKey, + CryptoTypes::SharedSecret &sharedSecret) +{ + size_t outputLength{}; + + psa_status_t status = psa_raw_key_agreement(PSA_ALG_ECDH, // ECKA-DH with P-256 + keyId, peerPublicKey.data(), peerPublicKey.size(), + sharedSecret.data(), sharedSecret.size(), &outputLength); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Cannot perform raw key agreement [Error: %d]", status)); + VerifyOrReturnStatus(outputLength == sharedSecret.size(), ALIRO_ERROR_INTERNAL, + LOG_WRN("Invalid raw key agreement output length")); + + return ALIRO_NO_ERROR; +} + +AliroError DeriveSharedKey(CryptoTypes::KeyId keyId, const uint8_t *info, size_t infoLength, const uint8_t *salt, + size_t saltLength, CryptoTypes::KeyId &outputKeyId) +{ + const auto attributes = GetSharedKeyAttributes(); + + return DeriveKey(keyId, info, infoLength, salt, saltLength, attributes, outputKeyId); +} + +AliroError DeriveSymmetricKey(CryptoTypes::KeyId keyId, const uint8_t *info, size_t infoLength, const uint8_t *salt, + size_t saltLength, CryptoTypes::KeyId &outputKeyId) +{ + const auto attributes = GetSymmetricKeyAttributes(); + + return DeriveKey(keyId, info, infoLength, salt, saltLength, attributes, outputKeyId); +} + +AliroError DeriveRawKey(CryptoTypes::KeyId keyId, const uint8_t *info, size_t infoLength, const uint8_t *salt, + size_t saltLength, uint8_t *outputKey, size_t outputKeyLength) +{ + VerifyOrReturnStatus(outputKey && outputKeyLength > 0, ALIRO_INVALID_ARGUMENT, LOG_WRN("Invalid output key")); + + const psa_key_attributes_t attributes = GetRawKeyAttributes(outputKeyLength); + + CryptoTypes::KeyId derivedKeyId{}; + AliroError status = DeriveKey(keyId, info, infoLength, salt, saltLength, attributes, derivedKeyId); + VerifyOrExit(status == ALIRO_NO_ERROR); + + status = DoorLock::Crypto::ExportKey(derivedKeyId, outputKey, outputKeyLength); + +exit: + DoorLock::Crypto::DestroyKey(derivedKeyId); + + return status; +} + +AliroError AeadEncrypt(CryptoTypes::KeyId keyId, const uint8_t *plainTxt, size_t plainTxtLength, + const uint8_t *additionalData, size_t additionalDataLength, const CryptoTypes::Nonce &nonce, + uint8_t *cipherText, CryptoTypes::AuthenticationTag &authTag) +{ + VerifyOrReturnStatus(nonce.size() == CryptoTypes::kNonceLength, ALIRO_INVALID_ARGUMENT); + VerifyOrReturnStatus(((plainTxt != nullptr) == (plainTxtLength != 0)), ALIRO_ERROR_INTERNAL); + VerifyOrReturnStatus(additionalData || additionalDataLength == 0, ALIRO_ERROR_INTERNAL); + VerifyOrReturnStatus(cipherText, ALIRO_ERROR_INTERNAL); + + const psa_algorithm_t algorithm = PSA_ALG_GCM; + +#ifndef CONFIG_DOOR_LOCK_CRYPTO_PSA_AEAD_SINGLE_PART + psa_aead_operation_t operation = PSA_AEAD_OPERATION_INIT; + size_t outLength{}; + size_t authTagLengthOutput{}; + + psa_status_t status = psa_aead_encrypt_setup(&operation, keyId, algorithm); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("Cannot setup encryption [Error: %d]", status)); + + status = psa_aead_set_lengths(&operation, additionalDataLength, plainTxtLength); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("Cannot set lengths [Error: %d]", status)); + + status = psa_aead_set_nonce(&operation, nonce.data(), nonce.size()); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("Cannot set nonce [Error: %d]", status)); + + if (additionalDataLength != 0) { + status = psa_aead_update_ad(&operation, additionalData, additionalDataLength); + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("AEAD additional update failed [Error: %d]", status)); + } + + if (plainTxt) { + // For now assume the plainTxt is encrypted all at once + status = psa_aead_update(&operation, plainTxt, plainTxtLength, cipherText, + PSA_AEAD_UPDATE_OUTPUT_SIZE(PSA_KEY_TYPE_AES, algorithm, plainTxtLength), + &outLength); + + VerifyOrExit(status == PSA_SUCCESS, LOG_WRN("AEAD update failed [Error: %d]", status)); + + cipherText += outLength; + } + + status = psa_aead_finish(&operation, cipherText, PSA_AEAD_FINISH_OUTPUT_SIZE(PSA_KEY_TYPE_AES, algorithm), + &outLength, authTag.data(), authTag.size(), &authTagLengthOutput); + + VerifyOrExit(status == PSA_SUCCESS && authTag.size() == authTagLengthOutput, + LOG_WRN("Unexpected output authentication tag size or AEAD finish " + "failed [Error: %d]", + status)); + + return ALIRO_NO_ERROR; + +exit: + status = psa_aead_abort(&operation); + if (status != PSA_SUCCESS) { + LOG_WRN("Cannot abort AEAD operation [Error: %d]", status); + } + return ALIRO_ERROR_INTERNAL; + +#else // CONFIG_DOOR_LOCK_CRYPTO_PSA_AEAD_SINGLE_PART + + constexpr size_t kPlainTextSize{ CONFIG_DOOR_LOCK_CRYPTO_PSA_AEAD_SINGLE_PART_BUFFER_SIZE }; + constexpr size_t kBufferSize{ kPlainTextSize + CryptoTypes::kAuthenticationTagLength }; + const size_t expOutLen{ plainTxtLength + CryptoTypes::kAuthenticationTagLength }; + + VerifyOrReturnValue(expOutLen <= kBufferSize, ALIRO_NO_MEMORY); + + std::array buffer{}; + size_t outLen{}; + + psa_status_t status = + psa_aead_encrypt(keyId, algorithm, nonce.data(), nonce.size(), additionalData, additionalDataLength, + plainTxt, plainTxtLength, buffer.data(), buffer.size(), &outLen); + VerifyOrReturnStatus(status == PSA_SUCCESS && outLen == expOutLen, ALIRO_ERROR_INTERNAL, + LOG_WRN("AEAD encryption failed [Error: %d]", status)); + + if (plainTxtLength) { + std::copy_n(buffer.data(), plainTxtLength, cipherText); + } + + std::copy_n(buffer.data() + plainTxtLength, CryptoTypes::kAuthenticationTagLength, authTag.data()); + + return ALIRO_NO_ERROR; + +#endif // CONFIG_DOOR_LOCK_CRYPTO_PSA_AEAD_SINGLE_PART +} + +AliroError AeadDecrypt(CryptoTypes::KeyId keyId, const uint8_t *cipherTextWithTag, size_t cipherTextWithTagLength, + const uint8_t *additionalData, size_t additionalDataLength, const CryptoTypes::Nonce &nonce, + uint8_t *plainText, size_t &plainTextLength) +{ + size_t outLength{}; + + VerifyOrReturnStatus(cipherTextWithTagLength != 0 && cipherTextWithTag, ALIRO_INVALID_ARGUMENT, + LOG_WRN("Cipher text with tag is not valid")); + VerifyOrReturnStatus(plainTextLength == 0 || plainText, ALIRO_INVALID_ARGUMENT, + LOG_WRN("Plain text buffer is not valid")); + + psa_status_t status = + psa_aead_decrypt(keyId, PSA_ALG_GCM, nonce.data(), nonce.size(), additionalData, additionalDataLength, + cipherTextWithTag, cipherTextWithTagLength, plainText, plainTextLength, &outLength); + + // The ciphertext is not authentic, authentication tag is not valid. + if (status == PSA_ERROR_INVALID_SIGNATURE) { + return ALIRO_INVALID_AUTHENTICATION_TAG; + } + + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("AEAD decryption failed [Error: %d]", status)); + + plainTextLength = outLength; + return ALIRO_NO_ERROR; +} + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + +AliroError Encrypt(const uint8_t *plainText, size_t plainTextLength, uint8_t *cipherText) +{ + constexpr static psa_algorithm_t algorithm = PSA_ALG_ECB_NO_PADDING; + constexpr static size_t blockSize = PSA_BLOCK_CIPHER_BLOCK_LENGTH(PSA_KEY_TYPE_AES); + + VerifyOrReturnStatus((cipherText && plainText && plainTextLength == blockSize), ALIRO_INVALID_ARGUMENT); + + size_t outLength{}; + + psa_status_t status = psa_cipher_encrypt(kGroupResolvingKeyId, algorithm, plainText, blockSize, cipherText, + blockSize, &outLength); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Cannot encrypt payload [Error: %d]", status)); + VerifyOrReturnStatus(outLength == blockSize, ALIRO_ERROR_INTERNAL, LOG_WRN("Invalid output length")); + + return ALIRO_NO_ERROR; +} + +#endif // CONFIG_NCS_ALIRO_BLE_UWB + +AliroError Sha256(const uint8_t *data, size_t dataLength, CryptoTypes::Sha256Hash &hash) +{ + VerifyOrReturnStatus(data && dataLength > 0, ALIRO_INVALID_ARGUMENT); + + size_t hashLength; + psa_status_t status = + psa_hash_compute(PSA_ALG_SHA_256, data, dataLength, hash.data(), hash.size(), &hashLength); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Cannot compute SHA-256 hash [Error: %d]", status)); + VerifyOrReturnStatus(hashLength == hash.size(), ALIRO_ERROR_INTERNAL, LOG_WRN("Invalid SHA-256 hash length")); + + return ALIRO_NO_ERROR; +} + +} // namespace Aliro::Interface::Crypto diff --git a/app/src/aliro/platform/logger/platform_log.cpp b/app/src/aliro/interface_impl/log.cpp similarity index 62% rename from app/src/aliro/platform/logger/platform_log.cpp rename to app/src/aliro/interface_impl/log.cpp index 2cf429ca..4a44b8df 100644 --- a/app/src/aliro/platform/logger/platform_log.cpp +++ b/app/src/aliro/interface_impl/log.cpp @@ -1,19 +1,42 @@ /* - * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright (c) 2026 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#include "logger/platform_log.h" +#include #include -#include +LOG_MODULE_REGISTER(aliro_stack, CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE); -LOG_MODULE_REGISTER(platform, CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE); +namespace Aliro::Interface::Logging { -void _AliroPlatformLogHexdump(uint8_t platformLogLevel, const void *data, size_t size, const char *str) +void Log(uint8_t platformLogLevel, const char *logFormat, ...) { +#if defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_MINIMAL) + + if (platformLogLevel > CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE) { + return; + } + + va_list paramList; + va_start(paramList, logFormat); + log_generic(platformLogLevel, logFormat, paramList); + va_end(paramList); + +#else // defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_MINIMAL) + + ARG_UNUSED(platformLogLevel); + ARG_UNUSED(logFormat); + +#endif // defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_MINIMAL) +} + +void LogHexdump(uint8_t platformLogLevel, const void *data, size_t size, const char *str) +{ +#if defined(CONFIG_LOG) + switch (platformLogLevel) { case LOG_LEVEL_ERR: LOG_HEXDUMP_ERR(data, size, str); @@ -31,25 +54,15 @@ void _AliroPlatformLogHexdump(uint8_t platformLogLevel, const void *data, size_t default: break; } -} - -void _AliroPlatformLog(uint8_t platformLogLevel, const char *logFormat, ...) -{ -#if defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_MINIMAL) - if (platformLogLevel > CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE) { - return; - } - - va_list paramList; - va_start(paramList, logFormat); - log_generic(platformLogLevel, logFormat, paramList); - va_end(paramList); - -#else // defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_MINIMAL) +#else // defined(CONFIG_LOG) ARG_UNUSED(platformLogLevel); - ARG_UNUSED(logFormat); + ARG_UNUSED(data); + ARG_UNUSED(size); + ARG_UNUSED(str); -#endif // defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_MINIMAL) +#endif // defined(CONFIG_LOG) } + +} // namespace Aliro::Interface::Logging diff --git a/app/src/aliro/interface_impl/os.cpp b/app/src/aliro/interface_impl/os.cpp new file mode 100644 index 00000000..87dfb1a0 --- /dev/null +++ b/app/src/aliro/interface_impl/os.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/aliro.h" +#include "aliro/aliro_work/aliro_work.h" +#include "aliro/errors.h" +#include "aliro/interface.h" +#include "aliro/utils.h" + +#include +#include +#include + +LOG_MODULE_REGISTER(interface_events, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); + +namespace Aliro::Interface::Os { + +namespace { + +K_FIFO_DEFINE(sEventsFifo); +/* + * Set by producers after enqueue. + * Worker clears it before draining and checks it again after drain: + * if it was set meanwhile, new events arrived concurrently and the worker + * should continue (or resubmit during exit window handling). + */ +atomic_t sNewEvents = ATOMIC_INIT(0); +/* + * Scheduling/running token for the worker. + * 0: no work item queued/running + * 1: work item queued/running + * + * Producer uses CAS(0->1) to ensure only one submit for a burst. + * Worker clears it at end, then performs a final resubmit check to close + * the race where producer enqueued while worker was still active. + */ +atomic_t sWorkerActive = ATOMIC_INIT(0); + +void ProcessEventsWorkHandler(k_work *work) +{ + while (true) { + /* + * Start a new drain pass. Any producer that enqueues during/after this + * point will set sNewEvents again, which we observe after FIFO becomes + * empty to decide whether another pass is needed. + */ + atomic_clear(&sNewEvents); + + while (void *event = k_fifo_get(&sEventsFifo, K_NO_WAIT)) { + AliroStack::Instance().ProcessEvent(event); + } + + if (!atomic_get(&sNewEvents)) { + break; + } + } + + atomic_clear(&sWorkerActive); + + /* + * Close the exit race: + * - producer may enqueue just before/around worker deactivation + * - producer cannot submit while sWorkerActive==1 + * - therefore worker performs one final check and self-resubmits if needed + */ + if (atomic_get(&sNewEvents) && atomic_cas(&sWorkerActive, 0, 1)) { + const int submitErr = AliroWorkSubmit(work); + if (submitErr < 0) { + atomic_clear(&sWorkerActive); + LOG_ERR("Failed to resubmit events work, err: %d", submitErr); + } + } +} + +K_WORK_DEFINE(sProcessEventsWork, ProcessEventsWorkHandler); + +} // namespace + +AliroError QueueEvent(void *event) +{ + VerifyOrReturnStatus(event != nullptr, ALIRO_INVALID_ARGUMENT); + + k_fifo_put(&sEventsFifo, event); + /* Signal that at least one event is pending since current worker pass started. */ + atomic_set(&sNewEvents, 1); + + /* First producer in a burst schedules processing; others only enqueue+mark. */ + if (atomic_cas(&sWorkerActive, 0, 1)) { + const int submitErr = AliroWorkSubmit(&sProcessEventsWork); + if (submitErr < 0) { + atomic_clear(&sWorkerActive); + /* Do not return here, as it may cause the stack to free the event + * before removing it from the queue. */ + } + } + + return ALIRO_NO_ERROR; +} + +} // namespace Aliro::Interface::Os diff --git a/app/src/aliro/interface_impl/os_mutex.cpp b/app/src/aliro/interface_impl/os_mutex.cpp new file mode 100644 index 00000000..cce3806f --- /dev/null +++ b/app/src/aliro/interface_impl/os_mutex.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/interface.h" +#include "aliro/utils.h" + +#include +#include +#include + +LOG_MODULE_REGISTER(interface_mutex, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); + +namespace Aliro::Interface::Os::Mutex { + +namespace { + +K_MUTEX_DEFINE(sStackSessionMutex); + +} // namespace + +void Lock() +{ + const int lockErr = k_mutex_lock(&sStackSessionMutex, K_FOREVER); + VerifyOrDie(lockErr == 0, "Stack session mutex lock failed"); +} + +void Unlock() +{ + const int unlockErr = k_mutex_unlock(&sStackSessionMutex); + VerifyOrDie(unlockErr == 0, "Stack session mutex unlock failed"); +} + +} // namespace Aliro::Interface::Os::Mutex diff --git a/app/src/aliro/interface_impl/os_timer.cpp b/app/src/aliro/interface_impl/os_timer.cpp new file mode 100644 index 00000000..ce48385c --- /dev/null +++ b/app/src/aliro/interface_impl/os_timer.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/interface.h" + +#include "aliro/aliro_work/aliro_work.h" + +#include +#include +#include + +#include +#include + +LOG_MODULE_REGISTER(interface_os_timer, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); + +namespace Aliro::Interface::Os::Timer { + +namespace { + +constexpr size_t kNfcStackTimers{ 1 }; + +#ifdef CONFIG_DOOR_LOCK_BLE_UWB +constexpr size_t kBleStackTimers{ CONFIG_DOOR_LOCK_BLE_UWB_MAX_SESSIONS }; +#else +constexpr size_t kBleStackTimers{ 0 }; +#endif + +constexpr size_t kStackTimerCount{ kNfcStackTimers + kBleStackTimers }; + +struct TimerSlot { + k_timer mTimer{}; + k_work mWork{}; + Callback mCallback{ nullptr }; + void *mContext{ nullptr }; +}; + +std::array sTimerSlots{}; + +bool IsHandleValid(Handle handle) +{ + return handle >= 0 && handle < static_cast(sTimerSlots.size()); +} + +TimerSlot *GetSlot(Handle handle) +{ + if (IsHandleValid(handle)) { + auto &slot = sTimerSlots[handle]; + if (!slot.mCallback) { + LOG_ERR("Timer slot is not initialized (handle: %d)", handle); + return nullptr; + } + return &slot; + } + + LOG_ERR("Invalid timer slot (handle: %d)", handle); + return nullptr; +} + +void WorkHandler(k_work *work) +{ + auto &slot = *CONTAINER_OF(work, TimerSlot, mWork); + + if (slot.mCallback) { + slot.mCallback(slot.mContext); + } +} + +void ExpiryHandler(k_timer *timer) +{ + auto *slot = static_cast(k_timer_user_data_get(timer)); + if (!slot) { + return; + } + + const auto err = AliroWorkSubmit(&slot->mWork); + if (err < 0) { + LOG_ERR("Failed to submit timer expiry work, err: %d", err); + } +} + +int InitTimerSlots(void) +{ + for (auto &slot : sTimerSlots) { + k_timer_init(&slot.mTimer, ExpiryHandler, nullptr); + k_timer_user_data_set(&slot.mTimer, &slot); + k_work_init(&slot.mWork, WorkHandler); + } + + return 0; +} + +} // namespace + +Handle Acquire(Callback callback, void *context) +{ + if (!callback) { + LOG_ERR("Timer callback must not be null"); + return kInvalidHandle; + } + + for (size_t index = 0; index < kStackTimerCount; index++) { + auto &slot = sTimerSlots[index]; + if (!slot.mCallback) { + slot.mCallback = callback; + slot.mContext = context; + return static_cast(index); + } + } + + LOG_ERR("No free stack timer slot (count: %zu)", kStackTimerCount); + return kInvalidHandle; +} + +void Release(Handle handle) +{ + const auto slot{ GetSlot(handle) }; + if (!slot) { + return; + } + + /* Mark slot as unused first so pending work will not execute callback. */ + slot->mCallback = nullptr; + slot->mContext = nullptr; + + k_timer_stop(&slot->mTimer); + k_work_sync sync{}; + k_work_cancel_sync(&slot->mWork, &sync); +} + +void Start(Handle handle, uint32_t timeoutMs) +{ + const auto slot{ GetSlot(handle) }; + if (!slot) { + return; + } + + k_timer_start(&slot->mTimer, K_MSEC(timeoutMs), K_NO_WAIT); +} + +void Stop(Handle handle) +{ + const auto slot{ GetSlot(handle) }; + if (!slot) { + return; + } + + k_timer_stop(&slot->mTimer); +} + +bool IsRunning(Handle handle) +{ + const auto slot{ GetSlot(handle) }; + if (!slot) { + return false; + } + + return k_timer_remaining_ticks(&slot->mTimer) != 0; +} + +SYS_INIT(InitTimerSlots, POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY); + +} // namespace Aliro::Interface::Os::Timer diff --git a/app/src/aliro/interface_impl/reader.cpp b/app/src/aliro/interface_impl/reader.cpp new file mode 100644 index 00000000..e47546c2 --- /dev/null +++ b/app/src/aliro/interface_impl/reader.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/interface.h" + +#include "reader_cache.h" + +#include + +LOG_MODULE_REGISTER(interface_reader, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); + +namespace Aliro::Interface::Reader { + +AliroError GetIdentifier(Identifier &identifier) +{ + return ReaderCache::Instance().GetIdentifier(identifier); +} + +AliroError GetPublicKey(CryptoTypes::PublicKey &publicKey) +{ + return ReaderCache::Instance().GetPublicKey(publicKey); +} + +bool IsCertificateProvisioned() +{ +#ifdef CONFIG_DOOR_LOCK_READER_CERTIFICATE + return ReaderCache::Instance().IsCertificateSet() && ReaderCache::Instance().IsIssuerPublicKeySet(); +#else // CONFIG_DOOR_LOCK_READER_CERTIFICATE + return false; +#endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE +} + +AliroError GetIssuerPublicKey(CryptoTypes::PublicKey &publicKey) +{ +#ifdef CONFIG_DOOR_LOCK_READER_CERTIFICATE + return ReaderCache::Instance().GetIssuerPublicKey(publicKey); +#else // CONFIG_DOOR_LOCK_READER_CERTIFICATE + ARG_UNUSED(publicKey); + return ALIRO_ERROR_NOT_IMPLEMENTED; +#endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE +} + +AliroError GetCertificate(ConstData &certificate) +{ +#ifdef CONFIG_DOOR_LOCK_READER_CERTIFICATE + return ReaderCache::Instance().GetCertificate(certificate); +#else // CONFIG_DOOR_LOCK_READER_CERTIFICATE + ARG_UNUSED(certificate); + return ALIRO_ERROR_NOT_IMPLEMENTED; +#endif // CONFIG_DOOR_LOCK_READER_CERTIFICATE +} + +} // namespace Aliro::Interface::Reader diff --git a/app/src/aliro/interface_impl/session.cpp b/app/src/aliro/interface_impl/session.cpp new file mode 100644 index 00000000..9cd3ebac --- /dev/null +++ b/app/src/aliro/interface_impl/session.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aliro/interface.h" + +#include "aliro/access_manager/access_manager.h" +#include "aliro/platform/nfc/nfc_transport_rfal.h" + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB +#include "aliro/platform/ble/ble_manager.h" +#endif // CONFIG_NCS_ALIRO_BLE_UWB + +namespace Aliro::Interface::Session { + +AliroError Send(ConnectionHandle handle, Data data) +{ + if (handle.IsNfc()) { + return NfcTransportRfal::Instance().Send(data); + } +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + else if (handle.IsBle()) { + return BleManager::Instance().Send(handle, data); + } +#endif // CONFIG_NCS_ALIRO_BLE_UWB + + return ALIRO_INVALID_ARGUMENT; +} + +void HandleTermination(ConnectionHandle handle) +{ + if (handle.IsNfc()) { + NfcTransportRfal::Instance().Terminate(); + } +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + else if (handle.IsBle()) { + BleManager::Instance().Terminate(handle); + } +#endif // CONFIG_NCS_ALIRO_BLE_UWB + + AccessManagerInstance().HandleSessionTermination(handle); +} + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + +AliroError StartRangingSession(ConnectionHandle handle, uint32_t rangingSessionId, const CryptoTypes::Ursk &ursk, + ProtocolVersion protocolVersion) +{ + return AccessManagerInstance().StartRangingSession(rangingSessionId, ursk, protocolVersion, handle); +} + +#endif // CONFIG_NCS_ALIRO_BLE_UWB + +} // namespace Aliro::Interface::Session diff --git a/app/src/aliro/interface_impl/uwb.cpp b/app/src/aliro/interface_impl/uwb.cpp new file mode 100644 index 00000000..df2cad2c --- /dev/null +++ b/app/src/aliro/interface_impl/uwb.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + +#include "aliro/interface.h" + +#include "uwb_impl.h" + +namespace Aliro::Interface { + +namespace Uwb { + +AliroError HandleBleMessage(ConnectionHandle sessionContext, const uint8_t *data, size_t length) +{ + return ::Aliro::Uwb::UltraWideBandImpl::Instance().HandleBleMessage(data, length, sessionContext); +} + +} // namespace Uwb + +} // namespace Aliro::Interface + +#endif // CONFIG_NCS_ALIRO_BLE_UWB diff --git a/app/src/aliro/kpersistent_manager/Kconfig b/app/src/aliro/kpersistent_manager/Kconfig index 8498a12f..3b855a0f 100644 --- a/app/src/aliro/kpersistent_manager/Kconfig +++ b/app/src/aliro/kpersistent_manager/Kconfig @@ -7,10 +7,8 @@ config MAX_NUMBER_OF_KPERSISTENT int "Maximum number of supported Kpersistent keys" depends on DOOR_LOCK_EXPEDITED_FAST_PHASE - default DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS if DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT - range 1 DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS if DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT - default 255 - range 1 255 + default DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS + range 1 DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS help Specifies the maximum number of Kpersistent keys that can be supported in the Expedited-fast Phase. diff --git a/lib/aliro/interfaces/kpersistent_manager/kpersistent_manager.h b/app/src/aliro/kpersistent_manager/kpersistent_manager.h similarity index 94% rename from lib/aliro/interfaces/kpersistent_manager/kpersistent_manager.h rename to app/src/aliro/kpersistent_manager/kpersistent_manager.h index 7d7fc3ad..5485ebb2 100644 --- a/lib/aliro/interfaces/kpersistent_manager/kpersistent_manager.h +++ b/app/src/aliro/kpersistent_manager/kpersistent_manager.h @@ -42,13 +42,13 @@ class KpersistentManager { * @brief Preserve a Kpersistent key. * * @param publicKey The user's public key corresponding to the Kpersistent key. - * @param kpersistentKeyId The resulting keyID of the stored Kpersistent. + * @param kpersistentKeyId The volatile Kpersistent key ID. * * @return ALIRO_NO_ERROR when success, ALIRO_KEY_ALREADY_EXISTS if the Kpersistent already exists, other error * status otherwise. */ virtual AliroError PreserveKpersistent(const CryptoTypes::PublicKey &publicKey, - CryptoTypes::KeyId &kpersistentKeyId) = 0; + CryptoTypes::KeyId kpersistentKeyId) = 0; /** * @brief Remove a Kpersistent key. diff --git a/app/src/aliro/kpersistent_manager/kpersistent_manager_impl.cpp b/app/src/aliro/kpersistent_manager/kpersistent_manager_impl.cpp index f7c2a49d..3b80f453 100644 --- a/app/src/aliro/kpersistent_manager/kpersistent_manager_impl.cpp +++ b/app/src/aliro/kpersistent_manager/kpersistent_manager_impl.cpp @@ -6,11 +6,12 @@ #include "kpersistent_manager_impl.h" -#include "access_manager/access_manager.h" +#include "access_manager.h" #include "aliro/crypto_key_ids.h" #include "aliro/errors.h" +#include "aliro/interface.h" #include "aliro/utils.h" -#include "crypto/crypto.h" +#include "crypto/utils.h" #include "zephyr/sys/util.h" #include @@ -42,7 +43,7 @@ void KpersistentManagerImpl::Init() for (size_t i = 0; i < kMaxKpersistentCount; i++) { const auto kpersistentKeyId = ToKpersistentKeyId(i); - auto error = CryptoInstance().IsKeyValid(kpersistentKeyId); + auto error = DoorLock::Crypto::IsKeyAvailable(kpersistentKeyId); if (error != ALIRO_NO_ERROR) { continue; } @@ -75,7 +76,7 @@ AliroError KpersistentManagerImpl::GetKpersistentKeyIds(KeyId *keyIds, size_t &c return ALIRO_NO_ERROR; } -AliroError KpersistentManagerImpl::PreserveKpersistent(const PublicKey &publicKey, KeyId &kpersistentKeyId) +AliroError KpersistentManagerImpl::PreserveKpersistent(const PublicKey &publicKey, KeyId kpersistentKeyId) { size_t index{}; VerifyOrReturnStatus(AccessManagerInstance().IsPublicKeyStored(publicKey, &index), ALIRO_PUBLIC_KEY_NOT_FOUND, @@ -92,17 +93,16 @@ AliroError KpersistentManagerImpl::PreserveKpersistent(const PublicKey &publicKe LOG_DBG("Removing existing Kpersistent key"); auto tempKpersistentKeyId{ kpersistentKeyIdPersistent }; - auto status = CryptoInstance().DestroyKey(tempKpersistentKeyId); + auto status = DoorLock::Crypto::DestroyKey(tempKpersistentKeyId); VerifyOrReturnStatus(status == ALIRO_NO_ERROR, status, LOG_ERR("Cannot remove existing Kpersistent")); mKpersistentMap[index] = false; mKpersistentCount--; } - AliroError status = CryptoInstance().PreserveKey(kpersistentKeyId, kpersistentKeyIdPersistent); + AliroError status = DoorLock::Crypto::PreserveKey(kpersistentKeyId, kpersistentKeyIdPersistent); VerifyOrReturnStatus(status == ALIRO_NO_ERROR, status, LOG_ERR("Cannot preserve new Kpersistent")); - kpersistentKeyId = kpersistentKeyIdPersistent; mKpersistentMap[index] = true; mKpersistentCount++; @@ -123,7 +123,7 @@ AliroError KpersistentManagerImpl::RemoveKpersistent(size_t kpersistentKeyOffset VerifyOrReturnStatus(IN_RANGE(kPersistentKeyIdPersistent, kKpersistentRangeBegin, kKpersistentRangeEnd), ALIRO_INVALID_ARGUMENT, LOG_WRN("Kpersistent key ID is out of range")); - VerifyOrReturnStatus(CryptoInstance().DestroyKey(kPersistentKeyIdPersistent) == ALIRO_NO_ERROR, + VerifyOrReturnStatus(DoorLock::Crypto::DestroyKey(kPersistentKeyIdPersistent) == ALIRO_NO_ERROR, ALIRO_ERROR_INTERNAL, LOG_WRN("Cannot remove Kpersistent with key ID: 0x%08x", kPersistentKeyIdPersistent)); @@ -156,7 +156,8 @@ AliroError KpersistentManagerImpl::GetAccessCredentialPublicKey(CryptoTypes::Key VerifyOrReturnStatus(mKpersistentMap[index], ALIRO_INVALID_ARGUMENT); AliroError status = AccessManagerInstance().GetPublicKey(index, publicKey); - VerifyOrReturnStatus(status == ALIRO_NO_ERROR, status, LOG_ERR("Cannot get Access Credential public key")); + VerifyOrReturnStatus(status == ALIRO_NO_ERROR, ALIRO_PUBLIC_KEY_NOT_FOUND, + LOG_ERR("Cannot get Access Credential public key")); return ALIRO_NO_ERROR; } diff --git a/app/src/aliro/kpersistent_manager/kpersistent_manager_impl.h b/app/src/aliro/kpersistent_manager/kpersistent_manager_impl.h index 4ad8999b..aeab1539 100644 --- a/app/src/aliro/kpersistent_manager/kpersistent_manager_impl.h +++ b/app/src/aliro/kpersistent_manager/kpersistent_manager_impl.h @@ -6,7 +6,7 @@ #pragma once -#include "kpersistent_manager/kpersistent_manager.h" +#include "kpersistent_manager.h" #include #include @@ -22,7 +22,7 @@ class KpersistentManagerImpl : public KpersistentManager { AliroError GetKpersistentKeyIds(CryptoTypes::KeyId *keyIds, size_t &count) override; AliroError PreserveKpersistent(const CryptoTypes::PublicKey &publicKey, - CryptoTypes::KeyId &kpersistentKeyId) override; + CryptoTypes::KeyId kpersistentKeyId) override; AliroError RemoveKpersistent(size_t kpersistentKeyOffset) override; diff --git a/app/src/aliro/lock_sim/Kconfig b/app/src/aliro/lock_sim/Kconfig index 9e7c2dcf..90ed9ce2 100644 --- a/app/src/aliro/lock_sim/Kconfig +++ b/app/src/aliro/lock_sim/Kconfig @@ -4,6 +4,8 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +menu "Lock simulator" + config DOOR_LOCK_LOCK_SIM_MOVEMENT_TIME_MS int "Movement time of the lock actuator (ms)" default 2000 @@ -14,6 +16,7 @@ config DOOR_LOCK_LOCK_SIM_MOVEMENT_TIME_MS config DOOR_LOCK_LOCK_SIM_AUTO_RELOCK bool "Enable auto relock" default y + depends on !CHIP help Enable auto relock of the lock. When enabled, the lock will relock itself after a certain time. @@ -27,3 +30,12 @@ config DOOR_LOCK_LOCK_SIM_AUTO_RELOCK_TIME_MS The auto relock time is the time it takes for the lock to relock itself after it has been unlocked. endif # DOOR_LOCK_LOCK_SIM_AUTO_RELOCK + +config DOOR_LOCK_LOCK_SIM_INDICATOR + bool "Enable lock simulator indicator" + default y + depends on !CHIP + help + Enable the lock simulator indicator. When enabled, the lock simulator will indicate the lock state. + +endmenu # Lock simulator diff --git a/app/src/aliro/lock_sim/lock_sim.cpp b/app/src/aliro/lock_sim/lock_sim.cpp index 96f8b65d..59ddb7f9 100644 --- a/app/src/aliro/lock_sim/lock_sim.cpp +++ b/app/src/aliro/lock_sim/lock_sim.cpp @@ -5,14 +5,35 @@ */ #include "lock_sim.h" + +#include "aliro/aliro_work/aliro_work.h" #include "aliro/utils.h" +#ifdef CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR +#include +#endif // CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR + #include +#include + LOG_MODULE_REGISTER(lock_sim, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); namespace Aliro { +namespace { + +#ifdef CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR + +constexpr int kLedOn{ 1 }; +constexpr int kLedOff{ 0 }; + +constexpr gpio_dt_spec kLockSimIndicatorLed = GPIO_DT_SPEC_GET(DT_ALIAS(lock_sim_indicator), gpios); + +#endif // CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR + +} // namespace + void LockSim::Init(LockStateChangeCallback callback) { mLockStateChangeCallback = callback; @@ -26,6 +47,12 @@ void LockSim::Init(LockStateChangeCallback callback) k_timer_init(&mAutoRelockTimer, &LockSim::AutoRelockTimerEventHandler, nullptr); k_timer_user_data_set(&mAutoRelockTimer, this); #endif // CONFIG_DOOR_LOCK_LOCK_SIM_AUTO_RELOCK + +#ifdef CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR + VerifyOrDie(gpio_is_ready_dt(&kLockSimIndicatorLed), "Lock simulator indicator GPIO not ready"); + VerifyOrDie(gpio_pin_configure_dt(&kLockSimIndicatorLed, GPIO_OUTPUT_INACTIVE) == 0, + "Failed to configure lock simulator indicator GPIO"); +#endif // CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR } bool LockSim::Lock(OperationSource source) @@ -47,7 +74,7 @@ void LockSim::StartOperation(OperationSource source, ReaderStateByte state) mSource = source; mState = state; - k_work_submit(&mNotifyWork); + std::ignore = AliroWorkSubmit(&mNotifyWork); k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); } @@ -75,7 +102,7 @@ void LockSim::ActuatorTimerEventHandler() } if (prevState != mState) { - k_work_submit(&mNotifyWork); + std::ignore = AliroWorkSubmit(&mNotifyWork); } } @@ -90,9 +117,15 @@ void LockSim::NotifyWorkHandler() switch (mState) { case ReaderStateByte::Secured: LOG_INF("Locking the lock completed"); +#ifdef CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR + gpio_pin_set_dt(&kLockSimIndicatorLed, kLedOff); +#endif // CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR break; case ReaderStateByte::Unsecured: LOG_INF("Unlocking the lock completed"); +#ifdef CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR + gpio_pin_set_dt(&kLockSimIndicatorLed, kLedOn); +#endif // CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR #ifdef CONFIG_DOOR_LOCK_LOCK_SIM_AUTO_RELOCK StartAutoRelock(); #endif // CONFIG_DOOR_LOCK_LOCK_SIM_AUTO_RELOCK diff --git a/app/src/aliro/lock_sim/lock_sim_instance.h b/app/src/aliro/lock_sim/lock_sim_instance.h new file mode 100644 index 00000000..187d74df --- /dev/null +++ b/app/src/aliro/lock_sim/lock_sim_instance.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +#pragma once + +#include "lock_sim.h" + +namespace Aliro { + +/** + * @brief Get the LockSim instance. + * + * @return The LockSim instance. + */ +inline LockSim &LockSimInstance() +{ + static LockSim lockSim; + return lockSim; +} + +} // namespace Aliro diff --git a/app/src/aliro/platform/CMakeLists.txt b/app/src/aliro/platform/CMakeLists.txt index e72da348..8555cd06 100644 --- a/app/src/aliro/platform/CMakeLists.txt +++ b/app/src/aliro/platform/CMakeLists.txt @@ -6,12 +6,13 @@ zephyr_include_directories(.) -add_subdirectory(nfc_transport_impl) -add_subdirectory(logger) +zephyr_library_sources(timer.cpp) -if(CONFIG_DOOR_LOCK_BLE_UWB OR CONFIG_DOOR_LOCK_DFU_BLE_SMP OR CONFIG_DOOR_LOCK_BLE_NUS) +add_subdirectory(crypto) +add_subdirectory(nfc) + +if(CONFIG_BT) add_subdirectory(ble) endif() add_subdirectory_ifdef(CONFIG_DOOR_LOCK_BLE_UWB uwb_impl) -add_subdirectory_ifdef(CONFIG_ACCESS_DECISION_INDICATOR access_decision_indicator) diff --git a/app/src/aliro/platform/Kconfig b/app/src/aliro/platform/Kconfig index d8f9e442..36f5e672 100644 --- a/app/src/aliro/platform/Kconfig +++ b/app/src/aliro/platform/Kconfig @@ -4,13 +4,13 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -rsource "access_decision_indicator/Kconfig" -rsource "nfc_transport_impl/Kconfig" +rsource "crypto/Kconfig" +rsource "nfc/Kconfig" if DOOR_LOCK_BLE_UWB rsource "uwb_impl/Kconfig" endif # DOOR_LOCK_BLE_UWB -if DOOR_LOCK_BLE_UWB || DOOR_LOCK_DFU_BLE_SMP || DOOR_LOCK_BLE_NUS +if BT rsource "ble/Kconfig" -endif # DOOR_LOCK_BLE_UWB || DOOR_LOCK_DFU_BLE_SMP || DOOR_LOCK_BLE_NUS +endif # BT diff --git a/app/src/aliro/platform/access_decision_indicator/Kconfig b/app/src/aliro/platform/access_decision_indicator/Kconfig deleted file mode 100644 index da278698..00000000 --- a/app/src/aliro/platform/access_decision_indicator/Kconfig +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2025 Nordic Semiconductor ASA -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# - -config ACCESS_DECISION_INDICATOR - bool "Enable access decision indicator" - default y if !CHIP - -config RESET_ACCESS_DECISION_INDICATOR_STATE_DELAY_MS - int "Access decision indicator reset state delay (ms)" - depends on ACCESS_DECISION_INDICATOR - default 1000 - help - Specifies how long (in milliseconds) the access indicator (e.g. LED) remains active before automatically - resetting to its default state. This is useful to provide a visual indication of the access decision made - by a Aliro Access Manager (e.g. LED indication). diff --git a/app/src/aliro/platform/access_decision_indicator/access_decision_indicator.cpp b/app/src/aliro/platform/access_decision_indicator/access_decision_indicator.cpp deleted file mode 100644 index c95dda61..00000000 --- a/app/src/aliro/platform/access_decision_indicator/access_decision_indicator.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#include "access_decision_indicator.h" - -#include "aliro/utils.h" - -#include -#include - -namespace { - -constexpr int kDelayMs{ CONFIG_RESET_ACCESS_DECISION_INDICATOR_STATE_DELAY_MS }; -constexpr int kLedOn{ 1 }; -constexpr int kLedOff{ 0 }; -constexpr gpio_dt_spec kAccessGrantedLed = GPIO_DT_SPEC_GET(DT_ALIAS(access_decision_indicator), gpios); - -static K_WORK_DELAYABLE_DEFINE(ResetIndicatorStateWork, - []([[maybe_unused]] k_work *) { (void)gpio_pin_set_dt(&kAccessGrantedLed, kLedOff); }); - -} // namespace - -namespace Aliro::Access::Indicator { - -AliroError InitAccessDecisionIndicator() -{ - VerifyOrReturnStatus(gpio_is_ready_dt(&kAccessGrantedLed), ALIRO_ERROR_INTERNAL); - VerifyOrReturnStatus(gpio_pin_configure_dt(&kAccessGrantedLed, GPIO_OUTPUT_INACTIVE) == 0, - ALIRO_ERROR_INTERNAL); - - return ALIRO_NO_ERROR; -} - -void SignalAccessGranted() -{ - (void)gpio_pin_set_dt(&kAccessGrantedLed, kLedOn); - (void)k_work_schedule(&ResetIndicatorStateWork, K_MSEC(kDelayMs)); -} - -} // namespace Aliro::Access::Indicator diff --git a/app/src/aliro/platform/access_decision_indicator/access_decision_indicator.h b/app/src/aliro/platform/access_decision_indicator/access_decision_indicator.h deleted file mode 100644 index c1d506b2..00000000 --- a/app/src/aliro/platform/access_decision_indicator/access_decision_indicator.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -#include - -namespace Aliro::Access::Indicator { - -/** - * @brief Initializes a access decision indicator (e.g LED). - * This function sets up the access decision indicator to be used - * when an access decision is made. - * - * @return ALIRO_NO_ERROR on success, or an error code on failure. - */ -AliroError InitAccessDecisionIndicator(); - -/** - * @brief Signals that access has been granted. - * This function activates the access decision indicator (e.g LED) - * to indicate that access has been granted. - * It also schedules a work to reset the indicator state after a delay. - */ -void SignalAccessGranted(); - -} // namespace Aliro::Access::Indicator diff --git a/app/src/aliro/platform/ble/CMakeLists.txt b/app/src/aliro/platform/ble/CMakeLists.txt index f19e464a..a1bae64c 100644 --- a/app/src/aliro/platform/ble/CMakeLists.txt +++ b/app/src/aliro/platform/ble/CMakeLists.txt @@ -6,10 +6,15 @@ zephyr_include_directories(.) - add_subdirectory_ifdef(CONFIG_DOOR_LOCK_BLE_UWB gatt_server) add_subdirectory_ifdef(CONFIG_DOOR_LOCK_BLE_UWB l2cap_server) -file(GLOB ble_src CONFIGURE_DEPENDS *.cpp) +# BLE Manager (always included for BT builds) +zephyr_library_sources(ble_manager.cpp) -zephyr_library_sources(${ble_src}) +# BLE Advertising Arbiter - select implementation based on Matter support +if(CONFIG_CHIP) + zephyr_library_sources(ble_advertising_arbiter_chip.cpp) +else() + zephyr_library_sources(ble_advertising_arbiter.cpp) +endif() diff --git a/app/src/aliro/platform/ble/ble_advertising_arbiter.cpp b/app/src/aliro/platform/ble/ble_advertising_arbiter.cpp new file mode 100644 index 00000000..3a6615b5 --- /dev/null +++ b/app/src/aliro/platform/ble/ble_advertising_arbiter.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * BLE Advertising Arbiter implementation for non-Matter builds. + * + * This implementation directly manages BLE advertising using Zephyr's bt_le_adv_* APIs. + * It is compiled only when CONFIG_CHIP is not enabled. + */ + +#include "ble_advertising_arbiter.h" + +#include "aliro/utils.h" +#include "mutex_guard.h" + +#include +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(BleAdvArbiter, CONFIG_DOOR_LOCK_BLE_LOG_LEVEL); + +namespace DoorLock::Interface::BleAdvertisingArbiter { + +namespace { + +K_MUTEX_DEFINE(sMutex); + +// Registered requests per component +std::array sRequests{}; + +// Currently active (advertising) request +Request *sActiveRequest{ nullptr }; + +// Find the highest priority request based on Component enum order (lower index = higher priority) +std::pair FindTopPriorityRequest() +{ + for (size_t idx = 0; idx < kNumberOfComponents; ++idx) { + if (sRequests[idx]) { + return { sRequests[idx], static_cast(idx) }; + } + } + + return { nullptr, Component::None }; +} + +// Get the component that is currently advertising +Component GetActiveComponent() +{ + for (size_t idx = 0; idx < kNumberOfComponents; ++idx) { + if (sRequests[idx] == sActiveRequest) { + return static_cast(idx); + } + } + return Component::None; +} + +AliroError StopAdvertising() +{ + VerifyOrReturnStatus(sActiveRequest, ALIRO_NO_ERROR, LOG_INF("Currently not advertising")); + + int err = bt_le_adv_stop(); + VerifyOrReturnStatus(err == 0, AliroError::FromInt(err), LOG_ERR("Failed to stop advertising: %d", err)); + + LOG_INF("Stopped advertising for %s", ComponentToString(GetActiveComponent())); + + sActiveRequest = nullptr; + + return ALIRO_NO_ERROR; +} + +size_t GetConnectionCount() +{ + size_t count = 0; + + bt_conn_foreach( + BT_CONN_TYPE_ALL, + [](bt_conn *conn, void *data) { + bt_conn_info info{}; + if (bt_conn_get_info(conn, &info) == 0 && info.state == BT_CONN_STATE_CONNECTED) { + (*static_cast(data))++; + } + }, + &count); + + return count; +} + +AliroError StartAdvertising(Component component, Request &request) +{ + if ((request.mOptions & BT_LE_ADV_OPT_CONN) && GetConnectionCount() >= CONFIG_BT_MAX_CONN) { + LOG_WRN("Cannot start connectable advertising: max connections reached"); + return ALIRO_NO_MEMORY; + } + + bt_le_adv_param advParam = + BT_LE_ADV_PARAM_INIT(request.mOptions, request.mMinInterval, request.mMaxInterval, nullptr); + + int err = bt_le_adv_start(&advParam, request.mAdvertisingData, std::size(request.mAdvertisingData), + request.mScanResponseData, std::size(request.mScanResponseData)); + + VerifyOrReturnStatus(err == 0, AliroError::FromInt(err), + LOG_ERR("Failed to start advertising for %s: %d", ComponentToString(component), err)); + + sActiveRequest = &request; + + LOG_INF("Started advertising for %s", ComponentToString(component)); + + return ALIRO_NO_ERROR; +} + +} // namespace + +AliroError InsertRequest(Component component, Request &request) +{ + MutexGuard lock{ sMutex }; + + const auto componentIdx = static_cast(component); + sRequests[componentIdx] = &request; + + auto [top, topComponent] = FindTopPriorityRequest(); + VerifyOrReturnStatus(top, ALIRO_ERROR_INTERNAL, LOG_ERR("Failed to find top priority request")); + + // Try to update advertising data first. + // If advertising is not active, bt_le_adv_update_data returns -EAGAIN and we start advertising. + int err = bt_le_adv_update_data(top->mAdvertisingData, std::size(top->mAdvertisingData), top->mScanResponseData, + std::size(top->mScanResponseData)); + + if (err == 0) { + sActiveRequest = top; + LOG_INF("Advertising data updated successfully for %s", ComponentToString(topComponent)); + return ALIRO_NO_ERROR; + } else if (err == -EAGAIN) { + return StartAdvertising(topComponent, *top); + } + + LOG_ERR("Failed to update advertising data for %s: %d", ComponentToString(topComponent), err); + + return AliroError::FromInt(err); +} + +AliroError CancelRequest(Component component) +{ + MutexGuard lock{ sMutex }; + + const auto componentIdx = static_cast(component); + Request *requestToCancel = sRequests[componentIdx]; + + // If the cancelled request is currently active, stop advertising + if (sActiveRequest == requestToCancel) { + ReturnErrorOnFailure(StopAdvertising()); + } + + sRequests[componentIdx] = nullptr; + + // Start advertising for the next highest priority request, if any + auto [newTop, newTopComponent] = FindTopPriorityRequest(); + if (!newTop) { + LOG_INF("No more advertising requests pending"); + return ALIRO_NO_ERROR; + } + + return StartAdvertising(newTopComponent, *newTop); +} + +} // namespace DoorLock::Interface::BleAdvertisingArbiter diff --git a/app/src/aliro/platform/ble/ble_advertising_arbiter.h b/app/src/aliro/platform/ble/ble_advertising_arbiter.h new file mode 100644 index 00000000..2407225f --- /dev/null +++ b/app/src/aliro/platform/ble/ble_advertising_arbiter.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include "aliro/errors.h" + +#include + +#include + +namespace DoorLock::Interface::BleAdvertisingArbiter { + +/** + * @file + * BLE Advertising Arbiter for the Door Lock application. + * + * This module coordinates BLE advertising between different application components + * (Aliro, SMP DFU, NUS). When multiple components request BLE advertising at the + * same time, the arbiter selects the one with the highest priority based on the + * Component enum order (Aliro > Smp > Nus) and starts BLE advertising using + * parameters defined in the winning request. + * + * Two implementations exist: + * - Non-Matter (ble_advertising_arbiter.cpp): Directly manages bt_le_adv_* calls + * - Matter (ble_advertising_arbiter_chip.cpp): Delegates to Matter's BLEAdvertisingArbiter + * + * The appropriate implementation is selected at compile time based on CONFIG_CHIP. + */ + +/** + * @brief BLE advertising component identifiers. + * + * Components are ordered by default priority (lower value = higher priority). + */ +enum class Component : uint8_t { + Aliro, ///< Aliro BLE/UWB service + Smp, ///< SMP DFU service + Nus, ///< Nordic UART Service + None ///< None +}; + +/** + * @brief Number of components. + */ +static constexpr size_t kNumberOfComponents{ static_cast(Component::None) }; + +/** + * @brief Convert component enum to string for logging. + */ +constexpr const char *ComponentToString(Component component) +{ + switch (component) { + case Component::Aliro: + return "Aliro"; + case Component::Smp: + return "SMP"; + case Component::Nus: + return "NUS"; + case Component::None: + return "None"; + default: + return "Unknown"; + } +} + +/** + * @brief BLE advertising request structure. + */ +struct Request { + uint32_t mOptions; ///< Advertising options (BT_LE_ADV_OPT_XXX) + uint16_t mMinInterval; ///< Minimum advertising interval (0.625 ms units) + uint16_t mMaxInterval; ///< Maximum advertising interval (0.625 ms units) + bt_data mAdvertisingData[2]; ///< Advertising data buffer + bt_data mScanResponseData[1]; ///< Scan response data buffer +}; + +/** + * @brief Request BLE advertising for a component. + * + * If the request has higher priority than other active requests, BLE + * advertising is restarted using the new request's parameters. + * + * @note This function does not take ownership of the Request object, so the object + * must not get destroyed and shall remain valid after the call until the Request + * is cancelled by CancelRequest(). + * + * @param component The component making the request. + * @param request The advertising request parameters. + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ +AliroError InsertRequest(Component component, Request &request); + +/** + * @brief Cancel BLE advertising request for a component. + * + * If the cancelled request was the top-priority one, advertising is + * restarted with the next highest-priority request, or stopped if + * no other requests are active. + * + * @param component The component cancelling its request. + */ +AliroError CancelRequest(Component component); + +} // namespace DoorLock::Interface::BleAdvertisingArbiter diff --git a/app/src/aliro/platform/ble/ble_advertising_arbiter_chip.cpp b/app/src/aliro/platform/ble/ble_advertising_arbiter_chip.cpp new file mode 100644 index 00000000..ec1a034a --- /dev/null +++ b/app/src/aliro/platform/ble/ble_advertising_arbiter_chip.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * BLE Advertising Arbiter implementation for Matter builds. + * + * This implementation delegates to Matter's BLEAdvertisingArbiter to ensure + * compatibility with Matter's BLE requirements. It is compiled only when + * CONFIG_CHIP is enabled. + */ + +#include "ble_advertising_arbiter.h" + +#include +#include + +#include +#include + +#include +#include + +// Undefine Matter macros that conflict with Aliro utilities +#ifdef ReturnErrorOnFailure +#undef ReturnErrorOnFailure +#endif + +#ifdef VerifyOrReturnValue +#undef VerifyOrReturnValue +#endif + +#ifdef VerifyOrExit +#undef VerifyOrExit +#endif + +#ifdef VerifyOrDie +#undef VerifyOrDie +#endif + +#include "aliro/utils.h" + +LOG_MODULE_REGISTER(BleAdvArbiter, CONFIG_DOOR_LOCK_BLE_LOG_LEVEL); + +using namespace ::chip; +using namespace ::chip::DeviceLayer; + +namespace DoorLock::Interface::BleAdvertisingArbiter { + +namespace { + +struct RequestSlot { + Request *mRequest{ nullptr }; + chip::DeviceLayer::BLEAdvertisingArbiter::Request mMatterRequest{}; +}; + +std::array sSlots{}; + +} // namespace + +AliroError InsertRequest(Component component, Request &request) +{ + const auto idx = static_cast(component); + VerifyOrReturnStatus(idx < kNumberOfComponents, ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid component")); + + auto &slot = sSlots[idx]; + + slot.mRequest = &request; + + // Map to Matter arbiter's request structure + // Priority is based on Component enum order (lower index = higher priority) + auto &matterReq = slot.mMatterRequest; + matterReq.priority = static_cast(idx); + matterReq.options = request.mOptions; + matterReq.minInterval = request.mMinInterval; + matterReq.maxInterval = request.mMaxInterval; + matterReq.advertisingData = Span(request.mAdvertisingData, std::size(request.mAdvertisingData)); + matterReq.scanResponseData = + Span(request.mScanResponseData, std::size(request.mScanResponseData)); + matterReq.onStarted = nullptr; + matterReq.onStopped = nullptr; + + // Forward to Matter's arbiter + PlatformMgr().LockChipStack(); + CHIP_ERROR err = chip::DeviceLayer::BLEAdvertisingArbiter::InsertRequest(matterReq); + PlatformMgr().UnlockChipStack(); + + VerifyOrReturnStatus(err == CHIP_NO_ERROR, ALIRO_ERROR_INTERNAL, + LOG_ERR("Failed to insert advertising request for %s", ComponentToString(component))); + + LOG_INF("Inserted advertising request for %s", ComponentToString(component)); + + return ALIRO_NO_ERROR; +} + +AliroError CancelRequest(Component component) +{ + const auto idx = static_cast(component); + VerifyOrReturnStatus(idx < kNumberOfComponents, ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid component")); + + auto &slot = sSlots[idx]; + VerifyOrReturnStatus(slot.mRequest, ALIRO_NO_ERROR, LOG_ERR("No request to cancel")); + + // Forward to Matter's arbiter + PlatformMgr().LockChipStack(); + chip::DeviceLayer::BLEAdvertisingArbiter::CancelRequest(slot.mMatterRequest); + PlatformMgr().UnlockChipStack(); + + slot.mRequest = nullptr; + + LOG_INF("Cancelled advertising request for %s", ComponentToString(component)); + + return ALIRO_NO_ERROR; +} + +} // namespace DoorLock::Interface::BleAdvertisingArbiter diff --git a/app/src/aliro/platform/ble/ble_manager_impl.cpp b/app/src/aliro/platform/ble/ble_manager.cpp similarity index 61% rename from app/src/aliro/platform/ble/ble_manager_impl.cpp rename to app/src/aliro/platform/ble/ble_manager.cpp index 92dcfab3..3337f4e4 100644 --- a/app/src/aliro/platform/ble/ble_manager_impl.cpp +++ b/app/src/aliro/platform/ble/ble_manager.cpp @@ -4,14 +4,17 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#include "ble_manager_impl.h" +#include "ble_manager.h" -#include "aliro/mutex_guard.h" +#include "aliro/aliro.h" +#include "aliro/aliro_work/aliro_work.h" +#include "ble_advertising_arbiter.h" +#include "mutex_guard.h" #ifdef CONFIG_DOOR_LOCK_BLE_UWB #include "gatt_server/gatt_server.h" #include "l2cap_server/l2cap_server.h" -#include "uwb/uwb.h" +#include "uwb.h" #include "uwb_impl.h" #endif // CONFIG_DOOR_LOCK_BLE_UWB @@ -28,52 +31,18 @@ using namespace Aliro::BtNus; #include #include -#ifdef CONFIG_CHIP - -#include -#include - -#ifdef ReturnErrorOnFailure -#undef ReturnErrorOnFailure -#endif - -#ifdef VerifyOrReturnValue -#undef VerifyOrReturnValue -#endif - -#ifdef VerifyOrExit -#undef VerifyOrExit -#endif - -#ifdef VerifyOrDie -#undef VerifyOrDie -#endif - -#endif // CONFIG_CHIP - #include "aliro/utils.h" -LOG_MODULE_REGISTER(BLEManagerImpl, CONFIG_DOOR_LOCK_BLE_LOG_LEVEL); +LOG_MODULE_REGISTER(BleManager, CONFIG_DOOR_LOCK_BLE_LOG_LEVEL); -#ifdef CONFIG_CHIP - -using namespace ::chip; -using namespace ::chip::DeviceLayer; - -namespace { - -BLEAdvertisingArbiter::Request sAdvertisingRequest{}; - -} // namespace - -#endif // CONFIG_CHIP - -namespace Aliro::BleInterface { +namespace Aliro { namespace { K_MUTEX_DEFINE(sMutex); +namespace BleAdvArbiter = DoorLock::Interface::BleAdvertisingArbiter; + constexpr uint8_t GetAdvertisingDataFieldType(BleTypes::AdvertisingDataFieldType type) { switch (type) { @@ -97,7 +66,7 @@ constexpr uint8_t GetAdvertisingDataFieldType(BleTypes::AdvertisingDataFieldType #ifndef CONFIG_CHIP -int BleManagerImpl::CreateRandomStaticAddress() +int BleManager::CreateRandomStaticAddress() { // Generate a random static address for the default identity. // For nRF5340 this must be done before bt_enable() as after that updating the default identity is not possible. @@ -110,12 +79,12 @@ int BleManagerImpl::CreateRandomStaticAddress() return ALIRO_NO_ERROR; } -void BleManagerImpl::Connected(bt_conn *connId, uint8_t error) +void BleManager::Connected(bt_conn *connId, uint8_t error) { mConnectionCount++; ResumeAdvertising(); - VerifyOrReturn(connId, LOG_ERR("Connection ID is null")); + VerifyOrReturn(connId, LOG_ERR("Connection handle is null")); VerifyOrReturn(error == 0, LOG_ERR("Connection failed (error: %d, conn: %p)", error, connId)); LOG_DBG("Connected (conn: %p)", connId); @@ -131,16 +100,10 @@ void BleManagerImpl::Connected(bt_conn *connId, uint8_t error) VerifyOrReturn(refConn, LOG_ERR("Failed to reference connection (conn: %p)", connId)); } -void BleManagerImpl::Disconnected(bt_conn *connId, uint8_t reason) +void BleManager::Disconnected(bt_conn *connId, uint8_t reason) { mConnectionCount--; -#ifdef CONFIG_DOOR_LOCK_BLE_UWB - - L2capServer::Instance().FreeL2capChannel(connId); - -#endif // CONFIG_DOOR_LOCK_BLE_UWB - bt_conn_unref(connId); #ifdef CONFIG_DOOR_LOCK_BLE_NUS @@ -151,7 +114,7 @@ void BleManagerImpl::Disconnected(bt_conn *connId, uint8_t reason) } // This callback is called when the BT is disconnected and also when the advertising is stopped. -void BleManagerImpl::Recycled() +void BleManager::Recycled() { LOG_DBG("Connection recycled"); @@ -160,9 +123,10 @@ void BleManagerImpl::Recycled() #ifdef CONFIG_BT_SMP -void BleManagerImpl::SecurityChanged([[maybe_unused]] bt_conn *connId, bt_security_t level, enum bt_security_err error) +void BleManager::SecurityChanged(bt_conn *connId, bt_security_t level, bt_security_err error) { - VerifyOrReturn(error == 0, LOG_ERR("Security failed (error: %d, conn: %p)", error, connId)); + VerifyOrReturn(error == BT_SECURITY_ERR_SUCCESS, + LOG_WRN("Security failed (error: %d, conn: %p)", static_cast(error), connId)); #ifdef CONFIG_DOOR_LOCK_BLE_NUS NUSService::Instance().SecurityChanged(connId, level, error); @@ -173,20 +137,20 @@ void BleManagerImpl::SecurityChanged([[maybe_unused]] bt_conn *connId, bt_securi #endif // CONFIG_BT_SMP -void BleManagerImpl::ResumeAdvertising() +void BleManager::ResumeAdvertising() { // Allow to resume advertising only if it is enabled. VerifyOrReturn(IsAdvertising(), LOG_DBG("Skipped, advertising is disabled")); - const auto workErr = k_work_submit(&mAdvResumeWork); + const auto workErr = AliroWorkSubmit(&mAdvResumeWork); VerifyOrReturn(workErr >= 0, LOG_ERR("Failed to submit work (error: %d)", workErr)); } -void BleManagerImpl::ResumeAdvertisingHandler() +void BleManager::ResumeAdvertisingHandler() { VerifyOrReturn(mConnectionCount < CONFIG_BT_MAX_CONN, LOG_DBG("Max connections reached")); - auto error = StartAdvertising(); + auto error = RestartAdvertising(); VerifyOrReturn(error == ALIRO_NO_ERROR || error == ALIRO_INVALID_STATE, LOG_ERR("Failed to resume advertising")); @@ -195,7 +159,7 @@ void BleManagerImpl::ResumeAdvertisingHandler() #else // CONFIG_CHIP -AliroError BleManagerImpl::GetRandomStaticAddress() +AliroError BleManager::GetRandomStaticAddress() { static_assert(CONFIG_BT_ID_MAX == 1, "CONFIG_BT_ID_MAX must be 1"); @@ -209,7 +173,7 @@ AliroError BleManagerImpl::GetRandomStaticAddress() #endif // CONFIG_CHIP -AliroError BleManagerImpl::SetAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type) +AliroError BleManager::SetAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type) { VerifyOrReturnStatus(data.mLength <= mAdvertisingServiceData.size(), ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid service data size")); @@ -221,14 +185,14 @@ AliroError BleManagerImpl::SetAdvertisingData(const ConstData &data, BleTypes::A return ALIRO_NO_ERROR; } -AliroError BleManagerImpl::Send(ConnectionHandle handle, Data data) const +AliroError BleManager::Send(ConnectionHandle handle, Data data) const { #ifdef CONFIG_DOOR_LOCK_BLE_UWB VerifyOrReturnStatus(IsInitialized(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not initialized")); - VerifyOrReturnStatus(handle, ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid connection handle")); + VerifyOrReturnStatus(handle.IsBle(), ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid connection handle")); - return L2capServer::Instance().Send(static_cast(handle), data.mData, data.mLength); + return L2capServer::Instance().Send(handle.GetBtConn(), data.mData, data.mLength); #else // CONFIG_DOOR_LOCK_BLE_UWB @@ -237,20 +201,20 @@ AliroError BleManagerImpl::Send(ConnectionHandle handle, Data data) const #endif // CONFIG_DOOR_LOCK_BLE_UWB } -AliroError BleManagerImpl::Disconnect(ConnectionHandle handle) +AliroError BleManager::Terminate(ConnectionHandle handle) { VerifyOrReturnStatus(IsInitialized(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not initialized")); - VerifyOrReturnStatus(handle, ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid connection handle")); + VerifyOrReturnStatus(handle.IsBle(), ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid connection handle")); bt_conn_info info{}; - VerifyOrReturnStatus(bt_conn_get_info(static_cast(handle), &info) == 0, ALIRO_ERROR_INTERNAL, + VerifyOrReturnStatus(bt_conn_get_info(handle.GetBtConn(), &info) == 0, ALIRO_ERROR_INTERNAL, LOG_ERR("Failed to get connection info")); VerifyAndExit(info.state != BT_CONN_STATE_CONNECTED, LOG_DBG("No active connection found")); - LOG_INF("Disconnecting (handle: %p)", handle); + LOG_INF("Disconnecting (handle: %p)", handle.GetRaw()); { - int error = bt_conn_disconnect(static_cast(handle), BT_HCI_ERR_REMOTE_USER_TERM_CONN); + int error = bt_conn_disconnect(handle.GetBtConn(), BT_HCI_ERR_REMOTE_USER_TERM_CONN); VerifyOrReturnStatus(error == 0 || error == -ENOTCONN, ALIRO_ERROR_INTERNAL, LOG_ERR("Failed to disconnect (error: %d)", error)); } @@ -259,9 +223,9 @@ AliroError BleManagerImpl::Disconnect(ConnectionHandle handle) return ALIRO_NO_ERROR; } -void BleManagerImpl::DisconnectAll() +AliroError BleManager::DisconnectAll() { - VerifyOrReturn(IsInitialized(), LOG_ERR("BLE manager not initialized")); + VerifyOrReturnStatus(IsInitialized(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not initialized")); bt_conn_foreach( BT_CONN_TYPE_ALL, @@ -269,16 +233,16 @@ void BleManagerImpl::DisconnectAll() bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); }, nullptr); + + return ALIRO_NO_ERROR; } -AliroError BleManagerImpl::Init(const PlatformTransportCallbacks &callbacks) +AliroError BleManager::Init() { MutexGuard lock{ sMutex }; VerifyOrReturnStatus(!IsInitialized(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager already initialized")); - mTransportCallbacks = callbacks; - #ifndef CONFIG_CHIP k_work_init(&mAdvResumeWork, []([[maybe_unused]] k_work *) { Instance().ResumeAdvertisingHandler(); }); @@ -330,19 +294,12 @@ AliroError BleManagerImpl::Init(const PlatformTransportCallbacks &callbacks) L2capServer::Callbacks l2capCallbacks = { .mOnConnected = - [](bt_conn *conn) { - VerifyAndCall(Instance().mTransportCallbacks.mOnTransportReady, - static_cast(conn)); - }, + [](bt_conn *conn) { AliroStack::Instance().CreateSession(ConnectionHandle::Ble(conn)); }, .mOnDisconnected = - [](bt_conn *conn) { - VerifyAndCall(Instance().mTransportCallbacks.mOnTransportLoss, - static_cast(conn)); - }, + [](bt_conn *conn) { AliroStack::Instance().DestroySession(ConnectionHandle::Ble(conn)); }, .mOnDataReceived = [](bt_conn *conn, uint8_t *data, size_t length) { - VerifyAndCall(Instance().mTransportCallbacks.mOnDataReceived, - static_cast(conn), { data, length }); + AliroStack::Instance().HandleSessionData(ConnectionHandle::Ble(conn), { data, length }); }, }; @@ -358,7 +315,7 @@ AliroError BleManagerImpl::Init(const PlatformTransportCallbacks &callbacks) return ALIRO_NO_ERROR; } -AliroError BleManagerImpl::GetAddress(BleTypes::BleAddress &address) const +AliroError BleManager::GetAddress(BleTypes::BleAddress &address) const { VerifyOrReturnStatus(IsInitialized(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not initialized")); std::copy_n(mAddress.a.val, BleTypes::kBleAddressSize, address.data()); @@ -366,7 +323,7 @@ AliroError BleManagerImpl::GetAddress(BleTypes::BleAddress &address) const return ALIRO_NO_ERROR; } -AliroError BleManagerImpl::GetTxPowerLevel(BleTypes::TxPowerLevel &txPowerLevel) const +AliroError BleManager::GetTxPowerLevel(BleTypes::TxPowerLevel &txPowerLevel) const { VerifyOrReturnStatus(IsInitialized(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not initialized")); @@ -407,7 +364,7 @@ AliroError BleManagerImpl::GetTxPowerLevel(BleTypes::TxPowerLevel &txPowerLevel) return ALIRO_NO_ERROR; } -size_t BleManagerImpl::GetMaxSessions() const +size_t BleManager::GetMaxSessions() const { #ifdef CONFIG_DOOR_LOCK_BLE_UWB_MAX_SESSIONS @@ -420,143 +377,94 @@ size_t BleManagerImpl::GetMaxSessions() const #endif // CONFIG_DOOR_LOCK_BLE_UWB_MAX_SESSIONS } -AliroError BleManagerImpl::StartAdvertising(const ConstData &data, BleTypes::AdvertisingDataFieldType type) +AliroError BleManager::StartAdvertising(const BleTypes::AdvertisingServiceData &data) +{ + BleTypes::AdvertisingService advertisingService{}; + advertisingService.mAdvertisingServiceData = data; + + return StartAdvertising({ reinterpret_cast(&advertisingService), sizeof(advertisingService) }, + BleTypes::AdvertisingDataFieldType::Uuid16); +} + +AliroError BleManager::StartAdvertising(const ConstData &data, BleTypes::AdvertisingDataFieldType type) { VerifyOrReturnStatus(IsInitialized(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not initialized")); ReturnErrorOnFailure(SetAdvertisingData(data, type)); - return StartAdvertising(); + return RestartAdvertising(); } -AliroError BleManagerImpl::StartAdvertising() +AliroError BleManager::RestartAdvertising() { MutexGuard lock{ sMutex }; VerifyOrReturnStatus(IsInitialized(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not initialized")); - mAdvertisingData[kAdvertisingFlagsIndex] = + mBleAdvertisingRequest = BleAdvArbiter::Request{ .mOptions = kAdvertisingOptions, + .mMinInterval = kIntervalMin, + .mMaxInterval = kIntervalMax }; + + // Set advertising data buffers + mBleAdvertisingRequest.mAdvertisingData[0] = BT_DATA(BT_DATA_FLAGS, &kAdvertisingFlags, sizeof(kAdvertisingFlags)); - mAdvertisingData[kAdvertisingServiceDataIndex] = + mBleAdvertisingRequest.mAdvertisingData[1] = BT_DATA(GetAdvertisingDataFieldType(mAdvertisingDataFieldType), mAdvertisingServiceData.data(), mAdvertisingServiceDataSize); const char *name = bt_get_name(); - mScanResponseData[kScanResponseIndex] = + mBleAdvertisingRequest.mScanResponseData[0] = BT_DATA(BT_DATA_NAME_COMPLETE, name, static_cast(strlen(name))); -#ifndef CONFIG_CHIP - - bt_le_adv_param advParam = BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_CONN, kIntervalMin, kIntervalMax, NULL); - - int error = bt_le_adv_start(&advParam, mAdvertisingData.data(), mAdvertisingData.size(), - mScanResponseData.data(), mScanResponseData.size()); - VerifyOrReturnStatus(error == 0 || error == -EALREADY, ALIRO_ERROR_INTERNAL, - LOG_ERR("Cannot start advertising (err %d)", error)); - if (error == -EALREADY) { - LOG_INF("Advertising already started"); - return ALIRO_NO_ERROR; - } - -#else // CONFIG_CHIP - - sAdvertisingRequest.priority = kAdvertisingPriority; - sAdvertisingRequest.options = kAdvertisingOptions; - sAdvertisingRequest.minInterval = kIntervalMin; - sAdvertisingRequest.maxInterval = kIntervalMax; - sAdvertisingRequest.advertisingData = Span(mAdvertisingData); - sAdvertisingRequest.scanResponseData = Span(mScanResponseData); - - sAdvertisingRequest.onStopped = []() { - Instance().SetState(BleManagerState::Initialized); - LOG_DBG("BLE advertising stopped"); - }; - - PlatformMgr().LockChipStack(); - CHIP_ERROR ret = BLEAdvertisingArbiter::InsertRequest(sAdvertisingRequest); - PlatformMgr().UnlockChipStack(); - - VerifyOrReturnStatus(ret == CHIP_NO_ERROR, ALIRO_ERROR_INTERNAL, - LOG_ERR("Failed to insert BLE advertising request")); - -#endif // CONFIG_CHIP + AliroError err = BleAdvArbiter::InsertRequest(BleAdvArbiter::Component::Aliro, mBleAdvertisingRequest); + VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, LOG_ERR("Failed to insert Aliro advertising request")); SetState(BleManagerState::Advertising); - LOG_INF("BLE advertising started"); LOG_HEXDUMP_DBG(mAdvertisingServiceData.data(), mAdvertisingServiceDataSize, "Advertising data:"); return ALIRO_NO_ERROR; } -AliroError BleManagerImpl::UpdateAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type) +AliroError BleManager::UpdateAdvertisingData(const BleTypes::AdvertisingServiceData &data) { - VerifyOrReturnStatus(IsAdvertising(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not advertising")); - - ReturnErrorOnFailure(SetAdvertisingData(data, type)); - - mAdvertisingData[kAdvertisingServiceDataIndex] = - BT_DATA(GetAdvertisingDataFieldType(mAdvertisingDataFieldType), mAdvertisingServiceData.data(), - mAdvertisingServiceDataSize); - -#ifndef CONFIG_CHIP + BleTypes::AdvertisingService advertisingService{}; + advertisingService.mAdvertisingServiceData = data; - int error = bt_le_adv_update_data(mAdvertisingData.data(), mAdvertisingData.size(), mScanResponseData.data(), - mScanResponseData.size()); - VerifyOrReturnStatus(error == 0, ALIRO_ERROR_INTERNAL, - LOG_ERR("Cannot update advertising data (err %d)", error)); - -#else // CONFIG_CHIP - - sAdvertisingRequest.advertisingData = Span(mAdvertisingData); - sAdvertisingRequest.scanResponseData = Span(mScanResponseData); + return UpdateAdvertisingData({ reinterpret_cast(&advertisingService), + sizeof(advertisingService) }, + BleTypes::AdvertisingDataFieldType::Uuid16); +} - PlatformMgr().LockChipStack(); - CHIP_ERROR ret = BLEAdvertisingArbiter::InsertRequest(sAdvertisingRequest); - PlatformMgr().UnlockChipStack(); - VerifyOrReturnStatus(ret == CHIP_NO_ERROR, ALIRO_ERROR_INTERNAL, - LOG_ERR("Failed to update BLE advertising data")); +AliroError BleManager::UpdateAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type) +{ + VerifyOrReturnStatus(IsAdvertising(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not advertising")); -#endif // CONFIG_CHIP + ReturnErrorOnFailure(SetAdvertisingData(data, type)); - LOG_INF("BLE advertising service data updated"); - LOG_HEXDUMP_DBG(mAdvertisingServiceData.data(), mAdvertisingServiceDataSize, "Advertising data:"); - return ALIRO_NO_ERROR; + return RestartAdvertising(); } -AliroError BleManagerImpl::StopAdvertising() +AliroError BleManager::StopAdvertising() { MutexGuard lock{ sMutex }; VerifyOrReturnStatus(IsAdvertising(), ALIRO_INVALID_STATE, LOG_ERR("BLE manager not advertising")); -#ifndef CONFIG_CHIP - - int error = bt_le_adv_stop(); - VerifyOrReturnStatus(error == 0, ALIRO_ERROR_INTERNAL, LOG_ERR("Cannot stop advertising (err %d)", error)); - -#else // CONFIG_CHIP - - PlatformMgr().LockChipStack(); - BLEAdvertisingArbiter::CancelRequest(sAdvertisingRequest); - PlatformMgr().UnlockChipStack(); - -#endif // CONFIG_CHIP + BleAdvArbiter::CancelRequest(BleAdvArbiter::Component::Aliro); SetState(BleManagerState::Initialized); - LOG_INF("BLE advertising stopped"); - return ALIRO_NO_ERROR; } -ProtocolVersion BleManagerImpl::GetProtocolVersion(ConnectionHandle handle) const +ProtocolVersion BleManager::GetProtocolVersion(ConnectionHandle handle) const { #ifdef CONFIG_DOOR_LOCK_BLE_UWB VerifyOrReturnValue(IsInitialized(), BleTypes::kInvalidProtocolVersion, LOG_ERR("BLE manager not initialized")); - VerifyOrReturnValue(handle, BleTypes::kInvalidProtocolVersion, LOG_ERR("Invalid connection handle")); + VerifyOrReturnValue(handle.IsBle(), BleTypes::kInvalidProtocolVersion, LOG_ERR("Invalid connection handle")); - return L2capServer::Instance().GetBleUwbProtocolVersion(static_cast(handle)); + return L2capServer::Instance().GetBleUwbProtocolVersion(handle.GetBtConn()); #else // CONFIG_DOOR_LOCK_BLE_UWB @@ -565,4 +473,4 @@ ProtocolVersion BleManagerImpl::GetProtocolVersion(ConnectionHandle handle) cons #endif // CONFIG_DOOR_LOCK_BLE_UWB } -} // namespace Aliro::BleInterface +} // namespace Aliro diff --git a/app/src/aliro/platform/ble/ble_manager.h b/app/src/aliro/platform/ble/ble_manager.h new file mode 100644 index 00000000..5faa637a --- /dev/null +++ b/app/src/aliro/platform/ble/ble_manager.h @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include "aliro/ble_types.h" +#include "ble_advertising_arbiter.h" + +#ifdef CONFIG_DOOR_LOCK_BLE_UWB +#include "gatt_server/gatt_server.h" +#endif // CONFIG_DOOR_LOCK_BLE_UWB + +#include "aliro/errors.h" +#include "aliro/interface.h" +#include "aliro/protocol_version.h" +#include "aliro/types.h" + +#include +#include +#include + +#include + +namespace Aliro { + +/** + * @brief BLE Manager. + * + * This class manages the BLE stack and provides BLE control functionality. + */ +class BleManager final { +#ifdef CONFIG_DOOR_LOCK_BLE_UWB + // Allow Interface::Ble functions to access private methods + friend size_t Interface::Ble::GetMaxSessions(); + friend ProtocolVersion Interface::Ble::GetProtocolVersion(ConnectionHandle handle); + // Allow Interface::Session functions to access private methods + friend AliroError Interface::Session::Send(ConnectionHandle handle, Data data); + friend void Interface::Session::HandleTermination(ConnectionHandle handle); +#endif // CONFIG_DOOR_LOCK_BLE_UWB + +public: + /** + * @brief Get the instance of the BLE manager. + * + * @return The instance of the BLE manager. + */ + static BleManager &Instance() + { + static BleManager sInstance; + return sInstance; + } + + /** + * @brief Initialize the BLE manager and Bluetooth stack. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError Init(); + + /** + * @brief Start BLE advertising with Aliro service data. + * + * @param data The Aliro advertising service data. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError StartAdvertising(const BleTypes::AdvertisingServiceData &data); + + /** + * @brief Start BLE advertising with custom service data. + * + * @param data The raw service data (e.g., UUID). + * @param type The advertising data field type. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError StartAdvertising(const ConstData &data, BleTypes::AdvertisingDataFieldType type); + + /** + * @brief Stop BLE advertising. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError StopAdvertising(); + + /** + * @brief Disconnect all active BLE connections. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError DisconnectAll(); + + /** + * @brief Update BLE advertising data with Aliro service data while advertising is active. + * + * @param data The updated Aliro advertising service data. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError UpdateAdvertisingData(const BleTypes::AdvertisingServiceData &data); + + /** + * @brief Get the current BLE address. + * + * @param address The structure that will be filled with the current BLE address. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError GetAddress(BleTypes::BleAddress &address) const; + + /** + * @brief Get the current TX power level. + * + * @param txPowerLevel The structure that will be filled with the current TX power level. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError GetTxPowerLevel(BleTypes::TxPowerLevel &txPowerLevel) const; + +private: + enum class BleManagerState : uint8_t { Uninitialized, Initialized, Advertising }; + + BleManager() = default; + ~BleManager() = default; + BleManager(const BleManager &) = delete; + BleManager &operator=(const BleManager &) = delete; + BleManager(BleManager &&) = delete; + BleManager &operator=(BleManager &&) = delete; + + // Interface::Ble implementation methods (accessed via friend functions) + /** + * @brief Send data over an established BLE connection. + */ + AliroError Send(ConnectionHandle handle, Data data) const; + /** + * @brief Get the maximum number of concurrent BLE sessions. + */ + size_t GetMaxSessions() const; + /** + * @brief Get the protocol version for a connection. + */ + ProtocolVersion GetProtocolVersion(ConnectionHandle handle) const; + /** + * @brief Terminate a BLE connection (called by stack to disconnect). + */ + AliroError Terminate(ConnectionHandle handle); + + // Bluetooth connection callbacks + void Connected(bt_conn *connId, uint8_t error); + void Disconnected(bt_conn *connId, uint8_t reason); + void Recycled(); +#ifdef CONFIG_BT_SMP + void SecurityChanged(bt_conn *connId, bt_security_t level, enum bt_security_err error); +#endif // CONFIG_BT_SMP + + int CreateRandomStaticAddress(); + [[maybe_unused]] AliroError GetRandomStaticAddress(); + void ResumeAdvertising(); + void ResumeAdvertisingHandler(); + AliroError RestartAdvertising(); + AliroError SetAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type); + AliroError UpdateAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type); + + // currently not used + [[maybe_unused]] bt_ready_cb_t mReadyCb{ nullptr }; + + bt_conn_cb mConnCallbacks{}; + bt_addr_le_t mAddress{}; + [[maybe_unused]] k_work mAdvResumeWork{}; + + static constexpr uint8_t kAdvertisingFlags{ BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR }; + static constexpr uint32_t kIntervalMin{ BT_GAP_ADV_FAST_INT_MIN_2 }; + static constexpr uint32_t kIntervalMax{ BT_GAP_ADV_FAST_INT_MAX_2 }; + static constexpr uint32_t kAdvertisingOptions{ BT_LE_ADV_OPT_CONN }; + + // Legacy BLE ADV packets are limited to 31 bytes, 2 bytes are reserved flags (1 byte for flags, 1 byte for + // service data length). There are 29 bytes left for advertising data. + static constexpr size_t kMaxAdvertisingDataSize{ 29 }; + std::array mAdvertisingServiceData{}; + uint8_t mAdvertisingServiceDataSize{}; + BleTypes::AdvertisingDataFieldType mAdvertisingDataFieldType{ BleTypes::AdvertisingDataFieldType::Uuid16 }; + +#ifdef CONFIG_DOOR_LOCK_BLE_UWB + GattServer mGattServer{}; +#endif // CONFIG_DOOR_LOCK_BLE_UWB + + DoorLock::Interface::BleAdvertisingArbiter::Request mBleAdvertisingRequest{}; + + size_t mConnectionCount{}; + + BleManagerState mState{ BleManagerState::Uninitialized }; + bool IsInitialized() const { return mState >= BleManagerState::Initialized; } + bool IsAdvertising() const { return mState == BleManagerState::Advertising; } + + void SetState(BleManagerState state) { mState = state; } +}; + +} // namespace Aliro diff --git a/app/src/aliro/platform/ble/ble_manager_impl.h b/app/src/aliro/platform/ble/ble_manager_impl.h deleted file mode 100644 index 5b782fef..00000000 --- a/app/src/aliro/platform/ble/ble_manager_impl.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -#include "aliro/ble_types.h" -#include "aliro/transport_callbacks.h" - -#ifdef CONFIG_DOOR_LOCK_BLE_UWB -#include "gatt_server/gatt_server.h" -#endif // CONFIG_DOOR_LOCK_BLE_UWB - -#include "transport/ble/ble_iface.h" - -#include -#include -#include - -#include - -namespace Aliro::BleInterface { - -class BleManagerImpl : public BleIfc { -public: - static BleManagerImpl &Instance() - { - static BleManagerImpl sInstance; - return sInstance; - } - - AliroError Init(const PlatformTransportCallbacks &callbacks) override; - - AliroError Send(ConnectionHandle handle, Data data) const override; - - AliroError Disconnect(ConnectionHandle handle) override; - - void DisconnectAll() override; - - AliroError StartAdvertising(const ConstData &data, BleTypes::AdvertisingDataFieldType type) override; - AliroError UpdateAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type) override; - AliroError StopAdvertising() override; - - AliroError GetTxPowerLevel(BleTypes::TxPowerLevel &txPowerLevel) const override; - AliroError GetAddress(BleTypes::BleAddress &address) const override; - - size_t GetMaxSessions() const override; - - ProtocolVersion GetProtocolVersion(ConnectionHandle handle) const override; - -private: - enum class BleManagerState : uint8_t { Uninitialized, Initialized, Advertising }; - - BleManagerImpl() = default; - ~BleManagerImpl() final = default; - BleManagerImpl(const BleManagerImpl &) = delete; - BleManagerImpl &operator=(const BleManagerImpl &) = delete; - BleManagerImpl(BleManagerImpl &&) = delete; - BleManagerImpl &operator=(BleManagerImpl &&) = delete; - - // Bluetooth connection callbacks - void Connected(bt_conn *connId, uint8_t error); - void Disconnected(bt_conn *connId, uint8_t reason); - void Recycled(); -#ifdef CONFIG_BT_SMP - void SecurityChanged(bt_conn *connId, bt_security_t level, enum bt_security_err error); -#endif // CONFIG_BT_SMP - - int CreateRandomStaticAddress(); - [[maybe_unused]] AliroError GetRandomStaticAddress(); - void ResumeAdvertising(); - void ResumeAdvertisingHandler(); - AliroError StartAdvertising(); - AliroError SetAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type); - - // currently not used - [[maybe_unused]] bt_ready_cb_t mReadyCb{ nullptr }; - - bt_conn_cb mConnCallbacks{}; - bt_addr_le_t mAddress{}; - [[maybe_unused]] k_work mAdvResumeWork{}; - - static constexpr size_t kAdvertisingDataSize{ 2 }; - static constexpr size_t kScanResponseSize{ 1 }; - static constexpr uint32_t kAdvertisingPriority{ 1 }; - static constexpr uint8_t kAdvertisingFlags{ BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR }; - static constexpr uint32_t kIntervalMin{ BT_GAP_ADV_FAST_INT_MIN_2 }; - static constexpr uint32_t kIntervalMax{ BT_GAP_ADV_FAST_INT_MAX_2 }; - static constexpr uint32_t kAdvertisingOptions{ BT_LE_ADV_OPT_CONN }; - static constexpr size_t kAdvertisingFlagsIndex{ 0 }; - static constexpr size_t kAdvertisingServiceDataIndex{ 1 }; - static constexpr size_t kScanResponseIndex{ 0 }; - - // Legacy BLE ADV packets are limited to 31 bytes, 2 bytes are reserved flags (1 byte for flags, 1 byte for - // service data length). There are 29 bytes left for advertising data. - static constexpr size_t kMaxAdvertisingDataSize{ 29 }; - std::array mAdvertisingServiceData{}; - uint8_t mAdvertisingServiceDataSize{}; - BleTypes::AdvertisingDataFieldType mAdvertisingDataFieldType{ BleTypes::AdvertisingDataFieldType::Uuid16 }; - -#ifdef CONFIG_DOOR_LOCK_BLE_UWB - GattServer mGattServer{}; -#endif // CONFIG_DOOR_LOCK_BLE_UWB - - using AdvertisingData = std::array; - using ScanResponseData = std::array; - AdvertisingData mAdvertisingData{}; - ScanResponseData mScanResponseData{}; - - size_t mConnectionCount{}; - - PlatformTransportCallbacks mTransportCallbacks{}; - - BleManagerState mState{ BleManagerState::Uninitialized }; - bool IsInitialized() const { return mState >= BleManagerState::Initialized; } - bool IsAdvertising() const { return mState == BleManagerState::Advertising; } - - void SetState(BleManagerState state) { mState = state; } -}; - -} // namespace Aliro::BleInterface diff --git a/app/src/aliro/platform/ble/gatt_server/gatt_server.cpp b/app/src/aliro/platform/ble/gatt_server/gatt_server.cpp index bd8682e4..034d867c 100644 --- a/app/src/aliro/platform/ble/gatt_server/gatt_server.cpp +++ b/app/src/aliro/platform/ble/gatt_server/gatt_server.cpp @@ -86,12 +86,11 @@ ssize_t GattServer::UserDeviceSelectedSpsmBleUwbProtocolversion(bt_conn *connect VerifyOrReturnValue(versionFound, dataLength, LOG_DBG("Unsupported protocol version: 0x%04x", version)); - const auto *l2capServerPtr = static_cast(attribute->user_data); + auto *l2capServerPtr = static_cast(attribute->user_data); VerifyOrReturnValue(l2capServerPtr, BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE), LOG_ERR("Invalid L2CAP server pointer")); - // Ignore the error code, as it is not critical. - std::ignore = l2capServerPtr->AllocateL2capChannel(connectionId, version); + l2capServerPtr->SetProtocolVersion(connectionId, version); return dataLength; } diff --git a/app/src/aliro/platform/ble/l2cap_server/l2cap_server.cpp b/app/src/aliro/platform/ble/l2cap_server/l2cap_server.cpp index 38baca1b..6bc71911 100644 --- a/app/src/aliro/platform/ble/l2cap_server/l2cap_server.cpp +++ b/app/src/aliro/platform/ble/l2cap_server/l2cap_server.cpp @@ -8,8 +8,8 @@ #include "aliro/aliro.h" #include "aliro/memory.h" -#include "aliro/mutex_guard.h" #include "aliro/utils.h" +#include "mutex_guard.h" #include LOG_MODULE_REGISTER(L2CAPServer, CONFIG_DOOR_LOCK_BLE_LOG_LEVEL); @@ -21,7 +21,6 @@ struct L2CapChanNode { bt_conn *conn{ nullptr }; bt_l2cap_le_chan chan{}; - Aliro::ProtocolVersion bleUwbProtocolVersion{}; }; static_assert(offsetof(L2CapChanNode, node) == 0); @@ -35,7 +34,7 @@ NET_BUF_POOL_DEFINE(sNetBufPool, CONFIG_DOOR_LOCK_BLE_UWB_MAX_SESSIONS, BT_L2CAP K_MUTEX_DEFINE(sL2CapChanMutex); sys_slist_t sL2CapChanList{}; -bt_l2cap_le_chan *AllocateNewL2capChan(bt_conn *conn, Aliro::ProtocolVersion version) +bt_l2cap_le_chan *AllocateNewL2capChan(bt_conn *conn) { auto *ref = bt_conn_ref(conn); VerifyOrReturnValue(ref, nullptr, LOG_ERR("Cannot reference connection (conn: %p)", conn)); @@ -44,7 +43,6 @@ bt_l2cap_le_chan *AllocateNewL2capChan(bt_conn *conn, Aliro::ProtocolVersion ver VerifyOrReturnValue(node, nullptr, LOG_ERR("Cannot allocate channel node (conn: %p)", conn)); node->conn = ref; - node->bleUwbProtocolVersion = version; { MutexGuard lock{ sL2CapChanMutex }; @@ -98,12 +96,11 @@ int L2capServer::Accept(bt_conn *conn, bt_l2cap_server *server, bt_l2cap_chan ** VerifyOrReturnValue(Instance().mChannelCount < CONFIG_DOOR_LOCK_BLE_UWB_MAX_SESSIONS, -ENOMEM, LOG_ERR("Too many connections")); - auto *l2capChan = GetL2capChan(conn); - VerifyOrReturnValue(l2capChan, -ENOENT, LOG_ERR("Cannot get allocated channel (conn: %p)", conn)); - - VerifyOrReturnValue(Instance().GetBleUwbProtocolVersion(conn) != BleTypes::kInvalidProtocolVersion, -ENOENT, + const auto protocolVersion = Instance().GetBleUwbProtocolVersion(conn); + VerifyOrReturnValue(protocolVersion != BleTypes::kInvalidProtocolVersion, -ENOENT, LOG_ERR("BLE UWB protocol version is not set(conn: %p)", conn)); + auto *l2capChan = AllocateNewL2capChan(conn); l2capChan->chan.ops = &Instance().mChannelCallbacks; *channel = &l2capChan->chan; @@ -129,6 +126,8 @@ void L2capServer::Disconnected(bt_l2cap_chan *channel) LOG_INF("L2CAP disconnected: %p", channel); VerifyAndCall(Instance().mCallbacks.mOnDisconnected, channel->conn); + + Instance().SetProtocolVersion(channel->conn, BleTypes::kInvalidProtocolVersion); } int L2capServer::DataReceived(bt_l2cap_chan *channel, net_buf *buffer) @@ -175,44 +174,16 @@ bool L2capServer::IsValidDynamicSpsm(Spsm spsm) return IN_RANGE(spsm, kL2capSpsmMin, kL2capSpsmMax); } -AliroError L2capServer::AllocateL2capChannel(bt_conn *conn, ProtocolVersion version) const -{ - VerifyOrReturnValue(conn, ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid connection")); - VerifyOrExit(GetL2capChan(conn) == nullptr, LOG_DBG("Channel already allocated (conn: %p)", conn)); - - VerifyOrReturnValue(AllocateNewL2capChan(conn, version) != nullptr, ALIRO_NO_MEMORY, - LOG_ERR("Cannot allocate channel (conn: %p)", conn)); - -exit: - return ALIRO_NO_ERROR; -} - -void L2capServer::FreeL2capChannel(bt_conn *conn) const +void L2capServer::SetProtocolVersion(bt_conn *conn, ProtocolVersion protocolVersion) { - VerifyOrReturn(conn, LOG_ERR("Invalid connection")); - - auto *chan = GetL2capChan(conn); - VerifyOrReturn(chan, LOG_DBG("Channel not allocated or already freed (conn: %p)", conn)); - - FreeL2capChan(chan); + const auto index = bt_conn_index(conn); + mConnectionProtocolVersion[index] = protocolVersion; } ProtocolVersion L2capServer::GetBleUwbProtocolVersion(bt_conn *conn) const { - sys_snode_t *node{ nullptr }; - - { - MutexGuard lock{ sL2CapChanMutex }; - - SYS_SLIST_FOR_EACH_NODE (&sL2CapChanList, node) { - const auto *nodeObj = CONTAINER_OF(node, L2CapChanNode, node); - if (nodeObj->conn == conn) { - return nodeObj->bleUwbProtocolVersion; - } - } - } - - return BleTypes::kInvalidProtocolVersion; + const auto index = bt_conn_index(conn); + return mConnectionProtocolVersion[index]; } AliroError L2capServer::Send(bt_conn *conn, const uint8_t *data, size_t length) const diff --git a/app/src/aliro/platform/ble/l2cap_server/l2cap_server.h b/app/src/aliro/platform/ble/l2cap_server/l2cap_server.h index 3dcb859b..13cc7a63 100644 --- a/app/src/aliro/platform/ble/l2cap_server/l2cap_server.h +++ b/app/src/aliro/platform/ble/l2cap_server/l2cap_server.h @@ -68,28 +68,15 @@ class L2capServer { AliroError Init(); /** - * @brief Allocates a new L2CAP channel for the given connection. + * @brief Sets the BLE UWB protocol version for the given connection. * - * This function allocates a new L2CAP channel that will be used when the L2CAP connection is accepted. - * The channel must be pre-allocated after protocol BLE UWB version validation via GATT. Additionally, the - * channel is pre-allocated for the given protocol version. - * If the channel is already pre-allocated, this function returns success without re-allocating. + * This function stores the protocol version negotiated for the given + * connection. The version can be retrieved later with GetBleUwbProtocolVersion(). * - * @param conn The connection to preallocate the channel for. - * @param version The BLE UWB protocol version to use for the channel. - * - * @return ALIRO_NO_ERROR on success, or an error code on failure. - */ - AliroError AllocateL2capChannel(bt_conn *conn, ProtocolVersion version) const; - - /** - * @brief Frees a L2CAP channel for the given connection. - * - * This function frees a L2CAP channel that was previously allocated using the AllocateL2capChannel function. - * - * @param conn The connection to free the L2CAP channel for. + * @param conn The connection to set the BLE UWB protocol version for. + * @param protocolVersion The protocol version to store. */ - void FreeL2capChannel(bt_conn *conn) const; + void SetProtocolVersion(bt_conn *conn, ProtocolVersion protocolVersion); /** * @brief Gets the BLE UWB protocol version for the given connection. @@ -179,6 +166,7 @@ class L2capServer { static void Released(bt_l2cap_chan *channel); Spsm mSpsm{}; + ProtocolVersion mConnectionProtocolVersion[CONFIG_BT_MAX_CONN]{}; Callbacks mCallbacks{}; diff --git a/app/src/aliro/access_manager_impl_default/CMakeLists.txt b/app/src/aliro/platform/crypto/CMakeLists.txt similarity index 58% rename from app/src/aliro/access_manager_impl_default/CMakeLists.txt rename to app/src/aliro/platform/crypto/CMakeLists.txt index dd7be26e..0c6e9191 100644 --- a/app/src/aliro/access_manager_impl_default/CMakeLists.txt +++ b/app/src/aliro/platform/crypto/CMakeLists.txt @@ -1,11 +1,11 @@ # -# Copyright (c) 2025 Nordic Semiconductor ASA +# Copyright (c) 2026 Nordic Semiconductor ASA # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # zephyr_include_directories(.) -file(GLOB src CONFIGURE_DEPENDS ./*.cpp) +file(GLOB src CONFIGURE_DEPENDS *.cpp) zephyr_library_sources(${src}) diff --git a/app/src/aliro/platform/crypto/Kconfig b/app/src/aliro/platform/crypto/Kconfig new file mode 100644 index 00000000..02ddd254 --- /dev/null +++ b/app/src/aliro/platform/crypto/Kconfig @@ -0,0 +1,27 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +rsource "Kconfig.defaults" + +menu "Crypto" + +config DOOR_LOCK_CRYPTO_PSA_AEAD_SINGLE_PART + bool + default y if SOC_NRF54LM20A + help + When enabled, the single-part AEAD API is used. + +if DOOR_LOCK_CRYPTO_PSA_AEAD_SINGLE_PART + +config DOOR_LOCK_CRYPTO_PSA_AEAD_SINGLE_PART_BUFFER_SIZE + int "Maximum size of the buffer used for the single-part AEAD API" + default 256 + help + Maximum size of the buffer used for the single-part AEAD API. + +endif # DOOR_LOCK_CRYPTO_PSA_AEAD_SINGLE_PART + +endmenu # Crypto diff --git a/app/src/aliro/platform/crypto/Kconfig.defaults b/app/src/aliro/platform/crypto/Kconfig.defaults new file mode 100644 index 00000000..4fc590a1 --- /dev/null +++ b/app/src/aliro/platform/crypto/Kconfig.defaults @@ -0,0 +1,60 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config MBEDTLS_PSA_CRYPTO_C + default y + +config MBEDTLS_ENABLE_HEAP + default y + +config MBEDTLS_HEAP_SIZE + default 8192 + +config MBEDTLS_X509_LIBRARY + default y if DOOR_LOCK_CREDENTIAL_ISSUER_CA + +config PSA_WANT_ALG_ECDSA + default y + +config PSA_WANT_ALG_ECDH + default y + +config PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE + default y + +config PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT + default y + +config PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT + default y + +config PSA_WANT_ECC_SECP_R1_256 + default y + +config PSA_WANT_ALG_SHA_256 + default y + +config PSA_WANT_ALG_HKDF + default y + +config PSA_WANT_ALG_HMAC + default y + +config PSA_WANT_KEY_TYPE_HMAC + default y + +config PSA_WANT_KEY_TYPE_AES + default y + +config PSA_WANT_ALG_ECB_NO_PADDING + default y if NCS_ALIRO_BLE_UWB + +config PSA_WANT_GENERATE_RANDOM + default y + +config PSA_WANT_ALG_GCM + default y + diff --git a/app/src/aliro/platform/crypto/utils.cpp b/app/src/aliro/platform/crypto/utils.cpp new file mode 100644 index 00000000..247b679b --- /dev/null +++ b/app/src/aliro/platform/crypto/utils.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "utils.h" + +#include "aliro/utils.h" + +#include +#include + +LOG_MODULE_REGISTER(crypto_utils, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); + +using namespace Aliro; + +namespace DoorLock::Crypto { + +namespace { + +psa_key_attributes_t GetPrivateKeyAttributes() +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + psa_set_key_bits(&attributes, PSA_BYTES_TO_BITS(CryptoTypes::kEccP256KeyPrivateKeyLength)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_HASH); + + return attributes; +} + +psa_key_attributes_t GetPublicKeyAttributes() +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + psa_set_key_bits(&attributes, PSA_BYTES_TO_BITS(CryptoTypes::kEccP256KeyPrivateKeyLength)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_VERIFY_HASH); + + return attributes; +} + +psa_key_attributes_t GetGroupResolvingKeyAttributes() +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); + psa_set_key_algorithm(&attributes, PSA_ALG_ECB_NO_PADDING); + psa_set_key_bits(&attributes, PSA_BYTES_TO_BITS(CryptoTypes::kGroupResolvingKeyLength)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); + + return attributes; +} + +AliroError SetPersistentLifetime(psa_key_attributes_t &attributes, CryptoTypes::KeyId keyId) +{ + VerifyOrReturnStatus(IN_RANGE(keyId, PSA_KEY_ID_USER_MIN, PSA_KEY_ID_USER_MAX), ALIRO_INVALID_ARGUMENT, + LOG_WRN("Key ID is not defined")); + psa_set_key_id(&attributes, keyId); + + return ALIRO_NO_ERROR; +} + +AliroError ImportKey(const uint8_t *key, size_t keyLength, psa_key_attributes_t &attributes, bool persistent, + CryptoTypes::KeyId &keyId) +{ + if (persistent) { + ReturnErrorOnFailure(SetPersistentLifetime(attributes, keyId)); + } + + const auto status = psa_import_key(&attributes, key, keyLength, &keyId); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Could not import key [Error: %d]", status)); + + return ALIRO_NO_ERROR; +} + +} // namespace + +AliroError Init() +{ + /* Initialize PSA Crypto */ + psa_status_t status = psa_crypto_init(); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_ERR("Cannot initialize PSA Crypto! (Error: %d)", status)); + + return ALIRO_NO_ERROR; +} + +AliroError ImportPrivateKey(const CryptoTypes::PrivateKey &privateKey, bool persistent, CryptoTypes::KeyId &keyId) +{ + auto attributes = GetPrivateKeyAttributes(); + return ImportKey(privateKey.data(), privateKey.size(), attributes, persistent, keyId); +} + +AliroError ImportPublicKey(const CryptoTypes::PublicKey &publicKey, bool persistent, CryptoTypes::KeyId &keyId) +{ + auto attributes = GetPublicKeyAttributes(); + return ImportKey(publicKey.data(), publicKey.size(), attributes, persistent, keyId); +} + +AliroError ImportGroupResolvingKey(const CryptoTypes::GroupResolvingKey &groupResolvingKey, bool persistent, + CryptoTypes::KeyId &keyId) +{ + auto attributes = GetGroupResolvingKeyAttributes(); + return ImportKey(groupResolvingKey.data(), groupResolvingKey.size(), attributes, persistent, keyId); +} + +AliroError ExportPublicKey(CryptoTypes::KeyId keyId, CryptoTypes::PublicKey &publicKey) +{ + size_t outputLength{}; + psa_status_t status = psa_export_public_key(keyId, publicKey.data(), publicKey.size(), &outputLength); + + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_PUBLIC_KEY_NOT_FOUND, + LOG_WRN("Cannot export public key with ID: 0x%x [Error: %d]", keyId, status)); + VerifyOrReturnStatus(outputLength == publicKey.size(), ALIRO_INVALID_ARGUMENT, + LOG_WRN("Invalid public key length")); + + return ALIRO_NO_ERROR; +} + +AliroError ExportKey(CryptoTypes::KeyId keyId, uint8_t *key, size_t keyLength) +{ + VerifyOrReturnStatus(keyId != PSA_KEY_ID_NULL, ALIRO_INVALID_ARGUMENT, LOG_WRN("Invalid Key ID")); + + size_t outputLength{}; + psa_status_t status = psa_export_key(keyId, key, keyLength, &outputLength); + + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Cannot export key ID: 0x%x [Error: %d]", keyId, status)); + VerifyOrReturnStatus(outputLength == keyLength, ALIRO_ERROR_INTERNAL, LOG_WRN("Invalid key length")); + + return ALIRO_NO_ERROR; +} + +AliroError PreserveKey(CryptoTypes::KeyId volatileKeyId, CryptoTypes::KeyId persistentKeyId) +{ + // Validate destination key ID range + VerifyOrReturnStatus(IN_RANGE(persistentKeyId, PSA_KEY_ID_USER_MIN, PSA_KEY_ID_USER_MAX), + ALIRO_INVALID_ARGUMENT, + LOG_WRN("Destination key ID 0x%x is outside valid range [0x%x, 0x%x]", persistentKeyId, + PSA_KEY_ID_USER_MIN, PSA_KEY_ID_USER_MAX)); + + psa_key_attributes_t attributes{}; + psa_status_t status = psa_get_key_attributes(volatileKeyId, &attributes); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_DBG("Cannot get key attributes [Error: %d]", status)); + + psa_set_key_id(&attributes, persistentKeyId); + + CryptoTypes::KeyId keyId{}; + status = psa_copy_key(volatileKeyId, &attributes, &keyId); + if (status == PSA_ERROR_ALREADY_EXISTS) { + LOG_INF("Key already exists [Status: %d]", status); + return ALIRO_KEY_ALREADY_EXISTS; + } + + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_WRN("Cannot copy key [Error: %d]", status)); + + VerifyOrReturnStatus(keyId == persistentKeyId, ALIRO_ERROR_INTERNAL, LOG_WRN("Bad key ID")); + + return ALIRO_NO_ERROR; +} + +AliroError DestroyKey(CryptoTypes::KeyId &keyId) +{ + psa_status_t status = psa_destroy_key(keyId); + AliroError error = (status == PSA_SUCCESS) ? ALIRO_NO_ERROR : ALIRO_ERROR_INTERNAL; + VerifyOrReturnStatus(error == ALIRO_NO_ERROR, error, LOG_ERR("Cannot destroy key [Error: %d]", status)); + keyId = 0; + + return error; +} + +AliroError IsKeyAvailable(CryptoTypes::KeyId keyId) +{ + VerifyOrReturnStatus(keyId != PSA_KEY_ID_NULL, ALIRO_INVALID_ARGUMENT, LOG_WRN("Key ID is not defined")); + + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_status_t status = psa_get_key_attributes(keyId, &attributes); + VerifyOrReturnStatus(status == PSA_SUCCESS, ALIRO_ERROR_INTERNAL, + LOG_DBG("Cannot get key attributes [Error: %d]", status)); + + return ALIRO_NO_ERROR; +} + +AliroError VerifySignature(CryptoTypes::KeyId keyId, const uint8_t *msg, const size_t msgLength, + const CryptoTypes::Signature &signature) +{ + VerifyOrReturnStatus(msg && msgLength > 0, ALIRO_INVALID_ARGUMENT); + + psa_status_t ec = psa_verify_message(keyId, PSA_ALG_ECDSA(PSA_ALG_SHA_256), msg, msgLength, signature.data(), + signature.size()); + + return (ec == PSA_SUCCESS) ? ALIRO_NO_ERROR : ALIRO_INVALID_SIGNATURE; +} + +} // namespace DoorLock::Crypto diff --git a/app/src/aliro/platform/crypto/utils.h b/app/src/aliro/platform/crypto/utils.h new file mode 100644 index 00000000..44684a8f --- /dev/null +++ b/app/src/aliro/platform/crypto/utils.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include "aliro/errors.h" +#include "aliro/types.h" + +namespace DoorLock::Crypto { + +/** + * @brief Initialize the crypto module. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError Init(); + +/** + * @brief Import a private key. + * + * @param privateKey input buffer with the private key. + * @param persistent true if the key is persistent, false otherwise. + * @param keyId output identifier of the imported key. Has to be set to a valid key ID if the key is persistent. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError ImportPrivateKey(const Aliro::CryptoTypes::PrivateKey &privateKey, bool persistent, + Aliro::CryptoTypes::KeyId &keyId); + +/** + * @brief Import a public key. + * + * @param publicKey input buffer with the public key. + * @param persistent true if the key is persistent, false otherwise. + * @param keyId output identifier of the imported key. Has to be set to a valid key ID if the key is persistent. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError ImportPublicKey(const Aliro::CryptoTypes::PublicKey &publicKey, bool persistent, + Aliro::CryptoTypes::KeyId &keyId); + +/** + * @brief Import a Group Resolving Key. + * + * @param groupResolvingKey input buffer with the Group Resolving Key. + * @param persistent true if the key is persistent, false otherwise. + * @param keyId output identifier of the imported key. Has to be set to a valid key ID if the key is persistent. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError ImportGroupResolvingKey(const Aliro::CryptoTypes::GroupResolvingKey &groupResolvingKey, bool persistent, + Aliro::CryptoTypes::KeyId &keyId); + +/** + * @brief Export EC public key. + * + * @param keyId input identifier of a private key for which the public key should be exported. + * @param publicKey Output buffer where the public key is to be copied. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError ExportPublicKey(Aliro::CryptoTypes::KeyId keyId, Aliro::CryptoTypes::PublicKey &publicKey); + +/** + * @brief Export a key. + * + * @note The caller must know the key type associated with the given keyId. + * The size of the buffer must match the key size for that type. + * + * @param keyId input identifier of a key to export. + * @param key output buffer where the key is to be copied. + * @param keyLength length of the key in bytes. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError ExportKey(Aliro::CryptoTypes::KeyId keyId, uint8_t *key, size_t keyLength); + +/** + * @brief Preserve a volatile key by copying it to persistent slot. + * + * @param volatileKeyId input identifier of the volatile key. + * @param persistentKeyId input identifier of the persistent key. + * + * @return ALIRO_NO_ERROR on success, ALIRO_KEY_ALREADY_EXISTS if the key already exists, other error status + * otherwise. + */ +AliroError PreserveKey(Aliro::CryptoTypes::KeyId volatileKeyId, Aliro::CryptoTypes::KeyId persistentKeyId); + +/** + * @brief Destroy an key by ID. + * + * @note When the key is successfully destroyed, keyId is set to 0. + * + * @param keyId identifier of a key to delete. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError DestroyKey(Aliro::CryptoTypes::KeyId &keyId); + +/** + * @brief Checks if a key ID exists. + * + * @param keyId The key ID to check. + * + * @return ALIRO_NO_ERROR if the key is available, a error code otherwise. + */ +AliroError IsKeyAvailable(Aliro::CryptoTypes::KeyId keyId); + +/** + * @brief Verify signature of a message. + * + * @param keyId input identifier of the public key to use for verification. + * @param msg input message whose signature is to be verified. + * @param msgLength input size of the message. + * @param signature input signature to verify. + * + * @return ALIRO_NO_ERROR when signature is valid, ALIRO_INVALID_SIGNATURE otherwise. + */ +AliroError VerifySignature(Aliro::CryptoTypes::KeyId keyId, const uint8_t *msg, const size_t msgLength, + const Aliro::CryptoTypes::Signature &signature); + +} // namespace DoorLock::Crypto diff --git a/lib/aliro/include/aliro/mutex_guard.h b/app/src/aliro/platform/mutex_guard.h similarity index 79% rename from lib/aliro/include/aliro/mutex_guard.h rename to app/src/aliro/platform/mutex_guard.h index f8aa95c2..e5174d00 100644 --- a/lib/aliro/include/aliro/mutex_guard.h +++ b/app/src/aliro/platform/mutex_guard.h @@ -8,6 +8,8 @@ #include +#include + /** * @brief RAII-style mutex guard. * @@ -20,14 +22,17 @@ struct MutexGuard { * * @param mutex Pointer to the mutex to lock. */ - explicit MutexGuard(k_mutex &mutex) noexcept; + explicit MutexGuard(k_mutex &mutex) noexcept : mMutex{ mutex } + { + std::ignore = k_mutex_lock(&mMutex, K_FOREVER); + } /** * @brief Destructor for MutexGuard. * * Unlocks the mutex when the MutexGuard object is destroyed. */ - ~MutexGuard() noexcept; + ~MutexGuard() noexcept { k_mutex_unlock(&mMutex); } // Disable copy and move constructors and assignment operators. MutexGuard(const MutexGuard &) = delete; @@ -36,5 +41,5 @@ struct MutexGuard { MutexGuard &operator=(MutexGuard &&) = delete; private: - k_mutex *const mMutex{ nullptr }; + k_mutex &mMutex; }; diff --git a/app/src/aliro/platform/nfc_transport_impl/CMakeLists.txt b/app/src/aliro/platform/nfc/CMakeLists.txt similarity index 72% rename from app/src/aliro/platform/nfc_transport_impl/CMakeLists.txt rename to app/src/aliro/platform/nfc/CMakeLists.txt index 4cc34d51..e7f635c6 100644 --- a/app/src/aliro/platform/nfc_transport_impl/CMakeLists.txt +++ b/app/src/aliro/platform/nfc/CMakeLists.txt @@ -8,5 +8,3 @@ zephyr_include_directories(.) # Core RFAL transport implementation zephyr_library_sources(nfc_transport_rfal.cpp) - -zephyr_library_sources_ifdef(CONFIG_DOOR_LOCK_NFC_PROP nfc_transport_rfal_prop.cpp) diff --git a/app/src/aliro/platform/nfc_transport_impl/Kconfig b/app/src/aliro/platform/nfc/Kconfig similarity index 96% rename from app/src/aliro/platform/nfc_transport_impl/Kconfig rename to app/src/aliro/platform/nfc/Kconfig index ca5b9905..00d5ade4 100644 --- a/app/src/aliro/platform/nfc_transport_impl/Kconfig +++ b/app/src/aliro/platform/nfc/Kconfig @@ -6,8 +6,10 @@ rsource "Kconfig.rfal.defconfig" -# Prompt-less option only for internal tuning of the RFAL worker thread. Do not change it. -config RFAL_NFC_WORKER_TIMEOUT_MS +menu "NFC" + +# Prompt-less option only for internal tuning of the RFAL worker. +config RFAL_NFC_WORKER_INTERVAL_MS int default 10 @@ -68,3 +70,5 @@ module-str = DOOR_LOCK_RFAL module-dep = LOG module-help = Enables Door Lock NFC RFAL log messages. source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endmenu # NFC diff --git a/app/src/aliro/platform/nfc_transport_impl/Kconfig.rfal.defconfig b/app/src/aliro/platform/nfc/Kconfig.rfal.defconfig similarity index 100% rename from app/src/aliro/platform/nfc_transport_impl/Kconfig.rfal.defconfig rename to app/src/aliro/platform/nfc/Kconfig.rfal.defconfig diff --git a/app/src/aliro/platform/nfc_transport_impl/nfc_transport_rfal.cpp b/app/src/aliro/platform/nfc/nfc_transport_rfal.cpp similarity index 63% rename from app/src/aliro/platform/nfc_transport_impl/nfc_transport_rfal.cpp rename to app/src/aliro/platform/nfc/nfc_transport_rfal.cpp index 873fecf9..a4bb0655 100644 --- a/app/src/aliro/platform/nfc_transport_impl/nfc_transport_rfal.cpp +++ b/app/src/aliro/platform/nfc/nfc_transport_rfal.cpp @@ -5,10 +5,11 @@ */ #include "nfc_transport_rfal.h" -#include "ncs_pal_semaphore.h" #include #include +#include "aliro/aliro.h" +#include "aliro/aliro_work/aliro_work.h" #include "aliro/utils.h" #include "ncs_pal_nfc_worker.h" @@ -20,19 +21,33 @@ LOG_MODULE_REGISTER(nfc_st_rfal_impl, CONFIG_DOOR_LOCK_RFAL_LOG_LEVEL); -namespace Aliro { +K_WORK_DELAYABLE_DEFINE(nfc_pal_nfc_work, [](k_work *) { Aliro::NfcTransportRfal::Instance().Execute(); }); -K_THREAD_STACK_DEFINE(mStack, CONFIG_RFAL_WORKER_THREAD_STACK_SIZE); +extern "C" void ncs_pal_submit_nfc_work() +{ + (void)AliroWorkReschedule(&nfc_pal_nfc_work, K_NO_WAIT); +} +namespace Aliro { -[[noreturn]] void NfcTransportRfal::Run() +void NfcTransportRfal::Execute() { - while (true) { - if (mRecoverPolling && !mSendInProgress) { - mRecoverPolling = false; - RecoverPolling(); - } - rfalNfcWorker(); - ncs_pal_take_semaphore(K_MSEC(CONFIG_RFAL_NFC_WORKER_TIMEOUT_MS)); + // NFC shield generates IRQ on startup, triggering Execute() before RFAL is initialized. + // Without this check, rfalNfcGetState() returns garbage which accidentally satisfies the + // condition below, causing unnecessary periodic worker scheduling at CONFIG_RFAL_NFC_WORKER_INTERVAL_MS. + if (!atomic_get(&mStarted)) { + return; + } + + if (mRecoverPolling && !mSendInProgress) { + mRecoverPolling = false; + RecoverPolling(); + } + rfalNfcWorker(); + + const rfalNfcState st = rfalNfcGetState(); + + if (st != RFAL_NFC_STATE_WAKEUP_MODE || mSendInProgress) { + (void)AliroWorkReschedule(&nfc_pal_nfc_work, K_MSEC(CONFIG_RFAL_NFC_WORKER_INTERVAL_MS)); } } @@ -67,6 +82,7 @@ void NfcTransportRfal::RfalNotifyCallback(rfalNfcState state) LOG_DBG("RFAL: Start discovery state"); mMultiSel = false; mSendInProgress = false; + mTagDetectedState = false; break; case RFAL_NFC_STATE_DATAEXCHANGE: LOG_DBG("RFAL: Data exchange state"); @@ -78,6 +94,7 @@ void NfcTransportRfal::RfalNotifyCallback(rfalNfcState state) case RFAL_NFC_STATE_DEACTIVATION: LOG_DBG("RFAL: Deactivation State"); mSendInProgress = false; + mTagDetectedState = false; break; case RFAL_NFC_STATE_ACTIVATED: LOG_DBG("RFAL: Activated state"); @@ -131,19 +148,20 @@ ReturnCode NfcTransportRfal::RfalNfcInit() return err; } -void NfcTransportRfal::SelectTag() const +void NfcTransportRfal::SelectTag() { rfalNfcDevice *nfcDevice; rfalNfcGetActiveDevice(&nfcDevice); VerifyOrReturn(nfcDevice); - LOG_INF("RFAL: Active device type = %d", nfcDevice->type); + LOG_INF("RFAL: Active device type = %d", static_cast(nfcDevice->type)); if (nfcDevice->type == RFAL_NFC_LISTEN_TYPE_NFCA) { if (nfcDevice->dev.nfca.type == RFAL_NFCA_T4T) { LOG_HEXDUMP_DBG(nfcDevice->nfcid, nfcDevice->nfcidLen, "RFAL: NFCA Passive ISO-DEP device found. UID: "); - VerifyAndCall(this->NfcDriver::mCallbacks.mOnTagDetected); + mTagDetectedState = true; + AliroStack::Instance().CreateSession(ConnectionHandle::Nfc()); } else { LOG_HEXDUMP_DBG(nfcDevice->nfcid, nfcDevice->nfcidLen, "RFAL: Usupported NFC card found. UID: %s"); @@ -156,7 +174,8 @@ void NfcTransportRfal::CaptureRxData() ReturnCode status = rfalNfcDataExchangeGetStatus(); if (status == RFAL_ERR_BUSY) { LOG_ERR("RFAL: Data transaction has not been completed [status: %d]", status); - VerifyAndCall(this->NfcDriver::mCallbacks.mOnError, ALIRO_INVALID_STATE); + mTagDetectedState = false; + AliroStack::Instance().DestroySession(ConnectionHandle::Nfc()); return; } @@ -168,12 +187,18 @@ void NfcTransportRfal::CaptureRxData() LOG_HEXDUMP_DBG(mRxBuffer.data(), currentDataLen, "RFAL: RX data:"); - VerifyAndCall(NfcDriver::mCallbacks.mOnDataReceived, { .mData = mRxBuffer.data(), .mLength = currentDataLen }, - 0); + if (currentDataLen > 0) { + AliroStack::Instance().HandleSessionData(ConnectionHandle::Nfc(), + { .mData = mRxBuffer.data(), .mLength = currentDataLen }); + } else { + mTagDetectedState = false; + AliroStack::Instance().DestroySession(ConnectionHandle::Nfc()); + } } -void NfcTransportRfal::RecoverPolling() const +void NfcTransportRfal::RecoverPolling() { + mTagDetectedState = false; if (rfalNfcIsDevActivated(rfalNfcGetState())) { ReturnCode err = rfalNfcDeactivate(RFAL_NFC_DEACTIVATE_SLEEP); VerifyOrReturn(err == RFAL_ERR_NONE, LOG_ERR("RFAL: Deactivation failed, return code: %d", err)); @@ -182,73 +207,50 @@ void NfcTransportRfal::RecoverPolling() const /* ****************************************************************************** -* IsoDep interface implementation +* Public API ****************************************************************************** */ -AliroError NfcTransportRfal::_Init(IsoDep::Callbacks callbacks) + +AliroError NfcTransportRfal::Init() { - IsoDep::mCallbacks = callbacks; + int err = rfal_ncs_pal_init(); + VerifyOrReturnStatus(err == 0, ALIRO_ERROR_INTERNAL, LOG_ERR("RFAL: NFC PAL init failed %d", err)); + return ALIRO_NO_ERROR; } -AliroError NfcTransportRfal::_PrepareData([[maybe_unused]] Data data) const +AliroError NfcTransportRfal::Start() { - // ISO-DEP layer is implemented internally in the RFAL, no need for special data handling - return ALIRO_ERROR_NOT_IMPLEMENTED; -} + VerifyOrReturnStatus(RfalNfcInit() == RFAL_ERR_NONE, ALIRO_ERROR_INTERNAL, + LOG_ERR("RFAL: NFC initialization failed")); -AliroError NfcTransportRfal::_PrepareRats() const -{ - // RATS is sent by the driver internally as an activation procedure, so we can presume the tag is fully selected - // a this point - return ALIRO_ERROR_NOT_IMPLEMENTED; -} + ReturnCode err = rfalNfcDiscover(&mNfcConfig); + VerifyOrReturnStatus(err == RFAL_ERR_NONE, ALIRO_ERROR_INTERNAL, + LOG_ERR("RFAL: NFC discovery failed, return code: %d", err)); -AliroError NfcTransportRfal::_HandleReceivedData([[maybe_unused]] Data data, [[maybe_unused]] int transferError) const -{ - // No specific processing needed, all ISO-DEP specific data handling happens in driver's internals - return ALIRO_ERROR_NOT_IMPLEMENTED; -} + atomic_set(&mStarted, true); -AliroError NfcTransportRfal::_ReportTimeout() const -{ - // No special handling needed with RFAL - return ALIRO_ERROR_NOT_IMPLEMENTED; -} + // Kickstart worker - at boot IRQ does this, but when starting with delay(provisioning) we need manual trigger + ncs_pal_submit_nfc_work(); -/* Implementation of the generic IsoDep instance getter. */ -IsoDep &IsoDepInstance() -{ - return NfcTransportRfal::Instance(); + return ALIRO_NO_ERROR; } -/* -****************************************************************************** -****************************************************************************** -*/ -/* -****************************************************************************** -* NfcDriver interface implementation -****************************************************************************** -*/ -AliroError NfcTransportRfal::_Init(NfcDriver::Callbacks callbacks) +AliroError NfcTransportRfal::Stop() { - NfcDriver::mCallbacks = callbacks; - - int err = rfal_ncs_pal_init(); - VerifyOrReturnStatus(err == 0, ALIRO_ERROR_INTERNAL, LOG_ERR("RFAL: NFC PAL init failed %d", err)); - - VerifyOrReturnStatus(RfalNfcInit() == RFAL_ERR_NONE, ALIRO_ERROR_INTERNAL, - LOG_ERR("RFAL: NFC initialization failed")); - - const k_tid_t thread = ncs_pal_nfc_worker_start([](void *, void *, void *) { Instance().Run(); }); - VerifyOrReturnStatus(thread, ALIRO_INVALID_STATE, LOG_ERR("RFAL: Cannot spawn the NFC driver thread")); + ReturnCode err = rfalNfcDeactivate(RFAL_NFC_DEACTIVATE_IDLE); + VerifyOrReturnStatus(err == RFAL_ERR_NONE || err == RFAL_ERR_WRONG_STATE, ALIRO_ERROR_INTERNAL, + LOG_ERR("RFAL: NFC deactivation failed, return code: %d", err)); + atomic_clear(&mStarted); return ALIRO_NO_ERROR; } -AliroError NfcTransportRfal::_Send(Data data, [[maybe_unused]] uint32_t maximumFrameDelayTime) +AliroError NfcTransportRfal::Send(Data data) { + VerifyOrReturnStatus(mTagDetectedState, ALIRO_INVALID_STATE, + LOG_WRN("NFC not activated, no data transfer possible")); + LOG_HEXDUMP_DBG(data.mData, data.mLength, "RFAL: TX data:"); mSendInProgress = true; @@ -261,39 +263,10 @@ AliroError NfcTransportRfal::_Send(Data data, [[maybe_unused]] uint32_t maximumF return ALIRO_NO_ERROR; } -AliroError NfcTransportRfal::_NfcOn() const -{ - VerifyOrReturnStatus(NfcTransportRfal::Instance().RfalNfcInit() == RFAL_ERR_NONE, ALIRO_ERROR_INTERNAL, - LOG_ERR("RFAL: NFC initialization failed")); - - ReturnCode err = rfalNfcDiscover(&mNfcConfig); - VerifyOrReturnStatus(err == RFAL_ERR_NONE, ALIRO_ERROR_INTERNAL, - LOG_ERR("RFAL: NFC discovery failed, return code: %d", err)); - - return ALIRO_NO_ERROR; -} - -AliroError NfcTransportRfal::_NfcOff() const -{ - // RFAL handles this internally and knows when the field can be off - return ALIRO_NO_ERROR; -} - -AliroError NfcTransportRfal::_RestartPolling() +AliroError NfcTransportRfal::Terminate() { mRecoverPolling = true; - return ALIRO_NO_ERROR; } -/* Implementation of the generic NfcDriver instance getter. */ -NfcDriver &NfcDriverInstance() -{ - return NfcTransportRfal::Instance(); -} -/* -****************************************************************************** -****************************************************************************** -*/ - } // namespace Aliro diff --git a/app/src/aliro/platform/nfc/nfc_transport_rfal.h b/app/src/aliro/platform/nfc/nfc_transport_rfal.h new file mode 100644 index 00000000..678f17cc --- /dev/null +++ b/app/src/aliro/platform/nfc/nfc_transport_rfal.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include "aliro/errors.h" +#include "aliro/types.h" + +extern "C" { +#include +#include +#include +} + +#include +#include + +#include + +namespace Aliro { + +/** + * @class NfcTransportRfal + * @brief NFC transport implementation using ST RFAL library. + * + */ +class NfcTransportRfal { +public: + /** + * @brief Gets the singleton instance. + * @return Reference to the singleton instance. + */ + static NfcTransportRfal &Instance() + { + static NfcTransportRfal sInstance; + return sInstance; + } + + /** + * @brief Initializes the NFC transport. + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError Init(); + + /** + * @brief Starts NFC polling/discovery. + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError Start(); + + /** + * @brief Stops NFC polling. + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError Stop(); + + /** + * @brief Sends data to the detected NFC card. + * + * @param data The data to send. + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError Send(Data data); + + /** + * @brief Terminates the current NFC session and restarts polling. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError Terminate(); + + /** + * @brief Execute a single RFAL NFC worker iteration. + * + */ + void Execute(); + +private: + NfcTransportRfal() = default; + NfcTransportRfal(const NfcTransportRfal &) = delete; + NfcTransportRfal(NfcTransportRfal &&) = delete; + ~NfcTransportRfal() = default; + NfcTransportRfal &operator=(const NfcTransportRfal &) = delete; + NfcTransportRfal &operator=(NfcTransportRfal &&) = delete; + + ReturnCode RfalNfcInit(); + void RfalNotifyCallback(rfalNfcState state); + void CaptureRxData(); + void SelectTag(); + void RecoverPolling(); + + rfalNfcDiscoverParam mNfcConfig{}; + k_thread mThread{}; + bool mMultiSel{ false }; + + std::array mRxBuffer{}; + uint8_t *mRxData{}; + uint16_t *mRcvLen{}; + + bool mRecoverPolling{ false }; + bool mSendInProgress{ false }; + bool mTagDetectedState{ false }; + atomic_t mStarted{ false }; +}; + +} // namespace Aliro diff --git a/app/src/aliro/platform/nfc_transport_impl/nfc_transport_rfal_prop.h b/app/src/aliro/platform/nfc/nfc_transport_rfal_prop.h similarity index 100% rename from app/src/aliro/platform/nfc_transport_impl/nfc_transport_rfal_prop.h rename to app/src/aliro/platform/nfc/nfc_transport_rfal_prop.h diff --git a/app/src/aliro/platform/nfc_transport_impl/isodep_config.h b/app/src/aliro/platform/nfc_transport_impl/isodep_config.h deleted file mode 100644 index 11188c0f..00000000 --- a/app/src/aliro/platform/nfc_transport_impl/isodep_config.h +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -/* An alias for a name of the class that implements the IsoDep interface. */ -#define IsoDepImpl NfcTransportRfal diff --git a/app/src/aliro/platform/nfc_transport_impl/isodep_impl.h b/app/src/aliro/platform/nfc_transport_impl/isodep_impl.h deleted file mode 100644 index 070867ce..00000000 --- a/app/src/aliro/platform/nfc_transport_impl/isodep_impl.h +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -#include "nfc_transport_rfal.h" diff --git a/app/src/aliro/platform/nfc_transport_impl/nfc_driver_config.h b/app/src/aliro/platform/nfc_transport_impl/nfc_driver_config.h deleted file mode 100644 index c4075d4c..00000000 --- a/app/src/aliro/platform/nfc_transport_impl/nfc_driver_config.h +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -/* An alias for a name of the class that implements the NfcDriver interface. */ -#define NfcDriverImpl NfcTransportRfal diff --git a/app/src/aliro/platform/nfc_transport_impl/nfc_driver_impl.h b/app/src/aliro/platform/nfc_transport_impl/nfc_driver_impl.h deleted file mode 100644 index 070867ce..00000000 --- a/app/src/aliro/platform/nfc_transport_impl/nfc_driver_impl.h +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -#include "nfc_transport_rfal.h" diff --git a/app/src/aliro/platform/nfc_transport_impl/nfc_transport_rfal.h b/app/src/aliro/platform/nfc_transport_impl/nfc_transport_rfal.h deleted file mode 100644 index dadabbdb..00000000 --- a/app/src/aliro/platform/nfc_transport_impl/nfc_transport_rfal.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -#include "transport/nfc/driver/interface/aliro_nfc_driver.h" -#include "transport/nfc/isodep/interface/aliro_isodep.h" - -extern "C" { -#include -#include -#include -} - -#include - -#include - -namespace Aliro { - -class NfcTransportRfal : public IsoDep, public NfcDriver { -private: - friend class IsoDep; - friend IsoDep &IsoDepInstance(); - friend class NfcDriver; - friend NfcDriver &NfcDriverInstance(); - - // IsoDep interface - AliroError _Init(IsoDep::Callbacks callbacks); - AliroError _PrepareData(Data data) const; - AliroError _PrepareRats() const; - AliroError _HandleReceivedData(Data data, int transferError) const; - AliroError _ReportTimeout() const; - - // NfcDriver interface - AliroError _Init(NfcDriver::Callbacks callbacks); - AliroError _Send(Data data, uint32_t maximumFrameDelayTime); - AliroError _NfcOn() const; - AliroError _NfcOff() const; - AliroError _RestartPolling(); - - static NfcTransportRfal &Instance() - { - static NfcTransportRfal sInstance; - return sInstance; - } - - ReturnCode RfalNfcInit(); - [[noreturn]] void Run(); - void RfalNotifyCallback(rfalNfcState state); - void CaptureRxData(); - void SelectTag() const; - void RecoverPolling() const; - - rfalNfcDiscoverParam mNfcConfig{}; - k_thread mThread{}; - bool mMultiSel{ false }; - - std::array mRxBuffer{}; - uint8_t *mRxData{}; - uint16_t *mRcvLen{}; - - bool mRecoverPolling{ false }; - bool mSendInProgress{ false }; -}; - -} // namespace Aliro diff --git a/app/src/aliro/platform/timer.cpp b/app/src/aliro/platform/timer.cpp new file mode 100644 index 00000000..56ae0d3f --- /dev/null +++ b/app/src/aliro/platform/timer.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause + */ + +#include "timer.h" + +#include "aliro/aliro_work/aliro_work.h" +#include "aliro/utils.h" + +#include + +namespace Aliro { + +Timer::Timer(uint32_t timeoutMs, Callback callback, Context userData) + : mTimeout{ K_MSEC(timeoutMs) }, mCallback{ callback }, mContext{ userData } +{ + k_timer_init( + &mTimer, + [](k_timer *timer) { + auto timerObj = static_cast(k_timer_user_data_get(timer)); + VerifyOrDie(timerObj, "Invalid timer"); + + std::ignore = AliroWorkSubmit(&timerObj->mWork); + }, + nullptr); + + k_timer_user_data_set(&mTimer, this); + + k_work_init(&mWork, [](k_work *work) { + auto *timer = CONTAINER_OF(work, Timer, mWork); + timer->mCallback(timer->mContext); + }); +} + +Timer::~Timer() +{ + k_timer_stop(&mTimer); + + // wait for the work to be cancelled before destroying the timer + k_work_sync sync{}; + k_work_cancel_sync(&mWork, &sync); +} + +void Timer::Start() +{ + k_timer_start(&mTimer, mTimeout, K_NO_WAIT); +} + +void Timer::Restart() +{ + Start(); +} + +bool Timer::IsRunning() +{ + return k_timer_remaining_ticks(&mTimer) != 0; +} + +void Timer::Stop() +{ + k_timer_stop(&mTimer); +} + +} // namespace Aliro diff --git a/lib/aliro/include/aliro/timer.h b/app/src/aliro/platform/timer.h similarity index 99% rename from lib/aliro/include/aliro/timer.h rename to app/src/aliro/platform/timer.h index 4b5e0211..72ac223f 100644 --- a/lib/aliro/include/aliro/timer.h +++ b/app/src/aliro/platform/timer.h @@ -7,6 +7,7 @@ #pragma once #include + #include namespace Aliro { diff --git a/app/src/aliro/platform/uwb_impl/Kconfig b/app/src/aliro/platform/uwb_impl/Kconfig index 92b7bf93..2ba2dee3 100644 --- a/app/src/aliro/platform/uwb_impl/Kconfig +++ b/app/src/aliro/platform/uwb_impl/Kconfig @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +menu "UWB" config TIMESYNC_PROCEDURE_0 bool "Enable timesync Procedure 0" @@ -24,3 +25,5 @@ module-str = DOOR_LOCK_UWB module-dep = LOG module-help = Enables Door Lock UWB platform log messages. source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endmenu # UWB diff --git a/lib/aliro/interfaces/uwb/uwb.h b/app/src/aliro/platform/uwb_impl/uwb.h similarity index 84% rename from lib/aliro/interfaces/uwb/uwb.h rename to app/src/aliro/platform/uwb_impl/uwb.h index 45e6cd9e..57fc8f4c 100644 --- a/lib/aliro/interfaces/uwb/uwb.h +++ b/app/src/aliro/platform/uwb_impl/uwb.h @@ -6,6 +6,7 @@ #pragma once +#include "aliro/connection_handle.h" #include "aliro/errors.h" #include "aliro/protocol_version.h" #include "aliro/types.h" @@ -24,7 +25,7 @@ namespace Aliro::Uwb { template class UltraWideBand { public: using SessionIdentifier = uint32_t; - using SessionContextHandle = const void *; + using SessionContextHandle = ConnectionHandle; /** * @struct Callbacks @@ -55,29 +56,6 @@ template class UltraWideBand { RangingSessionState state){ nullptr }; }; - /** - * @struct StackCallbacks - * @brief Struct containing callback functions for interfacing with the Aliro stack. - * - * This struct holds pointers to functions that are used to communicate with the Aliro stack. - */ - struct StackCallbacks { - /** - * @brief Callback to transmit a BLE message. - * - * This callback is invoked when the UWB module needs to send a BLE message - * as part of the Aliro protocol flow. The message must conform to the format - * specified in the Aliro spec., and the payload must stay - * unencrypted. - * - * @param sessionContextData Pointer to current session context data used by the Aliro stack. - * @param data Pointer to the formatted BLE message data. - * @param length Size of the message data in bytes. - */ - void (*mTransmitBleMessage)(SessionContextHandle sessionContextData, const uint8_t *data, - size_t length){ nullptr }; - }; - /** * @brief Initializes the UltraWideBand module. * @@ -91,13 +69,6 @@ template class UltraWideBand { */ AliroError Init(const Callbacks &callbacks) { return Impl()->_Init(callbacks); } - /** - * @brief Set the stack callbacks. - * - * @param callbacks Stack callbacks. - */ - void SetStackCallbacks(const StackCallbacks &callbacks) { Impl()->_SetStackCallbacks(callbacks); } - /** * @brief Deinitializes the UltraWideBand module. * @@ -145,14 +116,14 @@ template class UltraWideBand { * @param sessionId The session identifier for the ranging session. * @param ursk Reference to the URSK. * @param protocolVersion The protocol version to use for the ranging session. - * @param sessionContextData Pointer to current session context data used by the Aliro stack. + * @param sessionContextHandle Pointer to current session context handle used by the Aliro stack. * * @return ALIRO_NO_ERROR on success, or an error code on failure. */ AliroError ConfigureRangingSession(SessionIdentifier sessionId, const CryptoTypes::Ursk &ursk, - ProtocolVersion protocolVersion, SessionContextHandle sessionContextData) + ProtocolVersion protocolVersion, SessionContextHandle sessionContextHandle) { - return Impl()->_ConfigureRangingSession(sessionId, ursk, protocolVersion, sessionContextData); + return Impl()->_ConfigureRangingSession(sessionId, ursk, protocolVersion, sessionContextHandle); } /** diff --git a/app/src/aliro/platform/uwb_impl/uwb_dummy_impl/uwb_impl.cpp b/app/src/aliro/platform/uwb_impl/uwb_dummy_impl/uwb_impl.cpp index a4a34614..a37595c6 100644 --- a/app/src/aliro/platform/uwb_impl/uwb_dummy_impl/uwb_impl.cpp +++ b/app/src/aliro/platform/uwb_impl/uwb_dummy_impl/uwb_impl.cpp @@ -13,8 +13,6 @@ AliroError UltraWideBandImpl::_Init([[maybe_unused]] const Callbacks &) return ALIRO_ERROR_NOT_IMPLEMENTED; } -void UltraWideBandImpl::_SetStackCallbacks([[maybe_unused]] const StackCallbacks &) {} - AliroError UltraWideBandImpl::_Deinit() { return ALIRO_ERROR_NOT_IMPLEMENTED; diff --git a/app/src/aliro/platform/uwb_impl/uwb_dummy_impl/uwb_impl.h b/app/src/aliro/platform/uwb_impl/uwb_dummy_impl/uwb_impl.h index 66837e17..874abb74 100644 --- a/app/src/aliro/platform/uwb_impl/uwb_dummy_impl/uwb_impl.h +++ b/app/src/aliro/platform/uwb_impl/uwb_dummy_impl/uwb_impl.h @@ -7,7 +7,7 @@ #pragma once #include "aliro/errors.h" -#include "uwb/uwb.h" +#include "uwb.h" #include @@ -34,12 +34,11 @@ class UltraWideBandImpl : public UltraWideBand { } AliroError _Init(const Callbacks &callbacks); - void _SetStackCallbacks(const StackCallbacks &callbacks); AliroError _Deinit(); void _BleTimeSync(); AliroError _HandleBleMessage(const uint8_t *data, size_t length, SessionContextHandle sessionContextData); AliroError _ConfigureRangingSession(SessionIdentifier sessionId, const CryptoTypes::Ursk &ursk, - ProtocolVersion protocolVersion, SessionContextHandle sessionContextData); + ProtocolVersion protocolVersion, SessionContextHandle sessionContextHandle); AliroError _InitiateRangingSession(SessionContextHandle sessionContextData); AliroError _TerminateRangingSession(SessionContextHandle sessionContextData); AliroError _SuspendRangingSession(SessionContextHandle sessionContextData, bool force); diff --git a/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/cli/uwb_shell.cpp b/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/cli/uwb_shell.cpp index 378b08b0..d921328d 100644 --- a/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/cli/uwb_shell.cpp +++ b/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/cli/uwb_shell.cpp @@ -14,6 +14,11 @@ using namespace Aliro::Uwb; int ShellCmdQm35Version(const struct shell *shell, size_t, char **) { + if (!UltraWideBandImpl::Instance().IsInitialized()) { + shell_warn(shell, "UWB not initialized"); + return 0; + } + const char *version = UltraWideBandImpl::Instance().GetQm35FirmwareVersion(); if (version) { diff --git a/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/dfu/uwb_dfu.cpp b/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/dfu/uwb_dfu.cpp index 1de48fe3..14236b68 100644 --- a/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/dfu/uwb_dfu.cpp +++ b/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/dfu/uwb_dfu.cpp @@ -87,6 +87,8 @@ int PerformFirmwareUpdate() mcuboot_img_header header; uint8_t firmwareChunkBuffer[kMaxChunkSize]; + LOG_INF("Starting firmware update"); + ret = boot_read_bank_header(QM35_DFU_IMAGE_PARTITION_ID, &header, sizeof(mcuboot_img_header)); VerifyOrReturnValue(ret == 0, ret, LOG_WRN("Error when reading QM35 FW primary slot: %d", ret)); @@ -158,6 +160,8 @@ int PerformFirmwareUpdate() qmrom_spi_unregister_drivers(); + LOG_INF("Firmware update %s", (ret ? "failed" : "successful")); + return ret; } diff --git a/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/uwb_impl.cpp b/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/uwb_impl.cpp index c2703d3e..199deac2 100644 --- a/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/uwb_impl.cpp +++ b/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/uwb_impl.cpp @@ -8,9 +8,10 @@ #include "dfu/uwb_dfu.h" #include "uwb_message.h" +#include "aliro/aliro.h" #include "aliro/memory.h" -#include "aliro/mutex_guard.h" #include "aliro/utils.h" +#include "mutex_guard.h" // UWB API #include @@ -190,8 +191,7 @@ void UltraWideBandImpl::TransmitBleMessage(aliro_uwb_message *message, UwbSessio auto *sessionCtx = uwbImpl->FindSession(uwbSessionCtx); VerifyOrExit(sessionCtx, LOG_ERR("Session context not found")); - VerifyAndCall(uwbImpl->mStackCallbacks.mTransmitBleMessage, sessionCtx->mSessionContextData, - message->data, message->len); + AliroStack::Instance().SendBleMessage(sessionCtx->mSessionContextData, message->data, message->len); } exit: @@ -319,6 +319,16 @@ AliroError UltraWideBandImpl::HandleDeviceCapsEvent(CoreEvent *event) mAliroCtx = aliro_uwb_adapter_create_reader(mCtx, event->data.device_caps, &mReaderConfig); VerifyOrReturnStatus(mAliroCtx, ALIRO_ERROR_INTERNAL, LOG_ERR("Failed to create UWB adapter reader.")); + if (event->data.device_caps->ccc_capabilities) { + const auto *src = event->data.device_caps->ccc_capabilities; + mCccCaps.mSlotBitmask = src->slot_bitmask; + mCccCaps.mChannelBitmask = src->channel_bitmask; + mCccCaps.mHoppingConfigBitmask = src->hopping_config_bitmask; + mCccCaps.mSyncCodeIndexBitmask = src->sync_code_index_bitmask; + mCccCaps.mMinimumRanMultiplier = src->minimum_ran_multiplier; + mCccCapsValid = true; + } + return ALIRO_NO_ERROR; } @@ -374,80 +384,63 @@ AliroError UltraWideBandImpl::HandleUwbEvent(UwbEvents expectedEvent, EventHandl AliroError UltraWideBandImpl::_Init(const Callbacks &callbacks) { - cherry_err cErr{}; - AliroError err{}; + LOG_INF("Initializing UWB device..."); -#ifdef CONFIG_DOOR_LOCK_UWB_QM35_DFU - if (!mFwUpdateInProgress) { -#endif - mCallbacks = callbacks; + mInitialized = false; + mCccCapsValid = false; - VerifyOrReturnStatus(k_mutex_init(&mMutex) == 0, ALIRO_ERROR_INTERNAL, - LOG_ERR("Failed to initialize mutex")); - sys_slist_init(&mActiveSessionsList); -#ifdef CONFIG_DOOR_LOCK_UWB_QM35_DFU - } -#endif + mCallbacks = callbacks; - LOG_INF("Initializing UWB device..."); + VerifyOrReturnStatus(k_mutex_init(&mMutex) == 0, ALIRO_ERROR_INTERNAL, LOG_ERR("Failed to initialize mutex")); + sys_slist_init(&mActiveSessionsList); mCtx = cherry_create("qm35", &UwbCoreCallback, this); VerifyOrReturnStatus(mCtx, ALIRO_UWB_INIT_FAILED, LOG_ERR("Failed to create Cherry context")); - // Use full calibration data for the QM35825 device. - // This needs to be done only once during initialization, or after a power cycle. - const auto calibData = &util_calib_qm35825; - int status = cherry_set_calib(mCtx, calibData); - VerifyOrExit(status == CHERRY_ERR_NONE, LOG_ERR("Failed to set calibration data: %d", status)); + auto err = GetDeviceInfo(); - // Read Cherry device capabilities. - cErr = cherry_get_device_capabilities(mCtx); - VerifyOrExit(cErr == CHERRY_ERR_NONE, LOG_ERR("Failed to get device capabilities: %s", cherry_err_str(cErr))); +#ifdef CONFIG_DOOR_LOCK_UWB_QM35_DFU + + if (err != ALIRO_NO_ERROR || Dfu::ShouldUpdate(mQm35FirmwareVersion.get())) { + cherry_destroy_sync(mCtx); + mCtx = nullptr; + + const auto ret = Dfu::PerformFirmwareUpdate(); + VerifyOrReturnStatus(ret == 0, ALIRO_ERROR_INTERNAL, LOG_ERR("Firmware update failed")); + + mCtx = cherry_create("qm35", &UwbCoreCallback, this); + VerifyOrReturnStatus(mCtx, ALIRO_UWB_INIT_FAILED, LOG_ERR("Failed to create Cherry context")); + + err = GetDeviceInfo(); + } + +#endif // CONFIG_DOOR_LOCK_UWB_QM35_DFU - // Wait for and handle device capabilities event. - err = HandleUwbEvent(UwbEvents::DeviceCaps, - [](CoreEvent *event) -> AliroError { return Instance().HandleDeviceCapsEvent(event); }); VerifyOrExit(err == ALIRO_NO_ERROR); - // Read Cherry device info. - cErr = cherry_get_device_info(mCtx); - VerifyOrExit(cErr == CHERRY_ERR_NONE, LOG_ERR("Failed to get device info: %s", cherry_err_str(cErr))); + err = SetCalibrationData(); + VerifyOrExit(err == ALIRO_NO_ERROR); - // Wait for and handle device info event. - err = HandleUwbEvent(UwbEvents::DeviceInfo, - [](CoreEvent *event) -> AliroError { return Instance().HandleDeviceInfoEvent(event); }); + err = GetDeviceCapabilities(); VerifyOrExit(err == ALIRO_NO_ERROR); + mInitialized = true; LOG_INF("UWB device initialized successfully."); -#ifdef CONFIG_DOOR_LOCK_UWB_QM35_DFU - if (!mFwUpdateInProgress) { - return CheckAndUpdateQm35(false); - } -#endif - return ALIRO_NO_ERROR; exit: - -#ifdef CONFIG_DOOR_LOCK_UWB_QM35_DFU - if (!mFwUpdateInProgress) { - return CheckAndUpdateQm35(true); - } -#else + LOG_ERR("UWB device initialization failed"); _Deinit(); -#endif - return ALIRO_UWB_INIT_FAILED; } -void UltraWideBandImpl::_SetStackCallbacks(const StackCallbacks &callbacks) -{ - mStackCallbacks = callbacks; -} - AliroError UltraWideBandImpl::_Deinit() { + mInitialized = false; + mCccCapsValid = false; + mQm35FirmwareVersion.reset(); + RemoveAllSessions(); if (mAliroCtx) { @@ -474,7 +467,6 @@ void UltraWideBandImpl::_BleTimeSync() AliroError UltraWideBandImpl::_HandleBleMessage(const uint8_t *data, size_t length, SessionContextHandle sessionContextData) { - VerifyOrReturnStatus(sessionContextData, ALIRO_INVALID_ARGUMENT, LOG_ERR("Session context data is null.")); VerifyOrReturnStatus(data && length > 0, ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid BLE message data.")); aliro_uwb_err err{}; @@ -486,7 +478,7 @@ AliroError UltraWideBandImpl::_HandleBleMessage(const uint8_t *data, size_t leng const auto *sessionCtx = FindSession(sessionContextData); VerifyOrReturnStatus(sessionCtx, ALIRO_SESSION_NOT_FOUND, - LOG_ERR("Session context not found for handle: %p", sessionContextData)); + LOG_ERR("Session context not found for handle: %p", sessionContextData.GetRaw())); err = aliro_uwb_session_message_handle(sessionCtx->mUwbSessionContext, message.get()); VerifyOrReturnStatus(err == ALIRO_UWB_ERR_NONE, ConvertUwbError(err), LOG_ERR("Cannot handle UWB session message 0x%x", ToUnderlying(err))); @@ -496,14 +488,13 @@ AliroError UltraWideBandImpl::_HandleBleMessage(const uint8_t *data, size_t leng AliroError UltraWideBandImpl::_ConfigureRangingSession(SessionIdentifier sessionId, const CryptoTypes::Ursk &ursk, ProtocolVersion protocolVersion, - SessionContextHandle sessionContextData) + SessionContextHandle sessionContextHandle) { VerifyOrReturnStatus(mAliroCtx, ALIRO_INVALID_STATE, LOG_ERR("UWB is not initialized.")); - VerifyOrReturnStatus(sessionContextData, ALIRO_INVALID_ARGUMENT, LOG_ERR("Session context data is null.")); - auto *sessionCtx = FindSession(sessionContextData); + auto *sessionCtx = FindSession(sessionContextHandle); VerifyOrReturnStatus(!sessionCtx, ALIRO_INVALID_STATE, - LOG_ERR("Session context already exists for handle: %p", sessionContextData)); + LOG_ERR("Session context already exists for handle: %p", sessionContextHandle.GetRaw())); aliro_uwb_session *newSessionCtx = aliro_uwb_session_create(mAliroCtx, sessionId, &SessionHandlerCallback, &TransmitBleMessage, this); @@ -515,11 +506,11 @@ AliroError UltraWideBandImpl::_ConfigureRangingSession(SessionIdentifier session err = ConvertUwbError(aliro_uwb_session_set_protocol_version(newSessionCtx, protocolVersion)); VerifyOrExit(err == ALIRO_NO_ERROR, LOG_ERR("Failed to set protocol version in UWB session: %d", err.ToInt())); - err = AddSession({ .mUwbSessionContext = newSessionCtx, .mSessionContextData = sessionContextData }); + err = AddSession(newSessionCtx, sessionContextHandle); VerifyOrExit(err == ALIRO_NO_ERROR, LOG_ERR("Failed to add session to the active sessions list: %d", err.ToInt())); - LOG_INF("UWB session created with sessionContextData: %p", sessionContextData); + LOG_INF("UWB session created with sessionContextHandle: %p", sessionContextHandle.GetRaw()); return ALIRO_NO_ERROR; exit: @@ -530,11 +521,9 @@ AliroError UltraWideBandImpl::_ConfigureRangingSession(SessionIdentifier session AliroError UltraWideBandImpl::_InitiateRangingSession(SessionContextHandle sessionContextData) { - VerifyOrReturnStatus(sessionContextData, ALIRO_INVALID_ARGUMENT, LOG_ERR("Session context data is null.")); - const auto *sessionCtx = FindSession(sessionContextData); VerifyOrReturnStatus(sessionCtx, ALIRO_SESSION_NOT_FOUND, - LOG_ERR("Session context not found for handle: %p", sessionContextData)); + LOG_ERR("Session context not found for handle: %p", sessionContextData.GetRaw())); aliro_uwb_err err = aliro_uwb_session_init_setup(sessionCtx->mUwbSessionContext); VerifyOrReturnStatus(err == ALIRO_UWB_ERR_NONE, ConvertUwbError(err), @@ -545,25 +534,22 @@ AliroError UltraWideBandImpl::_InitiateRangingSession(SessionContextHandle sessi AliroError UltraWideBandImpl::_TerminateRangingSession(SessionContextHandle sessionContextData) { - VerifyOrReturnStatus(sessionContextData, ALIRO_INVALID_ARGUMENT, LOG_ERR("Session context data is null.")); - auto *sessionCtx = FindSession(sessionContextData); VerifyOrReturnStatus(sessionCtx, ALIRO_SESSION_NOT_FOUND, - LOG_ERR("Session context not found for handle: %p", sessionContextData)); + LOG_ERR("Session context not found for handle: %p", sessionContextData.GetRaw())); RemoveSession(sessionCtx); - LOG_DBG("Terminating UWB session with context: %p", sessionContextData); + LOG_DBG("Terminating UWB session with context: %p", sessionContextData.GetRaw()); return ALIRO_NO_ERROR; } AliroError UltraWideBandImpl::_SuspendRangingSession(SessionContextHandle sessionContextData) { - VerifyOrReturnStatus(sessionContextData, ALIRO_INVALID_ARGUMENT, LOG_ERR("Session context data is null.")); const auto *sessionCtx = FindSession(sessionContextData); VerifyOrReturnStatus(sessionCtx, ALIRO_SESSION_NOT_FOUND, - LOG_ERR("Session context not found for handle: %p", sessionContextData)); + LOG_ERR("Session context not found for handle: %p", sessionContextData.GetRaw())); aliro_uwb_err err = aliro_uwb_session_suspend(sessionCtx->mUwbSessionContext); VerifyOrReturnStatus(err == ALIRO_UWB_ERR_NONE, ConvertUwbError(err), @@ -574,10 +560,9 @@ AliroError UltraWideBandImpl::_SuspendRangingSession(SessionContextHandle sessio AliroError UltraWideBandImpl::_ResumeRangingSession(SessionContextHandle sessionContextData) { - VerifyOrReturnStatus(sessionContextData, ALIRO_INVALID_ARGUMENT, LOG_ERR("Session context data is null.")); const auto *sessionCtx = FindSession(sessionContextData); VerifyOrReturnStatus(sessionCtx, ALIRO_SESSION_NOT_FOUND, - LOG_ERR("Session context not found for handle: %p", sessionContextData)); + LOG_ERR("Session context not found for handle: %p", sessionContextData.GetRaw())); aliro_uwb_err err = aliro_uwb_session_resume(sessionCtx->mUwbSessionContext); VerifyOrReturnStatus(err == ALIRO_UWB_ERR_NONE, ConvertUwbError(err), @@ -586,14 +571,11 @@ AliroError UltraWideBandImpl::_ResumeRangingSession(SessionContextHandle session return ALIRO_NO_ERROR; } -AliroError UltraWideBandImpl::AddSession(const SessionContext &sessionCtx) +AliroError UltraWideBandImpl::AddSession(UwbSessionContext uwbSessionContext, SessionContextHandle sessionContextHandle) { - auto newCtx = Aliro::new_nothrow(); + auto newCtx = Aliro::new_nothrow(uwbSessionContext, sessionContextHandle); VerifyOrReturnStatus(newCtx, ALIRO_NO_MEMORY, LOG_ERR("Memory allocation failed for session context.")); - newCtx->mUwbSessionContext = sessionCtx.mUwbSessionContext; - newCtx->mSessionContextData = sessionCtx.mSessionContextData; - MutexGuard lock{ mMutex }; sys_slist_append(&mActiveSessionsList, &newCtx->mSessionContextNode); @@ -666,37 +648,40 @@ void UltraWideBandImpl::DestroySession(SessionContext *sessionCtx) } } -AliroError UltraWideBandImpl::CheckAndUpdateQm35(bool skipVersionCheck) +AliroError UltraWideBandImpl::GetDeviceInfo() { - int err; - cherry_err cErr{}; - AliroError aErr{}; - - if (!skipVersionCheck && !Dfu::ShouldUpdate(mQm35FirmwareVersion.get())) { - return ALIRO_NO_ERROR; - } - - LOG_DBG("Starting firmware update, resetting device"); - - cErr = cherry_reset_device(mCtx, true); - if (cErr != CHERRY_ERR_NONE) { - LOG_ERR("cherry_reset failed with error %d", cErr); - return ALIRO_ERROR_UNKNOWN; - } - - mFwUpdateInProgress = true; + // Read Cherry device info. + const auto cErr = cherry_get_device_info(mCtx); + VerifyOrReturnStatus(cErr == CHERRY_ERR_NONE, ALIRO_ERROR_INTERNAL, + LOG_ERR("Failed to get device info: %s", cherry_err_str(cErr))); - _Deinit(); + // Wait for and handle device info event. + return HandleUwbEvent(UwbEvents::DeviceInfo, + [](CoreEvent *event) -> AliroError { return Instance().HandleDeviceInfoEvent(event); }); +} - err = Dfu::PerformFirmwareUpdate(); - aErr = _Init(mCallbacks); +AliroError UltraWideBandImpl::GetDeviceCapabilities() +{ + // Read Cherry device capabilities. + const auto cErr = cherry_get_device_capabilities(mCtx); + VerifyOrReturnStatus(cErr == CHERRY_ERR_NONE, ALIRO_ERROR_INTERNAL, + LOG_ERR("Failed to get device capabilities: %s", cherry_err_str(cErr))); - mFwUpdateInProgress = false; + // Wait for and handle device capabilities event. + return HandleUwbEvent(UwbEvents::DeviceCaps, + [](CoreEvent *event) -> AliroError { return Instance().HandleDeviceCapsEvent(event); }); +} - LOG_INF("Firmware update %s", (err ? "failed" : "successful")); +AliroError UltraWideBandImpl::SetCalibrationData() +{ + // Use full calibration data for the QM35825 device. + // This needs to be done only once during initialization, or after a power cycle. + const auto calibData = &util_calib_qm35825; + const auto cErr = cherry_set_calib(mCtx, calibData); + VerifyOrReturnStatus(cErr == CHERRY_ERR_NONE, ALIRO_ERROR_INTERNAL, + LOG_ERR("Failed to set calibration data: %s", cherry_err_str(cErr))); - // If DFU was succesful report init's result - return (err == 0 ? aErr : ALIRO_UWB_DFU_FAILED); + return ALIRO_NO_ERROR; } } // namespace Aliro::Uwb diff --git a/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/uwb_impl.h b/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/uwb_impl.h index 6c863d92..ee27b995 100644 --- a/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/uwb_impl.h +++ b/app/src/aliro/platform/uwb_impl/uwb_qm35_impl/uwb_impl.h @@ -7,7 +7,7 @@ #pragma once #include "aliro/errors.h" -#include "uwb/uwb.h" +#include "uwb.h" #include #include @@ -50,12 +50,11 @@ class UltraWideBandImpl : public UltraWideBand { } AliroError _Init(const Callbacks &callbacks); - void _SetStackCallbacks(const StackCallbacks &callbacks); AliroError _Deinit(); void _BleTimeSync(); AliroError _HandleBleMessage(const uint8_t *data, size_t length, SessionContextHandle sessionContextData); AliroError _ConfigureRangingSession(SessionIdentifier sessionId, const CryptoTypes::Ursk &ursk, - ProtocolVersion protocolVersion, SessionContextHandle sessionContextData); + ProtocolVersion protocolVersion, SessionContextHandle sessionContextHandle); AliroError _InitiateRangingSession(SessionContextHandle sessionContextData); AliroError _TerminateRangingSession(SessionContextHandle sessionContextData); AliroError _SuspendRangingSession(SessionContextHandle sessionContextData); @@ -68,6 +67,28 @@ class UltraWideBandImpl : public UltraWideBand { */ const char *GetQm35FirmwareVersion() const { return mQm35FirmwareVersion.get(); } + /** + * @brief Indicates whether the UWB module has been fully initialized. + * + * When true, device info and capabilities have been retrieved and cached. + */ + bool IsInitialized() const { return mInitialized; } + + struct CccCaps { + uint8_t mSlotBitmask; + uint8_t mChannelBitmask; + uint8_t mHoppingConfigBitmask; + uint32_t mSyncCodeIndexBitmask; + uint8_t mMinimumRanMultiplier; + }; + + /** + * @brief Gets the CCC capabilities. + * + * @return Pointer to the CCC capabilities structure, or nullptr if not available. + */ + const CccCaps *GetCccCapabilities() const { return mCccCapsValid ? &mCccCaps : nullptr; } + // Delete copy and move constructors and assignment operators. UltraWideBandImpl(const UltraWideBandImpl &) = delete; UltraWideBandImpl &operator=(const UltraWideBandImpl &) = delete; @@ -85,9 +106,14 @@ class UltraWideBandImpl : public UltraWideBand { * for efficient linked list management following Zephyr patterns. */ struct SessionContext { + SessionContext(UwbSessionContext uwbSessionContext, SessionContextHandle sessionContextData) + : mUwbSessionContext(uwbSessionContext), mSessionContextData(sessionContextData) + { + } + sys_snode_t mSessionContextNode{}; - UwbSessionContext mUwbSessionContext{}; - SessionContextHandle mSessionContextData{}; + UwbSessionContext mUwbSessionContext; + SessionContextHandle mSessionContextData; cherry_ccc_session_state mSessionState{ CHERRY_CCC_SESSION_STATE_INIT }; RangingSessionState mRangingSessionState{ RangingSessionState::Uninitialized }; }; @@ -181,11 +207,12 @@ class UltraWideBandImpl : public UltraWideBand { /** * @brief Adds a session context to the list. * - * @param sessionCtx The session context to add. + * @param uwbSessionCtx The UWB session context. + * @param sessionContextHandle The session context data. * * @return ALIRO_NO_ERROR on success, error code otherwise. */ - AliroError AddSession(const SessionContext &sessionCtx); + AliroError AddSession(UwbSessionContext uwbSessionContext, SessionContextHandle sessionContextHandle); /** * @brief Removes and destroys a session from the list. @@ -210,27 +237,37 @@ class UltraWideBandImpl : public UltraWideBand { void DestroySession(SessionContext *sessionCtx); /** - * @brief Perform firmware update procedure for QM35 if needed. + * @brief Retrieves device information from the QM35 UWB device. * - * This method compares QM35 version to one stored in the primary slot. If the version is - * newer it will restart QM35, perform update, and re-initialize. + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError GetDeviceInfo(); + + /** + * @brief Retrieves device capabilities from the QM35 UWB device. * - * @param skipVersionCheck Skip version comparison + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError GetDeviceCapabilities(); + + /** + * @brief Sets the calibration data for the QM35825 UWB device. * * @return ALIRO_NO_ERROR on success, error code otherwise. */ - AliroError CheckAndUpdateQm35(bool skipVersionCheck); + AliroError SetCalibrationData(); CoreEvent *mCoreEvent{}; Callbacks mCallbacks{}; - StackCallbacks mStackCallbacks{}; cherry *mCtx{}; aliro_uwb_adapter *mAliroCtx{}; std::unique_ptr mQm35FirmwareVersion{ nullptr }; + CccCaps mCccCaps{}; + bool mCccCapsValid{ false }; + bool mInitialized{ false }; ActiveSessionsList mActiveSessionsList{}; k_mutex mMutex{}; std::array mCurrentDistanceCm{}; - bool mFwUpdateInProgress{ false }; aliro_uwb_adapter_reader_config mReaderConfig = { .min_ran_multiplier = CONFIG_DOOR_LOCK_UWB_MIN_RAN_MULTIPLIER, diff --git a/app/src/aliro/storage/CMakeLists.txt b/app/src/aliro/storage/CMakeLists.txt index 25de9ea9..061e11da 100644 --- a/app/src/aliro/storage/CMakeLists.txt +++ b/app/src/aliro/storage/CMakeLists.txt @@ -4,7 +4,14 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -file(GLOB src CONFIGURE_DEPENDS ./*.cpp) +target_sources(app PRIVATE + reader_cache.cpp + storage.cpp +) +target_sources_ifdef(CONFIG_DOOR_LOCK_STEP_UP_PHASE app PRIVATE validity_iterations.cpp) -zephyr_library_sources(${src}) -zephyr_include_directories(.) +if(CONFIG_DOOR_LOCK_STEP_UP_PHASE AND CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS GREATER 0) + target_sources(app PRIVATE access_document.cpp) +endif() + +target_include_directories(app PRIVATE .) diff --git a/app/src/aliro/storage/Kconfig b/app/src/aliro/storage/Kconfig index a33dd0e4..e38e2b0b 100644 --- a/app/src/aliro/storage/Kconfig +++ b/app/src/aliro/storage/Kconfig @@ -4,8 +4,11 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +menu "Storage" + config DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS int "Maximum number of stored Access Documents" + default 0 if SOC_SERIES_NRF52X default 5 if DOOR_LOCK_STEP_UP_PHASE default 0 help @@ -19,3 +22,5 @@ config DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENT_SIZE default 0 help Specifies the maximum size of the stored Access Document. + +endmenu # Storage diff --git a/app/src/aliro/storage/access_document.cpp b/app/src/aliro/storage/access_document.cpp index d6e49405..e166d966 100644 --- a/app/src/aliro/storage/access_document.cpp +++ b/app/src/aliro/storage/access_document.cpp @@ -4,15 +4,13 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE - #include "access_document.h" #include "access_manager_impl.h" #include "aliro/utils.h" -#include "storage.h" -#include "storage_keys.h" +#include "external_nvs_ids.h" +#include #include LOG_MODULE_REGISTER(access_document, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); @@ -21,16 +19,33 @@ namespace Aliro { namespace { -StorageKeys::KeyNameBuffer GetStorageKeyName(size_t index) +DoorLock::ExternalNvs::Id GetExternalNvsId(size_t index) +{ + return DoorLock::ExternalNvsIds::AccessDocument::kRangeStart + static_cast(index); +} + +bool IsIndexInRange(size_t index) { - return KeyValueStorage::GetStorageKeyName(StorageKeys::kStorageKeyNameAccessDocument, index); + return index < DoorLock::ExternalNvsIds::AccessDocument::kRangeSize; } int ReadAccessDocumentHelper(size_t index, AccessDocument &ad) { - const auto keyName = GetStorageKeyName(index); - return KeyValueStorage::Instance().Get(keyName.data(), reinterpret_cast(&ad), - sizeof(AccessDocument)); + const auto id = GetExternalNvsId(index); + size_t len = sizeof(AccessDocument); + const auto error = DoorLock::ExternalNvs::Read(id, &ad, len); + + if (error != 0) { + return error; + } + + if (len != sizeof(AccessDocument)) { + LOG_ERR("Invalid Access Document size at index: %zu, expected: %zu, got: %zu", index, + sizeof(AccessDocument), len); + return -EIO; + } + + return 0; } } // namespace @@ -40,18 +55,18 @@ AliroError LoadAccessDocuments() AccessDocument ad; for (size_t index = 0; index < CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS; index++) { const auto error = ReadAccessDocumentHelper(index, ad); - if (error == -ENODATA) { + if (error == -ENOENT) { continue; } VerifyOrReturnStatus(error == 0, AliroError::FromInt(error), - LOG_ERR("Failed to read Access Document at index: %u, error code: %d", index, + LOG_ERR("Failed to read Access Document at index: %zu, error code: %d", index, error)); ReturnErrorOnFailure(AccessManagerInstance().AddPublicKey( ad.mPublicKey, AccessManager::PublicKeyType::AccessDocument, index)); - LOG_DBG("Loaded AD at index: %u, Version: %u, CI index: %u, Timestamp: %.*s, Access Iteration: %" PRIu64, + LOG_DBG("Loaded AD at index: %zu, Version: %u, CI index: %u, Timestamp: %.*s, Access Iteration: %" PRIu64, index, ad.mVersion, ad.mCredentialIssuerKeyIndex, ad.mSignedTimestamp.size(), ad.mSignedTimestamp.data(), ad.mAccessIteration); } @@ -61,34 +76,40 @@ AliroError LoadAccessDocuments() AliroError StoreAccessDocument(size_t index, const AccessDocument &ad) { - const auto keyName = GetStorageKeyName(index); - const auto error = KeyValueStorage::Instance().Save(keyName.data(), reinterpret_cast(&ad), - sizeof(AccessDocument)); + VerifyOrReturnStatus(IsIndexInRange(index), ALIRO_INVALID_ARGUMENT, + LOG_ERR("Access Document index out of range: %zu", index)); + + const auto id = GetExternalNvsId(index); + const auto error = DoorLock::ExternalNvs::Write(id, &ad, sizeof(AccessDocument)); VerifyOrReturnStatus(error == 0, AliroError::FromInt(error), - LOG_ERR("Failed to store Access Document at index: %u", index)); + LOG_ERR("Failed to store Access Document at index: %zu", index)); return ALIRO_NO_ERROR; } AliroError ReadAccessDocument(size_t index, AccessDocument &ad) { + VerifyOrReturnStatus(IsIndexInRange(index), ALIRO_INVALID_ARGUMENT, + LOG_ERR("Access Document index out of range: %zu", index)); + const auto error = ReadAccessDocumentHelper(index, ad); VerifyOrReturnStatus(error == 0, AliroError::FromInt(error), - LOG_ERR("Failed to get Access Document at index: %u", index)); + LOG_ERR("Failed to get Access Document at index: %zu", index)); return ALIRO_NO_ERROR; } AliroError ClearAccessDocument(size_t index) { - const auto keyName = GetStorageKeyName(index); - const auto error = KeyValueStorage::Instance().Clear(keyName.data()); + VerifyOrReturnStatus(IsIndexInRange(index), ALIRO_INVALID_ARGUMENT, + LOG_ERR("Access Document index out of range: %zu", index)); + + const auto id = GetExternalNvsId(index); + const auto error = DoorLock::ExternalNvs::Delete(id); VerifyOrReturnStatus(error == 0, AliroError::FromInt(error), - LOG_ERR("Failed to clear Access Document at index: %u", index)); + LOG_ERR("Failed to clear Access Document at index: %zu", index)); return ALIRO_NO_ERROR; } } // namespace Aliro - -#endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE diff --git a/app/src/aliro/storage/access_document.h b/app/src/aliro/storage/access_document.h index 973f6da3..8ec88975 100644 --- a/app/src/aliro/storage/access_document.h +++ b/app/src/aliro/storage/access_document.h @@ -4,8 +4,6 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE - #pragma once #include "aliro/errors.h" @@ -77,5 +75,3 @@ AliroError StoreAccessDocument(size_t index, const AccessDocument &ad); AliroError ClearAccessDocument(size_t index); } // namespace Aliro - -#endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE diff --git a/app/src/aliro/storage/external_nvs_ids.h b/app/src/aliro/storage/external_nvs_ids.h new file mode 100644 index 00000000..ca54d974 --- /dev/null +++ b/app/src/aliro/storage/external_nvs_ids.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include + +namespace DoorLock::ExternalNvsIds::AccessDocument { + +#if CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + +constexpr ExternalNvs::Id kRangeStart{ 0 }; +constexpr ExternalNvs::Id kRangeSize{ CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS }; + +#endif // CONFIG_DOOR_LOCK_STORAGE_MAX_STORED_ACCESS_DOCUMENTS > 0 + +} // namespace DoorLock::ExternalNvsIds::AccessDocument diff --git a/app/src/aliro/storage/reader_certificate_cache.cpp b/app/src/aliro/storage/reader_cache.cpp similarity index 50% rename from app/src/aliro/storage/reader_certificate_cache.cpp rename to app/src/aliro/storage/reader_cache.cpp index 36dcc198..09cdb01b 100644 --- a/app/src/aliro/storage/reader_certificate_cache.cpp +++ b/app/src/aliro/storage/reader_cache.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#include "reader_certificate_cache.h" +#include "reader_cache.h" #include "aliro/memory.h" #include "aliro/utils.h" @@ -12,22 +12,65 @@ #include #include -LOG_MODULE_REGISTER(reader_certificate_cache, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); +LOG_MODULE_REGISTER(reader_cache, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); namespace Aliro { -ReaderCertificateCache &ReaderCertificateCache::Instance() +ReaderCache &ReaderCache::Instance() { - static ReaderCertificateCache instance; + static ReaderCache instance; return instance; } -bool ReaderCertificateCache::IsCertificateSet() const +bool ReaderCache::IsIdentifierSet() const +{ + return mIdentifier.has_value(); +} + +AliroError ReaderCache::SetIdentifier(const Identifier &identifier) +{ + mIdentifier = identifier; + return ALIRO_NO_ERROR; +} + +AliroError ReaderCache::GetIdentifier(Identifier &identifier) const +{ + VerifyOrReturnStatus(mIdentifier.has_value(), ALIRO_ERROR_INTERNAL, LOG_ERR("Reader identifier is not set")); + + identifier = mIdentifier.value(); + return ALIRO_NO_ERROR; +} + +void ReaderCache::ClearIdentifier() +{ + mIdentifier.reset(); +} + +AliroError ReaderCache::SetPublicKey(const CryptoTypes::PublicKey &publicKey) +{ + mPublicKey = publicKey; + return ALIRO_NO_ERROR; +} + +AliroError ReaderCache::GetPublicKey(CryptoTypes::PublicKey &publicKey) const +{ + VerifyOrReturnStatus(mPublicKey.has_value(), ALIRO_ERROR_INTERNAL, LOG_ERR("Reader public key is not set")); + + publicKey = mPublicKey.value(); + return ALIRO_NO_ERROR; +} + +void ReaderCache::ClearPublicKey() +{ + mPublicKey.reset(); +} + +bool ReaderCache::IsCertificateSet() const { return mCertificate.get() != nullptr; } -AliroError ReaderCertificateCache::SetCertificate(const Certificate &certificate) +AliroError ReaderCache::SetCertificate(const Certificate &certificate) { VerifyOrReturnStatus(certificate.mData != nullptr, ALIRO_INVALID_ARGUMENT, LOG_ERR("Certificate data is null")); VerifyOrReturnStatus(certificate.mLength != 0, ALIRO_INVALID_ARGUMENT, LOG_ERR("Certificate length is zero")); @@ -43,18 +86,18 @@ AliroError ReaderCertificateCache::SetCertificate(const Certificate &certificate return ALIRO_NO_ERROR; } -void ReaderCertificateCache::ClearCertificate() +void ReaderCache::ClearCertificate() { mCertificate.reset(); mCertificateLength = 0; } -bool ReaderCertificateCache::IsIssuerPublicKeySet() const +bool ReaderCache::IsIssuerPublicKeySet() const { return mIssuerPublicKey.has_value(); } -AliroError ReaderCertificateCache::GetCertificate(Certificate &certificate) const +AliroError ReaderCache::GetCertificate(Certificate &certificate) const { VerifyOrReturnStatus(mCertificate.get() != nullptr, ALIRO_ERROR_INTERNAL, LOG_ERR("Reader certificate is not set")); @@ -63,7 +106,7 @@ AliroError ReaderCertificateCache::GetCertificate(Certificate &certificate) cons return ALIRO_NO_ERROR; } -AliroError ReaderCertificateCache::SetIssuerPublicKey(const CryptoTypes::PublicKey &publicKey) +AliroError ReaderCache::SetIssuerPublicKey(const CryptoTypes::PublicKey &publicKey) { VerifyOrReturnStatus(publicKey[0] == CryptoTypes::kEccP256PublicKeyPrefix, ALIRO_INVALID_ARGUMENT, LOG_ERR("Invalid issuer public key prefix")); @@ -72,7 +115,7 @@ AliroError ReaderCertificateCache::SetIssuerPublicKey(const CryptoTypes::PublicK return ALIRO_NO_ERROR; } -AliroError ReaderCertificateCache::GetIssuerPublicKey(CryptoTypes::PublicKey &publicKey) const +AliroError ReaderCache::GetIssuerPublicKey(CryptoTypes::PublicKey &publicKey) const { VerifyOrReturnStatus(mIssuerPublicKey.has_value(), ALIRO_ERROR_INTERNAL, LOG_ERR("Issuer public key is not set")); @@ -81,7 +124,7 @@ AliroError ReaderCertificateCache::GetIssuerPublicKey(CryptoTypes::PublicKey &pu return ALIRO_NO_ERROR; } -void ReaderCertificateCache::ClearIssuerPublicKey() +void ReaderCache::ClearIssuerPublicKey() { mIssuerPublicKey.reset(); } diff --git a/app/src/aliro/storage/reader_certificate_cache.h b/app/src/aliro/storage/reader_cache.h similarity index 52% rename from app/src/aliro/storage/reader_certificate_cache.h rename to app/src/aliro/storage/reader_cache.h index 645841e2..4fd58b1f 100644 --- a/app/src/aliro/storage/reader_certificate_cache.h +++ b/app/src/aliro/storage/reader_cache.h @@ -15,13 +15,15 @@ namespace Aliro { /** - * @brief Singleton class for caching the Reader certificate. + * @brief Singleton class for caching Reader data. * - * This class manages the Reader certificate that is sent during the LOAD_CERT command. - * The certificate can be provisioned via CLI and is stored in RAM for quick access. - * If no certificate is provisioned, the LOAD_CERT state is skipped. + * This class manages Reader provisioning data used by the application: + * - Reader identifier + * - Reader public key + * - Reader certificate + * - Reader System Issuer public key */ -class ReaderCertificateCache { +class ReaderCache { public: /** * @brief Type alias for certificate data. @@ -33,7 +35,60 @@ class ReaderCertificateCache { * * @return Reference to the singleton instance. */ - static ReaderCertificateCache &Instance(); + static ReaderCache &Instance(); + + /** + * @brief Checks if the Reader identifier is set. + * + * @return True if the Reader identifier is set, false otherwise. + */ + bool IsIdentifierSet() const; + + /** + * @brief Sets the Reader identifier. + * + * @param identifier The Reader identifier. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError SetIdentifier(const Identifier &identifier); + + /** + * @brief Gets the Reader identifier. + * + * @param identifier The Reader identifier. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError GetIdentifier(Identifier &identifier) const; + + /** + * @brief Clears the Reader identifier. + */ + void ClearIdentifier(); + + /** + * @brief Sets the Reader public key. + * + * @param publicKey The Reader public key. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError SetPublicKey(const CryptoTypes::PublicKey &publicKey); + + /** + * @brief Gets the Reader public key. + * + * @param publicKey The Reader public key. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ + AliroError GetPublicKey(CryptoTypes::PublicKey &publicKey) const; + + /** + * @brief Clears the Reader public key. + */ + void ClearPublicKey(); /** * @brief Checks if the Reader certificate is set. @@ -95,17 +150,19 @@ class ReaderCertificateCache { void ClearIssuerPublicKey(); private: - ReaderCertificateCache() = default; - ~ReaderCertificateCache() = default; + ReaderCache() = default; + ~ReaderCache() = default; - ReaderCertificateCache(const ReaderCertificateCache &) = delete; - ReaderCertificateCache &operator=(const ReaderCertificateCache &) = delete; - ReaderCertificateCache(ReaderCertificateCache &&) = delete; - ReaderCertificateCache &operator=(ReaderCertificateCache &&) = delete; + ReaderCache(const ReaderCache &) = delete; + ReaderCache &operator=(const ReaderCache &) = delete; + ReaderCache(ReaderCache &&) = delete; + ReaderCache &operator=(ReaderCache &&) = delete; + std::optional mIdentifier{}; std::unique_ptr mCertificate{}; size_t mCertificateLength{ 0 }; std::optional mIssuerPublicKey{}; + std::optional mPublicKey{}; }; } // namespace Aliro diff --git a/app/src/aliro/storage/validity_iterations.cpp b/app/src/aliro/storage/validity_iterations.cpp index bbba29c9..80b85275 100644 --- a/app/src/aliro/storage/validity_iterations.cpp +++ b/app/src/aliro/storage/validity_iterations.cpp @@ -4,7 +4,6 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE #include "validity_iterations.h" #include "aliro/utils.h" @@ -56,5 +55,3 @@ AliroError ClearValidityIterations(size_t credentialIssuerKeyIndex) } } // namespace Aliro - -#endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE diff --git a/app/src/aliro/storage/validity_iterations.h b/app/src/aliro/storage/validity_iterations.h index 90e889b9..1dd17167 100644 --- a/app/src/aliro/storage/validity_iterations.h +++ b/app/src/aliro/storage/validity_iterations.h @@ -4,8 +4,6 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE - #pragma once #include "aliro/errors.h" @@ -26,5 +24,3 @@ AliroError StoreValidityIterations(size_t credentialIssuerKeyIndex, const Validi AliroError ClearValidityIterations(size_t credentialIssuerKeyIndex); } // namespace Aliro - -#endif // CONFIG_DOOR_LOCK_STEP_UP_PHASE diff --git a/app/src/aliro/test_key.h b/app/src/aliro/test_key.h deleted file mode 100644 index c1260b0a..00000000 --- a/app/src/aliro/test_key.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2024 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#pragma once - -#include "aliro/types.h" - -/* ECDSA long term keys generated with openssl: - openssl ecparam -name secp256k1 -genkey -noout -out ec-secp256k1-priv-key.pem - openssl ec -in ec-secp256k1-priv-key.pem -pubout > ec-secp256k1-pub-key.pem -*/ -constexpr Aliro::CryptoTypes::PrivateKey mPrivateKey = - Aliro::CryptoTypes::PrivateKey{ 0xfd, 0xf7, 0x1a, 0x37, 0x14, 0xe0, 0x78, 0xc2, 0xc2, 0xfa, 0x90, - 0x7a, 0xe9, 0xac, 0xf6, 0x24, 0xaa, 0x98, 0xad, 0xd7, 0xed, 0xf7, - 0x50, 0x0e, 0x61, 0xcf, 0x8a, 0xf4, 0xcc, 0x5a, 0x70, 0xa9 }; diff --git a/app/src/aliro/utils/hex_string.h b/app/src/aliro/utils/hex_string.h new file mode 100644 index 00000000..976f6a45 --- /dev/null +++ b/app/src/aliro/utils/hex_string.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace DoorLock::Utils { + +namespace Detail { + +template using RemoveCvRefType = std::remove_cv_t>; + +template struct IsByteArray : std::false_type {}; + +template struct IsByteArray> : std::true_type {}; + +template inline constexpr bool kIsByteArray = IsByteArray>::value; + +template constexpr size_t GetArraySize() +{ + using ObjectType = RemoveCvRefType; + static_assert(kIsByteArray, "Object type must be std::array"); + return std::tuple_size::value; +} + +template struct HexStringBufferType; + +template struct HexStringBufferType> { + using Type = std::array; +}; + +} // namespace Detail + +/** + * @brief Hex string buffer type for a std::array object type. + * + * Produces a null-terminated character buffer large enough to hold the + * hexadecimal representation of @p TObject: + * - 2 characters per byte + * - plus 1 character for the null terminator + * + * @tparam TObject `std::array` object type to be formatted. + */ +template +using HexStringBuffer = typename Detail::HexStringBufferType>::Type; + +/** + * @brief Convert a std::array object to a hexadecimal C-string. + * + * The function formats up to @p length bytes from @p object into @p buffer + * using `bin2hex`. If @p length is omitted, it defaults to the number of + * elements in the array (`N` for `std::array`). + * + * Compile-time checks enforce: + * - @p TObject is `std::array` + * - @p TBuffer matches `HexStringBuffer` + * + * @tparam TBuffer Character buffer type, expected to be `HexStringBuffer`. + * @tparam TObject Input object type, expected to be `std::array`. + * @param buffer Output hex string buffer. + * @param object Input data object to format. + * @param length Number of bytes to print. Defaults to `N`. + * + * @retval true Conversion completed and produced exactly `length * 2` characters. + * @retval false `length` exceeds object byte size or conversion result is invalid. + */ +template +inline bool ArrayToHexString(TBuffer &buffer, const TObject &object, size_t length = Detail::GetArraySize()) +{ + using ObjectType = Detail::RemoveCvRefType; + using BufferType = Detail::RemoveCvRefType; + + static_assert(Detail::kIsByteArray, "Object type must be std::array"); + static_assert(std::is_same_v>, + "Buffer type must match the object hex buffer type"); + + if (length > Detail::GetArraySize()) { + return false; + } + + const size_t convertedLength = bin2hex(object.data(), length, buffer.data(), buffer.size()); + return convertedLength == length * 2; +} + +} // namespace DoorLock::Utils diff --git a/app/src/bt_nus/Kconfig b/app/src/bt_nus/Kconfig index 08fefa03..d154d097 100644 --- a/app/src/bt_nus/Kconfig +++ b/app/src/bt_nus/Kconfig @@ -4,6 +4,20 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +menuconfig DOOR_LOCK_BLE_NUS + bool "Nordic UART Service (NUS)" + depends on !CHIP + help + Enables the Bluetooth LE Nordic Uart Service (NUS). Using NUS service you can control a door lock using pre-defined BLE commands. + +if DOOR_LOCK_BLE_NUS + +config DOOR_LOCK_BT_NUS_MAX_COMMANDS + int "Maximum number of NUS commands" + default 2 + help + Define the maximum number of NUS commands to declare by user. + config DOOR_LOCK_BT_NUS_MAX_COMMAND_LEN int "Maximum length of single command in Bytes" default 10 @@ -12,14 +26,10 @@ config DOOR_LOCK_BT_NUS_MAX_COMMAND_LEN a paired smart door lock. config DOOR_LOCK_BT_NUS_APP_PASSKEY - int "Define the default passkey for NUS" + int "Default passkey" depends on BT_APP_PASSKEY default 123456 help Define the default password for pairing with the Bluetooth LE device. -config DOOR_LOCK_BT_NUS_MAX_COMMANDS - int "Define maximum NUS commands amount" - default 1 - help - Define the maximum number of NUS commands to declare by user. +endif # DOOR_LOCK_BLE_NUS diff --git a/app/src/bt_nus/bt_nus.cpp b/app/src/bt_nus/bt_nus.cpp index 72c6ed99..f5894c0e 100644 --- a/app/src/bt_nus/bt_nus.cpp +++ b/app/src/bt_nus/bt_nus.cpp @@ -6,30 +6,27 @@ #include "bt_nus.h" -#include "aliro/ble_types.h" #include "aliro/utils.h" -#include "aliro/platform/ble/ble_manager_impl.h" +#include "aliro/platform/ble/ble_advertising_arbiter.h" #include #include #include #include +#include + LOG_MODULE_REGISTER(NusService, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); namespace Aliro::BtNus { -using BleInterface::BleManagerImpl; +namespace BleArbiter = DoorLock::Interface::BleAdvertisingArbiter; AliroError NUSService::Start() { VerifyOrReturnStatus(!mIsStarted, ALIRO_INVALID_STATE, LOG_ERR("NUS service is already started")); -#if !defined(CONFIG_DOOR_LOCK_DFU_BLE_SMP) && !defined(CONFIG_DOOR_LOCK_BLE_UWB) - VerifyOrReturnStatus(BleManagerImpl::Instance().Init({}) == ALIRO_NO_ERROR, ALIRO_ERROR_INTERNAL); -#endif // !CONFIG_DOOR_LOCK_DFU_BLE_SMP && !CONFIG_DOOR_LOCK_BLE_UWB - static bt_conn_auth_cb sConnAuthCallbacks = { .passkey_display = [](bt_conn *conn, unsigned int passkey) { Instance().AuthPasskeyDisplay(conn, passkey); }, @@ -45,8 +42,7 @@ AliroError NUSService::Start() static bt_conn_auth_info_cb sConnAuthInfoCallbacks = { .pairing_complete = [](bt_conn *conn, bool bonded) { Instance().PairingComplete(conn, bonded); }, - .pairing_failed = [](bt_conn *conn, - enum bt_security_err reason) { Instance().PairingFailed(conn, reason); }, + .pairing_failed = [](bt_conn *conn, bt_security_err reason) { Instance().PairingFailed(conn, reason); }, }; static bt_nus_cb sNusCallbacks = { @@ -61,15 +57,21 @@ AliroError NUSService::Start() VerifyOrReturnStatus(bt_nus_init(&sNusCallbacks) == 0, ALIRO_ERROR_INTERNAL, LOG_ERR("Failed to initialize NUS service")); -#ifndef CONFIG_DOOR_LOCK_BLE_UWB + mRequest = BleArbiter::Request{ .mOptions = BT_LE_ADV_OPT_CONN, + .mMinInterval = BT_GAP_ADV_FAST_INT_MIN_2, + .mMaxInterval = BT_GAP_ADV_FAST_INT_MAX_2 }; - AliroError err = BleManagerImpl::Instance().StartAdvertising({ kNusUuid.data(), kNusUuid.size() }, - BleTypes::AdvertisingDataFieldType::Uuid128All); + // Set advertising data buffers + mRequest.mAdvertisingData[0] = BT_DATA(BT_DATA_FLAGS, &kAdvertisingFlags, sizeof(kAdvertisingFlags)); + mRequest.mAdvertisingData[1] = BT_DATA(BT_DATA_UUID128_ALL, kNusUuid.data(), kNusUuid.size()); - VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, - LOG_ERR("NUS advertising failed to start (rc %d)", err.ToInt())); + const char *deviceName = bt_get_name(); + mRequest.mScanResponseData[0] = + BT_DATA(BT_DATA_NAME_COMPLETE, deviceName, static_cast(strlen(deviceName))); -#endif // CONFIG_DOOR_LOCK_BLE_UWB + AliroError err = BleArbiter::InsertRequest(BleArbiter::Component::Nus, mRequest); + VerifyOrReturnStatus(err == ALIRO_NO_ERROR, err, + LOG_ERR("NUS advertising request failed (rc %d)", err.ToInt())); mIsStarted = true; LOG_INF("NUS service started"); @@ -80,8 +82,7 @@ void NUSService::StopServer() { VerifyOrReturn(IsNusStarted(), LOG_ERR("NUS service not started")); - AliroError err = BleManagerImpl::Instance().StopAdvertising(); - VerifyOrReturn(err == ALIRO_NO_ERROR, LOG_ERR("NUS advertising failed to stop (rc %d)", err.ToInt())); + BleArbiter::CancelRequest(BleArbiter::Component::Nus); mIsStarted = false; LOG_INF("NUS service stopped"); @@ -169,7 +170,7 @@ void NUSService::Connected(bt_conn *conn, uint8_t err) mBTConnection = conn; bt_conn_set_security(conn, BT_SECURITY_L3); - LOG_DBG("NUS connected"); + LOG_INF("NUS connected"); } void NUSService::Disconnected(bt_conn *, uint8_t reason) @@ -177,7 +178,7 @@ void NUSService::Disconnected(bt_conn *, uint8_t reason) VerifyOrReturn(IsNusStarted(), LOG_DBG("NUS service not started, ignoring disconnection")); mBTConnection = nullptr; - LOG_DBG("NUS disconnected (reason: %u)", reason); + LOG_INF("NUS disconnected (reason: %u)", reason); } void NUSService::SecurityChanged(bt_conn *conn, bt_security_t level, bt_security_err err) @@ -215,7 +216,7 @@ void NUSService::PairingComplete(bt_conn *conn, bool bonded) LOG_DBG("NUS BT Pairing completed: %s, bonded: %d", GetAddressString(conn), bonded); } -void NUSService::PairingFailed(bt_conn *conn, enum bt_security_err reason) +void NUSService::PairingFailed(bt_conn *conn, bt_security_err reason) { VerifyOrReturn(IsNusStarted(), LOG_DBG("NUS service not started, ignoring pairing failed")); LOG_ERR("NUS BT Pairing failed to %s : reason %d", GetAddressString(conn), static_cast(reason)); diff --git a/app/src/bt_nus/bt_nus.h b/app/src/bt_nus/bt_nus.h index f57f426d..3a605381 100644 --- a/app/src/bt_nus/bt_nus.h +++ b/app/src/bt_nus/bt_nus.h @@ -7,6 +7,7 @@ #pragma once #include "aliro/errors.h" +#include "aliro/platform/ble/ble_advertising_arbiter.h" #include #include @@ -102,6 +103,12 @@ class NUSService { // NUS service UUID. static constexpr std::array kNusUuid{ BT_UUID_NUS_VAL }; + + // Advertising flags. + static constexpr uint8_t kAdvertisingFlags{ BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR }; + + // Advertising data storage. + DoorLock::Interface::BleAdvertisingArbiter::Request mRequest{}; }; } // namespace Aliro::BtNus diff --git a/app/src/dfu_smp/CMakeLists.txt b/app/src/dfu_smp/CMakeLists.txt index 7be6a6e5..bd30794d 100644 --- a/app/src/dfu_smp/CMakeLists.txt +++ b/app/src/dfu_smp/CMakeLists.txt @@ -8,7 +8,7 @@ zephyr_include_directories(.) zephyr_library_sources(dfu_smp_manager.cpp) -if(NOT CONFIG_DOOR_LOCK_BLE_UWB AND NOT CONFIG_DOOR_LOCK_BLE_NUS AND CONFIG_SHELL) +if(CONFIG_SHELL) zephyr_library_sources(dfu_smp_shell.cpp) endif() diff --git a/app/src/dfu_smp/Kconfig b/app/src/dfu_smp/Kconfig index 3e323a78..19b2f5a3 100644 --- a/app/src/dfu_smp/Kconfig +++ b/app/src/dfu_smp/Kconfig @@ -4,8 +4,18 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +menuconfig DOOR_LOCK_DFU_BLE_SMP + bool "DFU over SMP" + depends on !CHIP + help + Enables Device Firmware Upgrade over Bluetooth LE. + +if DOOR_LOCK_DFU_BLE_SMP + module = DFU_SMP module-str = DFU_SMP module-dep = LOG module-help = Enables DFU SMP module log messages. source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endif # DOOR_LOCK_DFU_BLE_SMP diff --git a/app/src/dfu_smp/dfu_smp_manager.cpp b/app/src/dfu_smp/dfu_smp_manager.cpp index 17f01604..80d68f17 100644 --- a/app/src/dfu_smp/dfu_smp_manager.cpp +++ b/app/src/dfu_smp/dfu_smp_manager.cpp @@ -6,11 +6,12 @@ #include "dfu_smp_manager.h" -#include "aliro/aliro.h" #include "aliro/ble_types.h" #include "aliro/utils.h" -#include "aliro/platform/ble/ble_manager_impl.h" +#include "aliro/aliro_work/aliro_work.h" + +#include "aliro/platform/ble/ble_advertising_arbiter.h" #include #include @@ -18,6 +19,9 @@ #include #include +#include +#include + LOG_MODULE_REGISTER(SmpManager, CONFIG_DFU_SMP_LOG_LEVEL); namespace { @@ -35,9 +39,7 @@ mgmt_cb_return UploadConfirmHandler(uint32_t, mgmt_cb_return, int32_t *, uint16_ namespace Aliro::Dfu { -#ifndef CONFIG_DOOR_LOCK_BLE_UWB - -using BleInterface::BleManagerImpl; +namespace BleArbiter = DoorLock::Interface::BleAdvertisingArbiter; AliroError SmpManager::InitButton() { @@ -61,34 +63,37 @@ AliroError SmpManager::InitButton() void SmpManager::StartAdvertising() { - AliroError err{}; + mRequest = BleArbiter::Request{ .mOptions = BT_LE_ADV_OPT_CONN, + .mMinInterval = BT_GAP_ADV_FAST_INT_MIN_2, + .mMaxInterval = BT_GAP_ADV_FAST_INT_MAX_2 }; - err = BleManagerImpl::Instance().StartAdvertising({ kSmpUuid.data(), kSmpUuid.size() }, - BleTypes::AdvertisingDataFieldType::Uuid128All); - VerifyOrReturn(err == ALIRO_NO_ERROR, LOG_ERR("Dfu SMP advertising failed to start (rc %d)", err.ToInt())); + // Set advertising data buffers + mRequest.mAdvertisingData[0] = BT_DATA(BT_DATA_FLAGS, &kAdvertisingFlags, sizeof(kAdvertisingFlags)); + mRequest.mAdvertisingData[1] = BT_DATA(BT_DATA_UUID128_ALL, kSmpUuid.data(), kSmpUuid.size()); + + const char *deviceName = bt_get_name(); + mRequest.mScanResponseData[0] = + BT_DATA(BT_DATA_NAME_COMPLETE, deviceName, static_cast(strlen(deviceName))); + + AliroError err = BleArbiter::InsertRequest(BleArbiter::Component::Smp, mRequest); + VerifyOrReturn(err == ALIRO_NO_ERROR, LOG_ERR("DFU SMP advertising request failed (rc %d)", err.ToInt())); - LOG_INF("DFU SMP advertising started"); mIsAdvEnabled = true; } void SmpManager::StopAdvertising() { - AliroError err = BleManagerImpl::Instance().StopAdvertising(); - VerifyOrReturn(err == ALIRO_NO_ERROR, LOG_ERR("Dfu SMP advertising failed to stop (rc %d)", err.ToInt())); - + BleArbiter::CancelRequest(BleArbiter::Component::Smp); LOG_INF("DFU SMP advertising stopped"); - mIsAdvEnabled = false; } void SmpManager::Toggle() { VerifyOrReturn(mIsInitialized, LOG_ERR("DFU SMP module not initialized")); - k_work_submit(&mWork); + std::ignore = AliroWorkSubmit(&mWork); } -#endif // !CONFIG_DOOR_LOCK_BLE_UWB - AliroError SmpManager::Init() { static mgmt_callback sUploadCallback = { @@ -98,8 +103,6 @@ AliroError SmpManager::Init() mgmt_callback_register(&sUploadCallback); -#ifndef CONFIG_DOOR_LOCK_BLE_UWB - k_work_init(&mWork, []([[maybe_unused]] k_work *) { if (Instance().IsSmpEnabled()) { Instance().StopAdvertising(); @@ -108,13 +111,8 @@ AliroError SmpManager::Init() } }); - PlatformTransportCallbacks emptyCallbacks{}; - VerifyOrReturnStatus(BleManagerImpl::Instance().Init(emptyCallbacks) == ALIRO_NO_ERROR, ALIRO_ERROR_INTERNAL); - VerifyOrReturnStatus(InitButton() == ALIRO_NO_ERROR, ALIRO_ERROR_INTERNAL); -#endif // !CONFIG_DOOR_LOCK_BLE_UWB - mIsInitialized = true; LOG_DBG("DFU SMP module initialized"); diff --git a/app/src/dfu_smp/dfu_smp_manager.h b/app/src/dfu_smp/dfu_smp_manager.h index c4da6184..85e0ce8e 100644 --- a/app/src/dfu_smp/dfu_smp_manager.h +++ b/app/src/dfu_smp/dfu_smp_manager.h @@ -7,8 +7,10 @@ #pragma once #include "aliro/errors.h" +#include "aliro/platform/ble/ble_advertising_arbiter.h" #include +#include #include #include #include @@ -88,6 +90,12 @@ class SmpManager { // DFU SMP service UUID. static constexpr std::array kSmpUuid{ BT_UUID_DFU_SMP_SERVICE_VAL }; + + // Advertising flags. + static constexpr uint8_t kAdvertisingFlags{ BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR }; + + // Advertising data storage. + DoorLock::Interface::BleAdvertisingArbiter::Request mRequest{}; }; } // namespace Aliro::Dfu diff --git a/app/src/dfu_smp/dfu_smp_shell.cpp b/app/src/dfu_smp/dfu_smp_shell.cpp index 7864293b..a6ca9b4e 100644 --- a/app/src/dfu_smp/dfu_smp_shell.cpp +++ b/app/src/dfu_smp/dfu_smp_shell.cpp @@ -4,33 +4,42 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#include "dfu_smp_shell.h" #include "dfu_smp_manager.h" -#include "aliro/utils.h" + +#include + #include -int ShellCmdHandleDfuSmp(const struct shell *shell, size_t argc, char **argv) +namespace { + +int ShellCmdHandleDfuSmpOn(const struct shell *shell, size_t argc, char **) { - using namespace Aliro::Dfu; - - VerifyOrReturnValue(IN_RANGE(argc, 2, 2), -EINVAL, shell_print(shell, "Usage: dl dfu_smp ")); - - if (strcmp(argv[1], "on") == 0) { - VerifyOrReturnValue(!SmpManager::Instance().IsSmpEnabled(), -EINVAL, - shell_print(shell, "DFU BLE SMP already started")); - SmpManager::Instance().Toggle(); - shell_print(shell, "DFU BLE SMP started"); - return 0; - } - - if (strcmp(argv[1], "off") == 0) { - VerifyOrReturnValue(SmpManager::Instance().IsSmpEnabled(), -EINVAL, - shell_print(shell, "DFU BLE SMP already stopped")); - SmpManager::Instance().Toggle(); - shell_print(shell, "DFU BLE SMP stopped"); - return 0; - } - - shell_print(shell, "Usage: dl dfu_smp "); - return -EINVAL; + VerifyOrReturnValue(argc == 1, -EINVAL, shell_print(shell, "Usage: dl dfu_smp on")); + + VerifyOrReturnValue(!Aliro::Dfu::SmpManager::Instance().IsSmpEnabled(), -EINVAL, + shell_print(shell, "DFU BLE SMP already started")); + Aliro::Dfu::SmpManager::Instance().Toggle(); + + shell_print(shell, "DFU BLE SMP started"); + return 0; } + +int ShellCmdHandleDfuSmpOff(const struct shell *shell, size_t argc, char **) +{ + VerifyOrReturnValue(argc == 1, -EINVAL, shell_print(shell, "Usage: dl dfu_smp off")); + + VerifyOrReturnValue(Aliro::Dfu::SmpManager::Instance().IsSmpEnabled(), -EINVAL, + shell_print(shell, "DFU BLE SMP already stopped")); + Aliro::Dfu::SmpManager::Instance().Toggle(); + + shell_print(shell, "DFU BLE SMP stopped"); + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(dfu_smp_cmd, SHELL_CMD(on, NULL, "Enable DFU BLE SMP", ShellCmdHandleDfuSmpOn), + SHELL_CMD(off, NULL, "Disable DFU BLE SMP", ShellCmdHandleDfuSmpOff), + SHELL_SUBCMD_SET_END); + +} // namespace + +SHELL_SUBCMD_ADD((dl), dfu_smp, &dfu_smp_cmd, "Enable/disable DFU BLE SMP: dl dfu_smp ", NULL, 0, 0); diff --git a/app/src/dfu_smp/dfu_smp_shell.h b/app/src/dfu_smp/dfu_smp_shell.h deleted file mode 100644 index e21fef78..00000000 --- a/app/src/dfu_smp/dfu_smp_shell.h +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#pragma once - -#include - -int ShellCmdHandleDfuSmp(const struct shell *shell, size_t argc, char **argv); diff --git a/app/src/main.cpp b/app/src/main.cpp index a742c530..e6e73992 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -8,11 +8,12 @@ #include "matter/init.h" #else // CONFIG_CHIP #include "aliro/init.h" +#include "aliro/lock_sim/lock_sim_instance.h" #endif // CONFIG_CHIP #include "aliro/utils.h" -#include "crypto/crypto.h" +#include #include #include @@ -23,15 +24,11 @@ #ifdef CONFIG_DOOR_LOCK_BLE_NUS -#ifdef CONFIG_ACCESS_DECISION_INDICATOR -#include "aliro/platform/access_decision_indicator/access_decision_indicator.h" -#endif // CONFIG_ACCESS_DECISION_INDICATOR - #include "bt_nus/bt_nus.h" #endif // CONFIG_DOOR_LOCK_BLE_NUS #ifdef CONFIG_DOOR_LOCK_BLE_UWB -#include "access_manager/access_manager.h" +#include "access_manager.h" #include "uwb_impl.h" #endif // CONFIG_DOOR_LOCK_BLE_UWB @@ -43,8 +40,8 @@ LOG_MODULE_REGISTER(door_lock_app, CONFIG_DOOR_LOCK_APP_LOG_LEVEL); int main() { - auto error = Aliro::CryptoInstance().Init(); - VerifyOrReturnValue(error == ALIRO_NO_ERROR, EXIT_FAILURE, LOG_ERR("Cannot initialize crypto engine.")); + auto error = DoorLock::Crypto::Init(); + VerifyOrDie(error == ALIRO_NO_ERROR, "Failed to initialize Aliro crypto"); #ifdef CONFIG_DOOR_LOCK_BLE_UWB @@ -93,10 +90,15 @@ int main() "Unlock", strlen("Unlock"), [](void *context) { LOG_INF("Unlock command received"); + Aliro::LockSimInstance().Unlock(Aliro::OperationSource::Unspecified); + }, + nullptr); -#ifdef CONFIG_ACCESS_DECISION_INDICATOR - Aliro::Access::Indicator::SignalAccessGranted(); -#endif // CONFIG_ACCESS_DECISION_INDICATOR + Aliro::BtNus::NUSService::Instance().RegisterCommand( + "Lock", strlen("Lock"), + [](void *context) { + LOG_INF("Lock command received"); + Aliro::LockSimInstance().Lock(Aliro::OperationSource::Unspecified); }, nullptr); diff --git a/app/src/matter/CMakeLists.txt b/app/src/matter/CMakeLists.txt index af8e92c6..233f4bd6 100644 --- a/app/src/matter/CMakeLists.txt +++ b/app/src/matter/CMakeLists.txt @@ -31,25 +31,28 @@ target_include_directories(app PRIVATE ${ZAP_PARENT_DIR} ) -SET(access_srcs +target_sources(app PRIVATE + app_task.cpp + bolt_lock_manager.cpp + door_lock_delegate.cpp + init.cpp + zcl_callbacks.cpp access/access_data_types.cpp access/access_manager_credentials.cpp access/access_manager_users.cpp access/access_manager.cpp - access/access_storage.cpp ) -if(CONFIG_LOCK_SCHEDULES) - LIST(APPEND access_srcs access/access_manager_schedules.cpp) -endif() +target_sources_ifdef(CONFIG_LOCK_ACCESS_STORAGE_PROTECTED_STORAGE app PRIVATE + access/access_storage_psa_ps.cpp +) -target_sources(app PRIVATE - app_task.cpp - bolt_lock_manager.cpp - door_lock_delegate.cpp - init.cpp - zcl_callbacks.cpp - ${access_srcs} +target_sources_ifdef(CONFIG_LOCK_SCHEDULES app PRIVATE + access/access_manager_schedules.cpp +) + +target_sources_ifdef(CONFIG_LOCK_PRINT_STORAGE_STATUS app PRIVATE + access/access_storage_print.cpp ) # Do not treat warnings as errors while the variable may be uninitialized for this sample data model. diff --git a/app/src/matter/Kconfig b/app/src/matter/Kconfig index 751dd4a3..9b50ec0a 100644 --- a/app/src/matter/Kconfig +++ b/app/src/matter/Kconfig @@ -4,8 +4,6 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -mainmenu "Matter Lock sample application" - config LOCK_MAX_NUM_USERS int "Maximum number of users supported by the lock" default 10 @@ -64,6 +62,8 @@ if LOCK_ENABLE_DEBUG config LOCK_PRINT_STORAGE_STATUS bool "Print storage status after each store call" + # This feature assumes that items are eventually stored in Zephyr settings subsystem + depends on TRUSTED_STORAGE_STORAGE_BACKEND_SETTINGS help Debug feature to print the debug-level log that contains information of the entry being stored to persistent storage and how many bytes are left to store the new entry. It can be used to verify @@ -105,39 +105,24 @@ config OPENTHREAD_DEFAULT_TX_POWER endif # OPENTHREAD -config NCS_SAMPLE_MATTER_PERSISTENT_STORAGE - default y - -# PSA SSF Crypto Client is not ready for the secure storage backend -if !PSA_SSF_CRYPTO_CLIENT - -config NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND - default y if !CHIP_WIFI - -config NCS_SAMPLE_MATTER_SETTINGS_STORAGE_BACKEND - default n if !CHIP_WIFI - -endif - -# Increase the storage capacity if the schedules are enabled with secure storage -# This also implies increasing of the OT and Matter stacks because some operations -# performed during commissioning seem to allocate stack buffers based on the -# maximum possible secure asset size. -if LOCK_SCHEDULES && NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND - -config NCS_SAMPLE_MATTER_SECURE_STORAGE_MAX_ENTRY_NUMBER - default 128 +choice LOCK_ACCESS_STORAGE_CHOICE + prompt "Access storage implementation" + default LOCK_ACCESS_STORAGE_PROTECTED_STORAGE -config TRUSTED_STORAGE_BACKEND_AEAD_MAX_DATA_SIZE - default 3072 +config LOCK_ACCESS_STORAGE_PROTECTED_STORAGE + bool "PSA Protected Storage" + # PSA SSF Crypto Client is not ready for PSA PS backend + depends on !CHIP_WIFI && !PSA_SSF_CRYPTO_CLIENT + # When building with TF-M, Protected Storage is enabled by default + select TRUSTED_STORAGE if !BUILD_WITH_TFM + select PSA_PROTECTED_STORAGE if !BUILD_WITH_TFM -config OPENTHREAD_THREAD_STACK_SIZE - default 7168 +endchoice -config CHIP_TASK_STACK_SIZE - default 10240 +if LOCK_ACCESS_STORAGE_PROTECTED_STORAGE -config MAIN_STACK_SIZE - default 7168 +config LOCK_ACCESS_STORAGE_PROTECTED_STORAGE_UID_OFFSET + int "Access storage PSA Protected Storage UID range start" + default 0 -endif +endif # LOCK_ACCESS_STORAGE_PROTECTED_STORAGE diff --git a/app/src/matter/access/access_data_types.h b/app/src/matter/access/access_data_types.h index 52e3ddac..8672bc57 100644 --- a/app/src/matter/access/access_data_types.h +++ b/app/src/matter/access/access_data_types.h @@ -491,6 +491,9 @@ using CredentialsBits = uint16_t; constexpr CredentialsBits ToCredentialBits(CredentialTypeEnum type) { + static_assert(static_cast(CredentialTypeEnum::kUnknownEnumValue) < sizeof(CredentialsBits) * 8, + "CredentialsBits does not have enough bits to represent all CredentialTypeEnum values"); + return BIT(chip::to_underlying(type)); } diff --git a/app/src/matter/access/access_manager.cpp b/app/src/matter/access/access_manager.cpp index 026de1b8..3d6d1acf 100644 --- a/app/src/matter/access/access_manager.cpp +++ b/app/src/matter/access/access_manager.cpp @@ -11,11 +11,38 @@ #include +#include + LOG_MODULE_REGISTER(cr_manager, CONFIG_CHIP_APP_LOG_LEVEL); using namespace chip; using namespace DoorLockData; +namespace { +/* + * For each itemIndex in indexList: calls AccessStorage::Remove(itemType, [extraArgs...,] itemIndex) + * Finally: calls AccessStorage::Remove(indexesType[, extraArgs...]) + */ +template +void RemoveCollection(const DoorLockData::IndexList &indexList, AccessStorage::Type itemType, + AccessStorage::Type indexesType, ExtraArgs &&...extraArgs) +{ + for (size_t i = 0; i < indexList.mList.mLength; i++) { + uint16_t itemIndex = indexList.mList.mIndexes[i]; + + if (!AccessStorage::Instance().Remove(itemType, std::forward(extraArgs)..., itemIndex)) { + LOG_ERR("Cannot remove item %u with type %u", itemIndex, static_cast(itemType)); + } + } + + if (indexList.mList.mLength > 0) { + if (!AccessStorage::Instance().Remove(indexesType, std::forward(extraArgs)...)) { + LOG_ERR("Cannot remove indexes with type %u", static_cast(indexesType)); + } + } +} +} /* namespace */ + template void AccessManager::Init(SetOrLoadCredentialCallback setCredentialClbk, ClearCredentialCallback clearCredentialClbk, @@ -37,8 +64,33 @@ void AccessManager::Init(SetOrLoadCredentialCallback setCredentia template void AccessManager::FactoryReset() { - /* Factory reset the storage */ - AccessStorage::Instance().FactoryReset(); + /* Remove users */ + RemoveCollection(mUsersIndexes, AccessStorage::Type::User, AccessStorage::Type::UsersIndexes); + + /* Remove credentials */ + for (auto type = to_underlying(CredentialTypeEnum::kPin); + type < to_underlying(CredentialTypeEnum::kUnknownEnumValue); ++type) { + RemoveCollection(mCredentialsIndexes.Get(static_cast(type)), + AccessStorage::Type::Credential, AccessStorage::Type::CredentialsIndexes, type); + } + +#ifdef CONFIG_LOCK_SCHEDULES + /* Remove schedules */ + for (uint16_t userIndex = 1; userIndex <= CONFIG_LOCK_MAX_NUM_USERS; userIndex++) { + RemoveCollection(mWeekDayScheduleIndexes.Get(userIndex), AccessStorage::Type::WeekDaySchedule, + AccessStorage::Type::WeekDayScheduleIndexes, userIndex); + RemoveCollection(mYearDayScheduleIndexes.Get(userIndex), AccessStorage::Type::YearDaySchedule, + AccessStorage::Type::YearDayScheduleIndexes, userIndex); + } + + RemoveCollection(mHolidayScheduleIndexes, AccessStorage::Type::HolidaySchedule, + AccessStorage::Type::HolidayScheduleIndexes); +#endif + + /* Remove other data */ + if (!AccessStorage::Instance().Remove(AccessStorage::Type::RequirePIN)) { + LOG_ERR("Cannot remove RequirePINforRemoteOperation"); + } /* Reinitialize to clear removed users/credentials/schedules */ InitializeAllCredentials(); diff --git a/app/src/matter/access/access_storage.cpp b/app/src/matter/access/access_storage.cpp deleted file mode 100644 index 9e611141..00000000 --- a/app/src/matter/access/access_storage.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#include "access_storage.h" - -#include - -#include - -#ifdef CONFIG_LOCK_PRINT_STORAGE_STATUS -#ifdef CONFIG_SETTINGS_NVS -#include -#else /* CONFIG_SETTINGS_ZMS */ -#include -#endif /* CONFIG_SETTINGS_NVS || CONFIG_SETTINGS_ZMS */ -#include -#include - -LOG_MODULE_DECLARE(storage_manager, CONFIG_CHIP_APP_LOG_LEVEL); - -namespace { -bool GetStorageFreeSpace(size_t &freeBytes) -{ - void *storage = nullptr; - int status = settings_storage_get(&storage); - if (status != 0 || !storage) { - LOG_ERR("AccessStorage: Cannot read NVS free space [error: %d]", status); - return false; - } -#ifdef CONFIG_SETTINGS_NVS - freeBytes = nvs_calc_free_space(static_cast(storage)); -#else /* CONFIG_SETTINGS_ZMS */ - freeBytes = zms_calc_free_space(static_cast(storage)); -#endif /* CONFIG_SETTINGS_NVS || CONFIG_SETTINGS_ZMS */ - return true; -} -} /* namespace */ -#endif /* CONFIG_LOCK_PRINT_STORAGE_STATUS */ - -/* Currently the secure storage is available only for non-Wi-Fi builds, - because NCS Wi-Fi implementation does not support PSA API yet. */ -#if defined(CONFIG_NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND) && defined(CONFIG_CHIP_WIFI) -#error CONFIG_NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND is currently not available if CONFIG_CHIP_WIFI is set. -#endif - -/* Prefer to use CONFIG_NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND if both backends are set simultaneously */ -#ifdef CONFIG_NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND -#define PSInit SecureInit -#define PSStore SecureStore -#define PSRemove SecureRemove -#define PSLoad SecureLoad -#define PSFactoryReset SecureFactoryReset -#elif defined(CONFIG_NCS_SAMPLE_MATTER_SETTINGS_STORAGE_BACKEND) -#define PSInit NonSecureInit -#define PSStore NonSecureStore -#define PSLoad NonSecureLoad -#define PSRemove NonSecureRemove -#define PSFactoryReset NonSecureFactoryReset -#endif - -bool AccessStorage::Init() -{ - return Nrf::PSErrorCode::Success == Nrf::GetPersistentStorage().PSInit(&mRootNode); -} - -void AccessStorage::FactoryReset() -{ - Nrf::GetPersistentStorage().PSFactoryReset(); -} - -bool AccessStorage::PrepareKeyName(Type storageType, uint16_t index, uint16_t subindex) -{ - memset(mKeyName, '\0', sizeof(mKeyName)); - - uint8_t limitedIndex = static_cast(index); - uint8_t limitedSubindex = static_cast(subindex); - - switch (storageType) { - case Type::User: - if (0 == limitedIndex && 0 == limitedSubindex) { - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s", kAccessPrefix, kUserPrefix); - } else if (0 == limitedSubindex) { - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s/%u", kAccessPrefix, kUserPrefix, limitedIndex); - } else { - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s/%u/%u", kAccessPrefix, kUserPrefix, - limitedIndex, limitedSubindex); - } - return true; - case Type::Credential: - if (0 == limitedIndex && 0 == limitedSubindex) { - (void)snprintf(mKeyName, kMaxAccessName, "%s", kAccessPrefix); - } else if (0 == limitedSubindex) { - (void)snprintf(mKeyName, kMaxAccessName, "%s/%u", kAccessPrefix, limitedIndex); - } else { - (void)snprintf(mKeyName, kMaxAccessName, "%s/%u/%u", kAccessPrefix, limitedIndex, - limitedSubindex); - } - return true; - case Type::UsersIndexes: - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s", kAccessPrefix, kUserCounterPrefix); - return true; - case Type::CredentialsIndexes: - (void)snprintf(mKeyName, kMaxAccessName, "%s/%u/%s", kAccessPrefix, limitedIndex, kAccessPrefix); - return true; - case Type::RequirePIN: - (void)snprintf(mKeyName, kMaxAccessName, "%s", kRequirePinPrefix); - return true; -#ifdef CONFIG_LOCK_SCHEDULES - case Type::WeekDaySchedule: - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s%s/%u/%u", kAccessPrefix, kSchedulePrefix, - kScheduleWeekDaySuffix, limitedIndex, limitedSubindex); - return true; - case Type::YearDaySchedule: - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s%s/%u/%u", kAccessPrefix, kSchedulePrefix, - kScheduleYearDaySuffix, limitedIndex, limitedSubindex); - return true; - case Type::HolidaySchedule: - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s%s/%u", kAccessPrefix, kSchedulePrefix, - kScheduleHolidaySuffix, limitedIndex); - return true; - case Type::WeekDayScheduleIndexes: - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s%s/%u", kAccessPrefix, kScheduleCounterPrefix, - kScheduleWeekDaySuffix, limitedIndex); - return true; - case Type::YearDayScheduleIndexes: - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s%s/%u", kAccessPrefix, kScheduleCounterPrefix, - kScheduleYearDaySuffix, limitedIndex); - return true; - case Type::HolidayScheduleIndexes: - (void)snprintf(mKeyName, kMaxAccessName, "%s/%s%s", kAccessPrefix, kScheduleCounterPrefix, - kScheduleHolidaySuffix); - return true; -#endif /* CONFIG_LOCK_SCHEDULES */ - default: - break; - } - - return false; -} - -bool AccessStorage::Store(Type storageType, const void *data, size_t dataSize, uint16_t index, uint16_t subindex) -{ - if (data == nullptr || !PrepareKeyName(storageType, index, subindex)) { - return false; - } - - Nrf::PersistentStorageNode node{ mKeyName, strlen(mKeyName) + 1 }; - bool ret = (Nrf::PSErrorCode::Success == Nrf::GetPersistentStorage().PSStore(&node, data, dataSize)); - -#ifdef CONFIG_LOCK_PRINT_STORAGE_STATUS - if (ret) { - LOG_DBG("AccessStorage: Stored %s of size: %d bytes", storageType == Type::User ? "user" : "credential", - dataSize); - - size_t storageFreeSpace; - if (GetStorageFreeSpace(storageFreeSpace)) { - LOG_DBG("AccessStorage: Free space: %d bytes", storageFreeSpace); - } - } -#endif - - return ret; -} - -bool AccessStorage::Load(Type storageType, void *data, size_t dataSize, size_t &outSize, uint16_t index, - uint16_t subindex) -{ - if (data == nullptr || !PrepareKeyName(storageType, index, subindex)) { - return false; - } - - Nrf::PersistentStorageNode node{ mKeyName, strlen(mKeyName) + 1 }; - Nrf::PSErrorCode result = Nrf::GetPersistentStorage().PSLoad(&node, data, dataSize, outSize); - - return (Nrf::PSErrorCode::Success == result); -} - -bool AccessStorage::Remove(Type storageType, uint16_t index, uint16_t subindex) -{ - if (!PrepareKeyName(storageType, index, subindex)) { - return false; - } - - Nrf::PersistentStorageNode node{ mKeyName, strlen(mKeyName) + 1 }; - Nrf::PSErrorCode result = Nrf::GetPersistentStorage().PSRemove(&node); - - return (Nrf::PSErrorCode::Success == result); -} diff --git a/app/src/matter/access/access_storage.h b/app/src/matter/access/access_storage.h index 88b85595..aa53928e 100644 --- a/app/src/matter/access/access_storage.h +++ b/app/src/matter/access/access_storage.h @@ -6,43 +6,29 @@ #pragma once -#include +#include +#include /** - * @brief Class to manage access storage + * @brief Class for storing and retrieving door lock access items from the persistent storage. * - * Eventually access items will be store to the Zephyr settings to be persistent (regardless of the used persistent - * storage backend). There are several credential types: PIN, RFID, Fingerprint etc. and each of them is declared as an - * array with the maximum size defined by Kconfig ...MAX_NUM_CREDENTIALS_PER_USER. Each user has a list of credentials - * that are assign to them. The new access item should be written with the new index if the previous one is already - * occupied. + * There are several access item types that can be stored using this class. Each of them may use up to two indexes. + * For example, a credential is referenced by its: + * - type: PIN, RFID or fingerprint + * - credential index: from 1 to CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_TYPE. + * On the other hand, a user is referenced by its user index: from 1 to CONFIG_LOCK_MAX_NUM_USERS. * - * Agreed the following settings key convention: - * - * /cr/ - * // - * / cr_idxs (bytes) - * / / - * / - * /usr_idxs (bytes) - * /usr/ - * / / - * / - * - * /sch_/ - * /sch_idx_ (bytes)/ - * / / - * / / - * / + * Each user has a list of credentials that are assigned to them. * + * The new access item should be written with the new index if the previous one is already occupied. */ class AccessStorage { public: enum class Type : uint8_t { User, - Credential, UsersIndexes, + Credential, CredentialsIndexes, RequirePIN, #ifdef CONFIG_LOCK_SCHEDULES @@ -63,11 +49,6 @@ class AccessStorage { */ bool Init(); - /** - * @brief Factory reset the storage. - */ - void FactoryReset(); - /** * @brief Store the entry into the persistent storage. * @@ -111,24 +92,4 @@ class AccessStorage { static AccessStorage sInstance; return sInstance; } - -private: - constexpr static auto kAccessPrefix = "cr"; - constexpr static auto kAccessCounterPrefix = "cr_idxs"; - 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 kMaxAccessName = Nrf::PersistentStorageNode::kMaxKeyNameLength; - - Nrf::PersistentStorageNode mRootNode{ kAccessPrefix, strlen(kAccessPrefix) }; - char mKeyName[AccessStorage::kMaxAccessName]; - - bool PrepareKeyName(Type storageType, uint16_t index, uint16_t subindex); }; diff --git a/app/src/matter/access/access_storage_print.cpp b/app/src/matter/access/access_storage_print.cpp new file mode 100644 index 00000000..e750fec9 --- /dev/null +++ b/app/src/matter/access/access_storage_print.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "access_storage_print.h" + +#ifdef CONFIG_SETTINGS_NVS +#include +#elif CONFIG_SETTINGS_ZMS || CONFIG_SETTINGS_ZMS_LEGACY +#include +#endif /* CONFIG_SETTINGS_NVS */ +#include +#include + +LOG_MODULE_DECLARE(storage_manager, CONFIG_CHIP_APP_LOG_LEVEL); + +static bool GetStorageFreeSpace(size_t &freeBytes) +{ + void *storage = nullptr; + int status = settings_storage_get(&storage); + if (status != 0 || !storage) { + LOG_ERR("AccessStorage: Cannot read free space: %d", status); + return false; + } +#ifdef CONFIG_SETTINGS_NVS + freeBytes = nvs_calc_free_space(static_cast(storage)); +#elif CONFIG_SETTINGS_ZMS || CONFIG_SETTINGS_ZMS_LEGACY + freeBytes = zms_calc_free_space(static_cast(storage)); +#endif /* CONFIG_SETTINGS_NVS */ + return true; +} + +static const char *TypeToString(AccessStorage::Type type) +{ + switch (type) { + case AccessStorage::Type::User: + return "user"; + case AccessStorage::Type::UsersIndexes: + return "user idx"; + case AccessStorage::Type::Credential: + return "credential"; + case AccessStorage::Type::CredentialsIndexes: + return "credential idx"; +#ifdef CONFIG_LOCK_SCHEDULES + case AccessStorage::Type::WeekDaySchedule: + case AccessStorage::Type::YearDaySchedule: + case AccessStorage::Type::HolidaySchedule: + return "schedule"; + case AccessStorage::Type::WeekDayScheduleIndexes: + case AccessStorage::Type::YearDayScheduleIndexes: + case AccessStorage::Type::HolidayScheduleIndexes: + return "schedule idx"; +#endif /* CONFIG_LOCK_SCHEDULES */ + default: + return "other"; + } +} + +void PrintAccessDataStored(AccessStorage::Type type, size_t dataSize, bool success) +{ + if (!success) { + return; + } + + LOG_DBG("AccessStorage: Stored %s of size: %d bytes", TypeToString(type), dataSize); + + size_t storageFreeSpace; + if (GetStorageFreeSpace(storageFreeSpace)) { + LOG_DBG("AccessStorage: Free space: %d bytes", storageFreeSpace); + } +} diff --git a/app/src/matter/access/access_storage_print.h b/app/src/matter/access/access_storage_print.h new file mode 100644 index 00000000..d380a893 --- /dev/null +++ b/app/src/matter/access/access_storage_print.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include "access_storage.h" + +#include + +/** + * @brief Print a log message that access data has been stored. + * + * Additionally, print the current storage free space. + * + * @param type Type of stored data. + * @param dataSize Size of the stored data in bytes. + * @param success Whether the storage operation was successful. + */ +void PrintAccessDataStored(AccessStorage::Type type, size_t dataSize, bool success); diff --git a/app/src/matter/access/access_storage_psa_ps.cpp b/app/src/matter/access/access_storage_psa_ps.cpp new file mode 100644 index 00000000..b0af1069 --- /dev/null +++ b/app/src/matter/access/access_storage_psa_ps.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file access_storage_psa_ps.cpp + * + * Provides the implementation of AccessStorage class that utilizes the PSA Protected Storage API. + * + * This implementation uses the PSA PS UID range of size 2^32, which starts at a configurable offset. + * The offset within the range is composed of a type and two optional indexes, which are packed into a uint32_t. + * The type is encoded within a uint8_t as follows: + * - 0: User + * - 1: UsersIndexes + * - 2: Credential + * - 3: CredentialsIndexes + * - 4: RequirePIN + * - 5: WeekDaySchedule + * - 6: WeekDayScheduleIndexes + * - 7: YearDaySchedule + * - 8: YearDayScheduleIndexes + * - 9: HolidaySchedule + * - 10: HolidayScheduleIndexes + * + * For example, the UID offset for the credential with type Fingerprint (3) and index 10 is 0x0103000a. + */ + +#include "access_storage.h" + +#include "access_data_types.h" +#include "access_storage_print.h" + +#include + +#include + +namespace { +constexpr psa_storage_uid_t kUIDOffset = CONFIG_LOCK_ACCESS_STORAGE_PROTECTED_STORAGE_UID_OFFSET; + +static_assert(static_cast(kUIDOffset) == 0, "UID offset must fit in the upper 32 bits of the 64-bit UID"); + +template uint32_t PackIntegers() +{ + return 0; +} + +template +uint32_t PackIntegers(Integer current, Integers... others) +{ + static_assert(Offset >= sizeof(Integer), "Cannot pack integers into uint32_t"); + + constexpr size_t NewOffset = Offset - sizeof(Integer); + + return (static_cast(current) << (NewOffset * 8)) | PackIntegers(others...); +} + +psa_storage_uid_t MakeUID(AccessStorage::Type type, uint16_t index, uint16_t subindex) +{ + using UserIndex = uint16_t; + using CredentialType = CredentialTypeEnum; + using CredentialIndex = uint16_t; +#ifdef CONFIG_LOCK_SCHEDULES + using ScheduleIndex = uint8_t; +#endif + + switch (type) { + case AccessStorage::Type::User: + return kUIDOffset + PackIntegers(type, static_cast(index)); + case AccessStorage::Type::UsersIndexes: + return kUIDOffset + PackIntegers(type); + case AccessStorage::Type::Credential: + return kUIDOffset + + PackIntegers(type, static_cast(index), static_cast(subindex)); + case AccessStorage::Type::CredentialsIndexes: + return kUIDOffset + PackIntegers(type, static_cast(index)); + case AccessStorage::Type::RequirePIN: + return kUIDOffset + PackIntegers(type); +#ifdef CONFIG_LOCK_SCHEDULES + case AccessStorage::Type::WeekDaySchedule: + case AccessStorage::Type::YearDaySchedule: + return kUIDOffset + + PackIntegers(type, static_cast(index), static_cast(subindex)); + case AccessStorage::Type::WeekDayScheduleIndexes: + case AccessStorage::Type::YearDayScheduleIndexes: + return kUIDOffset + PackIntegers(type, static_cast(index)); + case AccessStorage::Type::HolidaySchedule: + return kUIDOffset + PackIntegers(type, static_cast(index)); + case AccessStorage::Type::HolidayScheduleIndexes: + return kUIDOffset + PackIntegers(type); +#endif /* CONFIG_LOCK_SCHEDULES */ + default: + __ASSERT_NO_MSG(false); + return 0; + } +} +} /* namespace */ + +bool AccessStorage::Init() +{ + return true; +} + +bool AccessStorage::Store(Type type, const void *data, size_t dataSize, uint16_t index, uint16_t subindex) +{ + if (data == nullptr) { + return false; + } + + psa_storage_uid_t uid = MakeUID(type, index, subindex); + psa_status_t status = psa_ps_set(uid, dataSize, data, PSA_STORAGE_FLAG_NONE); + +#ifdef CONFIG_LOCK_PRINT_STORAGE_STATUS + PrintAccessDataStored(type, dataSize, status == PSA_SUCCESS); +#endif + + return status == PSA_SUCCESS; +} + +bool AccessStorage::Load(Type type, void *data, size_t dataSize, size_t &outSize, uint16_t index, uint16_t subindex) +{ + if (data == nullptr) { + return false; + } + + psa_storage_uid_t uid = MakeUID(type, index, subindex); + psa_status_t status = psa_ps_get(uid, 0, dataSize, data, &outSize); + + if (status != PSA_SUCCESS) { + outSize = 0; + return false; + } + + return true; +} + +bool AccessStorage::Remove(Type type, uint16_t index, uint16_t subindex) +{ + psa_storage_uid_t uid = MakeUID(type, index, subindex); + psa_status_t status = psa_ps_remove(uid); + + return status == PSA_SUCCESS; +} diff --git a/app/src/matter/app_task.cpp b/app/src/matter/app_task.cpp index 4eeaf58c..31809e74 100644 --- a/app/src/matter/app_task.cpp +++ b/app/src/matter/app_task.cpp @@ -45,12 +45,14 @@ constexpr uint8_t kLockNUSPriority{ 2 }; #ifndef CONFIG_CHIP_FACTORY_RESET_ERASE_SETTINGS void AppEventHandler(const ChipDeviceEvent *event, [[maybe_unused]] intptr_t) { + constexpr bool reinitializeStorage{ !IS_ENABLED(CONFIG_CHIP_LAST_FABRIC_REMOVED_ERASE_AND_REBOOT) }; + switch (event->Type) { case DeviceEventType::kFactoryReset: // With this configuration we have to manually clean up the storage, // as whole settings partition won't be erased. BoltLockMgr().FactoryReset(); - ClearStorageAliro(); + ClearStorageAliro(reinitializeStorage); break; default: break; @@ -62,26 +64,6 @@ Nrf::Matter::IdentifyCluster sIdentifyCluster(kLockEndpointId, false, []() { Nrf::PostTask([] { Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Set(BoltLockMgr().IsLocked()); }); }); -#ifdef CONFIG_DOOR_LOCK_BLE_UWB - -Aliro::ReaderStateByte ToAliroState(BoltLockManager::State state) -{ - switch (state) { - case BoltLockManager::State::kLockingInitiated: - return Aliro::ReaderStateByte::EnteringSecured; - case BoltLockManager::State::kLockingCompleted: - return Aliro::ReaderStateByte::Secured; - case BoltLockManager::State::kUnlockingInitiated: - return Aliro::ReaderStateByte::EnteringUnsecured; - case BoltLockManager::State::kUnlockingCompleted: - return Aliro::ReaderStateByte::Unsecured; - default: - return Aliro::ReaderStateByte::Unknown; - } -} - -#endif - } /* namespace */ void AppTask::ButtonEventHandler(Nrf::ButtonState state, Nrf::ButtonMask hasChanged) @@ -103,40 +85,41 @@ void AppTask::LockActionEventHandler() void AppTask::LockStateChanged(const BoltLockManager::StateData &stateData) { switch (stateData.mState) { - case BoltLockManager::State::kLockingInitiated: + case Aliro::ReaderStateByte::EnteringSecured: LOG_INF("Lock action initiated"); Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Blink(50, 50); #ifdef CONFIG_CHIP_NUS Nrf::GetNUSService().SendData("locking", sizeof("locking")); #endif break; - case BoltLockManager::State::kLockingCompleted: + case Aliro::ReaderStateByte::Secured: LOG_INF("Lock action completed"); Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Set(true); #ifdef CONFIG_CHIP_NUS Nrf::GetNUSService().SendData("locked", sizeof("locked")); #endif break; - case BoltLockManager::State::kUnlockingInitiated: + case Aliro::ReaderStateByte::EnteringUnsecured: LOG_INF("Unlock action initiated"); Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Blink(50, 50); #ifdef CONFIG_CHIP_NUS Nrf::GetNUSService().SendData("unlocking", sizeof("unlocking")); #endif break; - case BoltLockManager::State::kUnlockingCompleted: + case Aliro::ReaderStateByte::Unsecured: LOG_INF("Unlock action completed"); #ifdef CONFIG_CHIP_NUS Nrf::GetNUSService().SendData("unlocked", sizeof("unlocked")); #endif Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Set(false); break; + default: + break; } #ifdef CONFIG_DOOR_LOCK_BLE_UWB - Aliro::AliroStack::Instance().SendReaderStatusChangedMessage(stateData.mAliroSource, - ToAliroState(stateData.mState)); + Aliro::AliroStack::Instance().SendReaderStatusChangedMessage(stateData.mAliroSource, stateData.mState); #endif @@ -171,10 +154,10 @@ void AppTask::UpdateClusterStateHandler(const BoltLockManager::StateData &stateD DlLockState newLockState; switch (stateData.mState) { - case BoltLockManager::State::kLockingCompleted: + case Aliro::ReaderStateByte::Secured: newLockState = DlLockState::kLocked; break; - case BoltLockManager::State::kUnlockingCompleted: + case Aliro::ReaderStateByte::Unsecured: newLockState = DlLockState::kUnlocked; break; default: @@ -221,8 +204,8 @@ void AppTask::UpdateClusterStateHandler(const BoltLockManager::StateData &stateD void AppTask::NUSLockCallback(void *context) { LOG_DBG("Received LOCK command from NUS"); - if (BoltLockMgr().GetState().mState == BoltLockManager::State::kLockingCompleted || - BoltLockMgr().GetState().mState == BoltLockManager::State::kLockingInitiated) { + if (BoltLockMgr().GetState().mState == Aliro::ReaderStateByte::Secured || + BoltLockMgr().GetState().mState == Aliro::ReaderStateByte::EnteringSecured) { LOG_INF("Device is already locked"); return; } @@ -233,8 +216,8 @@ void AppTask::NUSLockCallback(void *context) void AppTask::NUSUnlockCallback(void *context) { LOG_DBG("Received UNLOCK command from NUS"); - if (BoltLockMgr().GetState().mState == BoltLockManager::State::kUnlockingCompleted || - BoltLockMgr().GetState().mState == BoltLockManager::State::kUnlockingInitiated) { + if (BoltLockMgr().GetState().mState == Aliro::ReaderStateByte::Unsecured || + BoltLockMgr().GetState().mState == Aliro::ReaderStateByte::EnteringUnsecured) { LOG_INF("Device is already unlocked"); } else { Nrf::PostTask([] { LockActionEventHandler(); }); @@ -301,9 +284,7 @@ CHIP_ERROR AppTask::Init() CHIP_ERROR AppTask::StartApp() { ReturnErrorOnFailure(Init()); - - int err = AliroInit(); - VerifyOrReturnError(err == EXIT_SUCCESS, CHIP_ERROR_INTERNAL, LOG_ERR("Failed to initialize Aliro")); + VerifyOrReturnError(AliroInit() == EXIT_SUCCESS, CHIP_ERROR_INTERNAL, LOG_ERR("Failed to initialize Aliro")); while (true) { Nrf::DispatchNextTask(); diff --git a/app/src/matter/bolt_lock_manager.cpp b/app/src/matter/bolt_lock_manager.cpp index f527a3ef..cc243281 100644 --- a/app/src/matter/bolt_lock_manager.cpp +++ b/app/src/matter/bolt_lock_manager.cpp @@ -5,9 +5,9 @@ */ #include "bolt_lock_manager.h" -#include "access_manager/access_manager.h" #include "app/task_executor.h" +#include "aliro/access_manager/access_manager.h" #include "aliro/aliro.h" #ifdef CONFIG_DOOR_LOCK_STEP_UP_PHASE @@ -48,22 +48,26 @@ void BoltLockManager::Init(StateChangeCallback callback) { mStateChangeCallback = callback; - k_timer_init(&mActuatorTimer, &BoltLockManager::ActuatorTimerEventHandler, nullptr); - k_timer_user_data_set(&mActuatorTimer, this); + mLockSim.Init([](Aliro::OperationSource, Aliro::ReaderStateByte state) { + Nrf::PostTask([state] { BoltLockMgr().UpdateState(state); }); + }); // Set Aliro AccessManager application callbacks Aliro::AccessManagerInstance().SetApplicationCallbacks({ .mUnlockIndicatorClb = [](Aliro::OperationSource source) { - if (!BoltLockMgr().Unlock(source)) { + Nrf::PostTask([source] { + if (!BoltLockMgr().Unlock(source)) { #ifdef CONFIG_DOOR_LOCK_BLE_UWB - // The lock is already unlocked, so we can send the Unsecured state - Aliro::AliroStack::Instance().SendReaderStatusChangedMessage( - source, Aliro::ReaderStateByte::Unsecured); + // The lock is already unlocked, so we can send the Unsecured state + Aliro::AliroStack::Instance().SendReaderStatusChangedMessage( + source, Aliro::ReaderStateByte::Unsecured); #endif // CONFIG_DOOR_LOCK_BLE_UWB - } + } + }); }, - .mLockIndicatorClb = [](Aliro::OperationSource source) { BoltLockMgr().Lock(source); }, + .mLockIndicatorClb = + [](Aliro::OperationSource source) { Nrf::PostTask([source] { BoltLockMgr().Lock(source); }); }, }); auto addPublicKey = [](uint16_t credentialIndex, CredentialTypeEnum credentialType, @@ -194,79 +198,60 @@ bool BoltLockManager::GetRequirePIN() void BoltLockManager::Lock(const OperationSource source, const Nullable &fabricIdx, const Nullable &nodeId, const Nullable &validatePINResult) { - VerifyOrReturn(mStateData.mState != State::kLockingCompleted); - StateData newStateData{ State::kLockingInitiated, source, ToAliroOperationSource(source), fabricIdx, nodeId, - validatePINResult }; - SetStateData(newStateData); - - k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); + VerifyOrReturn(mStateData.mState != Aliro::ReaderStateByte::Secured); + mStateData = { Aliro::ReaderStateByte::EnteringSecured, + source, + ToAliroOperationSource(source), + fabricIdx, + nodeId, + validatePINResult }; + + mLockSim.Lock(ToAliroOperationSource(source)); } void BoltLockManager::Unlock(const OperationSource source, const Nullable &fabricIdx, const Nullable &nodeId, const Nullable &validatePINResult) { - VerifyOrReturn(mStateData.mState != State::kUnlockingCompleted); - StateData newStateData{ State::kUnlockingInitiated, source, ToAliroOperationSource(source), fabricIdx, nodeId, - validatePINResult }; - SetStateData(newStateData); - - k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); + VerifyOrReturn(mStateData.mState != Aliro::ReaderStateByte::Unsecured); + mStateData = { Aliro::ReaderStateByte::EnteringUnsecured, + source, + ToAliroOperationSource(source), + fabricIdx, + nodeId, + validatePINResult }; + + mLockSim.Unlock(ToAliroOperationSource(source)); } bool BoltLockManager::Lock(Aliro::OperationSource source) { - VerifyOrReturnValue(mStateData.mState != State::kLockingCompleted, false); - StateData newStateData{ - State::kLockingInitiated, OperationSource::kAliro, source, NullNullable, NullNullable, NullNullable - }; - SetStateData(newStateData); - - k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); + VerifyOrReturnValue(mStateData.mState != Aliro::ReaderStateByte::Secured, false); + mStateData = { Aliro::ReaderStateByte::EnteringSecured, + OperationSource::kAliro, + source, + NullNullable, + NullNullable, + NullNullable }; + + mLockSim.Lock(source); return true; } bool BoltLockManager::Unlock(Aliro::OperationSource source) { - VerifyOrReturnValue(mStateData.mState != State::kUnlockingCompleted, false); - StateData newStateData{ - State::kUnlockingInitiated, OperationSource::kAliro, source, NullNullable, NullNullable, NullNullable - }; - SetStateData(newStateData); - - k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); + VerifyOrReturnValue(mStateData.mState != Aliro::ReaderStateByte::Unsecured, false); + mStateData = { Aliro::ReaderStateByte::EnteringUnsecured, + OperationSource::kAliro, + source, + NullNullable, + NullNullable, + NullNullable }; + + mLockSim.Unlock(source); return true; } -void BoltLockManager::ActuatorTimerEventHandler(k_timer *timer) -{ - /* - * The timer event handler is called in the context of the system clock ISR. - * Post an event to the application task queue to process the event in the - * context of the application thread. - */ - - BoltLockManagerEvent event; - event.manager = static_cast(k_timer_user_data_get(timer)); - Nrf::PostTask([event] { ActuatorAppEventHandler(event); }); -} - -void BoltLockManager::ActuatorAppEventHandler(const BoltLockManagerEvent &event) -{ - BoltLockManager *lock = reinterpret_cast(event.manager); - - switch (lock->mStateData.mState) { - case State::kLockingInitiated: - lock->SetState(State::kLockingCompleted); - break; - case State::kUnlockingInitiated: - lock->SetState(State::kUnlockingCompleted); - break; - default: - break; - } -} - -void BoltLockManager::SetState(State state) +void BoltLockManager::UpdateState(Aliro::ReaderStateByte state) { mStateData.mState = state; @@ -275,15 +260,6 @@ void BoltLockManager::SetState(State state) } } -void BoltLockManager::SetStateData(const StateData &stateData) -{ - mStateData = stateData; - - if (mStateChangeCallback != nullptr) { - mStateChangeCallback(mStateData); - } -} - void BoltLockManager::FactoryReset() { AccessMgr::Instance().FactoryReset(); diff --git a/app/src/matter/bolt_lock_manager.h b/app/src/matter/bolt_lock_manager.h index 8f4aadeb..a7ac13be 100644 --- a/app/src/matter/bolt_lock_manager.h +++ b/app/src/matter/bolt_lock_manager.h @@ -7,6 +7,7 @@ #pragma once #include "access/access_manager.h" +#include "aliro/lock_sim/lock_sim.h" #include "aliro/types.h" #include @@ -16,36 +17,16 @@ #include -struct BoltLockManagerEvent; - class BoltLockManager { using AccessMgr = AccessManager; public: - static constexpr size_t kMaxCredentialLength{ 128 }; - - enum class State : uint8_t { - kLockingInitiated = 0, - kLockingCompleted, - kUnlockingInitiated, - kUnlockingCompleted, - }; - - struct UserData { - char mName[DOOR_LOCK_USER_NAME_BUFFER_SIZE]; - CredentialStruct mCredentials[CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_USER]; - }; - - struct CredentialData { - chip::Platform::ScopedMemoryBuffer mSecret; - }; - using OperationSource = chip::app::Clusters::DoorLock::OperationSourceEnum; using ValidatePINResult = AccessMgr::ValidatePINResult; struct StateData { - State mState; + Aliro::ReaderStateByte mState; OperationSource mSource; Aliro::OperationSource mAliroSource; Nullable mFabricIdx; @@ -55,12 +36,10 @@ class BoltLockManager { using StateChangeCallback = void (*)(const StateData &); - static constexpr uint32_t kActuatorMovementTimeMs{ 2000 }; - void Init(StateChangeCallback callback); const StateData &GetState() const { return mStateData; } - bool IsLocked() const { return mStateData.mState == State::kLockingCompleted; } + bool IsLocked() const { return mStateData.mState == Aliro::ReaderStateByte::Secured; } bool GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo &user); bool SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, @@ -110,18 +89,16 @@ class BoltLockManager { private: friend class AppTask; - void SetState(State state); - void SetStateData(const StateData &stateData); + void UpdateState(Aliro::ReaderStateByte state); - static void ActuatorTimerEventHandler(k_timer *timer); - static void ActuatorAppEventHandler(const BoltLockManagerEvent &event); friend BoltLockManager &BoltLockMgr(); StateData mStateData = { - State::kLockingCompleted, OperationSource::kButton, Aliro::OperationSource::Manual, {}, {}, {} + Aliro::ReaderStateByte::Secured, OperationSource::kButton, Aliro::OperationSource::Manual, {}, {}, {} }; StateChangeCallback mStateChangeCallback = nullptr; - k_timer mActuatorTimer = {}; + + Aliro::LockSim mLockSim; static BoltLockManager sLock; }; @@ -130,7 +107,3 @@ inline BoltLockManager &BoltLockMgr() { return BoltLockManager::sLock; } - -struct BoltLockManagerEvent { - BoltLockManager *manager; -}; diff --git a/app/src/matter/door_lock_delegate.cpp b/app/src/matter/door_lock_delegate.cpp index 53705f21..cb3d5c0f 100644 --- a/app/src/matter/door_lock_delegate.cpp +++ b/app/src/matter/door_lock_delegate.cpp @@ -10,12 +10,15 @@ #include #include #include +#include #include #include -#include #include #include "aliro/crypto_key_ids.h" +#include "crypto/utils.h" +#include "reader_cache.h" + #include LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); @@ -23,15 +26,6 @@ LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace chip::app::Clusters::DoorLock; namespace { - -#ifdef CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER - -constexpr std::array kTestGroupSubIdentifier{ - 0x63, 0x20, 0x38, 0x36, 0x20, 0x31, 0x62, 0x20, 0x33, 0x39, 0x20, 0x31, 0x66, 0x20, 0x33, 0x34 -}; - -#endif // CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER - static_assert(sizeof(Aliro::CryptoTypes::PrivateKey) == kAliroSigningKeySize, "Aliro::CryptoTypes::PrivateKey size mismatch"); static_assert(sizeof(Aliro::CryptoTypes::PublicKey) == kAliroReaderVerificationKeySize, @@ -69,37 +63,17 @@ CHIP_ERROR DoorLockDelegate::Init() CHIP_ERROR err = chip::DeviceLayer::SystemLayer().ScheduleLambda([]() { Aliro::CryptoTypes::PublicKey publicKey{}; Aliro::Identifier identifier{}; - Aliro::CryptoTypes::KeyId groupResolvingKeyId{ 0 }; - Aliro::CryptoTypes::KeyId credentialIssuerCAPublicKeyId{ 0 }; - AliroError ec = Aliro::CryptoInstance().ExportPublicKey(Aliro::kPrivateKeyId, publicKey); + AliroError ec = DoorLock::Crypto::ExportPublicKey(Aliro::kPrivateKeyId, publicKey); VerifyOrReturn(ec == ALIRO_NO_ERROR, /* device not provisioned */); + VerifyOrReturn(Aliro::ReaderCache::Instance().SetPublicKey(publicKey) == ALIRO_NO_ERROR, + LOG_ERR("Failed to set reader public key")); VerifyOrReturn(KeyValueStorage::Instance().Get(Aliro::StorageKeys::kStorageKeyNameIdentifier, identifier.data(), identifier.size()) == 0, LOG_ERR("Failed to get reader group identifier")); - -#ifdef CONFIG_DOOR_LOCK_BLE_UWB - - groupResolvingKeyId = Aliro::kGroupResolvingKeyId; - -#endif // CONFIG_DOOR_LOCK_BLE_UWB - -#ifdef CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA - - ec = Aliro::CryptoInstance().ExportKey(Aliro::kCredentialIssuerCAPublicKeyId, publicKey.data(), - publicKey.size()); - if (ec == ALIRO_NO_ERROR) { - credentialIssuerCAPublicKeyId = Aliro::kCredentialIssuerCAPublicKeyId; - } else { - LOG_DBG("Credential Issuer CA Public Key is not provisioned"); - } - -#endif // CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA - - ec = Aliro::AliroStack::Instance().Provision(Aliro::kPrivateKeyId, groupResolvingKeyId, identifier, - credentialIssuerCAPublicKeyId); - VerifyOrReturn(ec == ALIRO_NO_ERROR, LOG_ERR("Failed to provision Aliro stack")); + VerifyOrReturn(Aliro::ReaderCache::Instance().SetIdentifier(identifier) == ALIRO_NO_ERROR, + LOG_ERR("Failed to set reader identifier")); int err = AliroStart(); if (err != EXIT_SUCCESS) { @@ -118,7 +92,7 @@ CHIP_ERROR DoorLockDelegate::GetAliroReaderVerificationKey(chip::MutableByteSpan VerifyOrReturnError(verificationKey.size() == kAliroReaderVerificationKeySize, CHIP_ERROR_INVALID_ARGUMENT); Aliro::CryptoTypes::PublicKey publicKey{}; - AliroError ec = Aliro::CryptoInstance().ExportPublicKey(Aliro::kPrivateKeyId, publicKey); + AliroError ec = DoorLock::Crypto::ExportPublicKey(Aliro::kPrivateKeyId, publicKey); if (ec != ALIRO_NO_ERROR) { verificationKey.reduce_size(0); @@ -193,8 +167,8 @@ CHIP_ERROR DoorLockDelegate::GetAliroGroupResolvingKey(chip::MutableByteSpan &gr #ifdef CONFIG_DOOR_LOCK_BLE_UWB - AliroError ec = Aliro::CryptoInstance().ExportKey(Aliro::kGroupResolvingKeyId, groupResolvingKey.data(), - groupResolvingKey.size()); + AliroError ec = DoorLock::Crypto::ExportKey(Aliro::kGroupResolvingKeyId, groupResolvingKey.data(), + groupResolvingKey.size()); if (ec != ALIRO_NO_ERROR) { groupResolvingKey.reduce_size(0); return CHIP_ERROR_NOT_FOUND; @@ -282,63 +256,42 @@ CHIP_ERROR DoorLockDelegate::SetAliroReaderConfig(const chip::ByteSpan &signingK Aliro::Identifier identifier{}; Aliro::CryptoTypes::GroupResolvingKey groupResKey{}; Aliro::CryptoTypes::KeyId privateKeyId{ 0 }; - Aliro::CryptoTypes::KeyId groupResolvingKeyId{ 0 }; - Aliro::CryptoTypes::KeyId credentialIssuerCAPublicKeyId{ 0 }; + Aliro::CryptoTypes::PublicKey publicKey{}; std::copy(signingKey.begin(), signingKey.end(), privateKey.data()); std::copy_n(groupIdentifier.data(), kAliroReaderGroupIdentifierSize, identifier.data()); -#ifdef CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER - - std::copy(kTestGroupSubIdentifier.begin(), kTestGroupSubIdentifier.end(), - identifier.data() + kAliroReaderGroupIdentifierSize); - -#else - - AliroError err = Aliro::CryptoInstance().GenerateRandom(identifier.data() + kAliroReaderGroupIdentifierSize, - kAliroReaderGroupSubIdentifierSize); + AliroError err = Aliro::Interface::Crypto::GenerateRandom(identifier.data() + kAliroReaderGroupIdentifierSize, + kAliroReaderGroupSubIdentifierSize); VerifyOrReturnError(err == ALIRO_NO_ERROR, CHIP_ERROR_INTERNAL); -#endif // CONFIG_DOOR_LOCK_USE_TEST_READER_IDENTIFIER - VerifyOrReturnError(KeyValueStorage::Instance().Save(Aliro::StorageKeys::kStorageKeyNameIdentifier, identifier.data(), identifier.size()) == 0, CHIP_ERROR_INTERNAL); + VerifyOrReturnError(Aliro::ReaderCache::Instance().SetIdentifier(identifier) == ALIRO_NO_ERROR, + CHIP_ERROR_INTERNAL); if (groupResolvingKey.HasValue()) { std::copy(groupResolvingKey.Value().begin(), groupResolvingKey.Value().end(), groupResKey.data()); } privateKeyId = Aliro::kPrivateKeyId; - AliroError ec = Aliro::CryptoInstance().ImportPrivateKey(privateKey, privateKeyId, true); + AliroError ec = DoorLock::Crypto::ImportPrivateKey(privateKey, true, privateKeyId); VerifyOrReturnError(ec == ALIRO_NO_ERROR, CHIP_ERROR_INTERNAL); + ec = DoorLock::Crypto::ExportPublicKey(privateKeyId, publicKey); + VerifyOrReturnError(ec == ALIRO_NO_ERROR, CHIP_ERROR_INTERNAL); + VerifyOrReturnError(Aliro::ReaderCache::Instance().SetPublicKey(publicKey) == ALIRO_NO_ERROR, + CHIP_ERROR_INTERNAL); + #ifdef CONFIG_DOOR_LOCK_BLE_UWB - groupResolvingKeyId = Aliro::kGroupResolvingKeyId; - ec = Aliro::CryptoInstance().ProvisionSymmetricKey(groupResKey.data(), groupResKey.size(), groupResolvingKeyId, - true); + Aliro::CryptoTypes::KeyId groupResolvingKeyId = Aliro::kGroupResolvingKeyId; + ec = DoorLock::Crypto::ImportGroupResolvingKey(groupResKey, true, groupResolvingKeyId); VerifyOrReturnError(ec == ALIRO_NO_ERROR, CHIP_ERROR_INTERNAL); #endif // CONFIG_DOOR_LOCK_BLE_UWB -#ifdef CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA - - Aliro::CryptoTypes::PublicKey publicKey{}; - ec = Aliro::CryptoInstance().ExportKey(Aliro::kCredentialIssuerCAPublicKeyId, publicKey.data(), - publicKey.size()); - if (ec == ALIRO_NO_ERROR) { - credentialIssuerCAPublicKeyId = Aliro::kCredentialIssuerCAPublicKeyId; - } else { - LOG_DBG("Credential Issuer CA Public Key is not provisioned"); - } - -#endif // CONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA - - ec = Aliro::AliroStack::Instance().Provision(privateKeyId, groupResolvingKeyId, identifier, - credentialIssuerCAPublicKeyId); - VerifyOrReturnError(ec == ALIRO_NO_ERROR, CHIP_ERROR_INTERNAL, LOG_ERR("Failed to provision Aliro stack")); - VerifyOrReturnError(AliroStart() == EXIT_SUCCESS, CHIP_ERROR_INTERNAL, LOG_ERR("Failed to start Aliro");); return CHIP_NO_ERROR; @@ -351,14 +304,16 @@ CHIP_ERROR DoorLockDelegate::ClearAliroReaderConfig() VerifyOrReturnError(AliroStop() == EXIT_SUCCESS, CHIP_ERROR_INTERNAL, LOG_ERR("Failed to stop Aliro")); KeyValueStorage::Instance().Clear(Aliro::StorageKeys::kStorageKeyNameIdentifier); + Aliro::ReaderCache::Instance().ClearIdentifier(); + Aliro::ReaderCache::Instance().ClearPublicKey(); Aliro::CryptoTypes::KeyId keyId{ Aliro::kPrivateKeyId }; - Aliro::CryptoInstance().DestroyKey(keyId); + DoorLock::Crypto::DestroyKey(keyId); #ifdef CONFIG_DOOR_LOCK_BLE_UWB keyId = Aliro::kGroupResolvingKeyId; - Aliro::CryptoInstance().DestroyKey(keyId); + DoorLock::Crypto::DestroyKey(keyId); #endif // CONFIG_DOOR_LOCK_BLE_UWB diff --git a/docs/building_and_running.rst b/docs/building_and_running.rst index ae381126..26a74e81 100644 --- a/docs/building_and_running.rst +++ b/docs/building_and_running.rst @@ -10,68 +10,91 @@ Building and running In the :file:`door-lock-workspace`, the |APP_NAME| is placed in the :file:`ncs-door-lock-app` directory. To build and run the application on one of the :ref:`supported development kits (DKs) `, complete the following steps: -1. Connect the DK to your computer using the **DEBUGGER** port on the DK. - Set the **POWER** switch to **ON**. +1. Connect the DK to your computer using the **DEBUGGER** port on the DK and set the **POWER** switch to **ON**. #. In the :file:`door-lock-workspace` directory, navigate to the :file:`ncs-door-lock-app` folder. -#. Depending on the :ref:`NFC reader expansion board ` connected to the development kit, - build the application by running the corresponding command: - - +-----------------------+-----------------------+------------------------------------------------------------------------------------------------+ - | Build type | X-NUCLEO-NFC board | Command | - +=======================+=======================+================================================================================================+ - | Debug (default) | `X-NUCLEO-NFC09A1`_ | ``west build -p -b build_target app`` | - | +-----------------------+------------------------------------------------------------------------------------------------+ - | | `X-NUCLEO-NFC08A1`_ | ``west build -p -b build_target app -- -DCONFIG_ST25R3916B_DRV=y`` | - | +-----------------------+------------------------------------------------------------------------------------------------+ - | | `X-NUCLEO-NFC05A1`_ | ``west build -p -b build_target app -- -DCONFIG_ST25R3911_DRV=y`` | - +-----------------------+-----------------------+------------------------------------------------------------------------------------------------+ +#. Depending on the :ref:`NFC reader expansion board ` connected to the development kit, build the application by running the corresponding command: You can find the ``build_target`` of your device in the :ref:`hw_requirements_development_kit` section. - For example, if you are using the nRF5340 DK and `X-NUCLEO-NFC09A1`_, the command is: + .. list-table:: + :header-rows: 1 + + * - Build type + - NFC reader expansion board + - Build command + - Description + * - Debug (default) + - `X-NUCLEO-NFC09A1`_ + - ``west build -p -b build_target app`` + - Recommended for new door lock designs. + * - Debug (default) + - `X-NUCLEO-NFC12A1`_ + - ``west build -p -b build_target app -- -DCONFIG_ST25R500_DRV=y`` + - Supported, but not recommended for new products. + + For example, if you are using the nRF5340 DK and `X-NUCLEO-NFC12A1`_, the command is: .. code-block:: bash west build -p -b nrf5340dk/nrf5340/cpuapp app - For the nRF54L15 DK and `X-NUCLEO-NFC08A1`_, run: + For the nRF54L15 DK and `X-NUCLEO-NFC09A1`_, run: .. code-block:: bash - west build -p -b nrf54l15dk/nrf54l15/cpuapp app -- -DCONFIG_ST25R3916B_DRV=y + west build -p -b nrf54l15dk/nrf54l15/cpuapp app -- -DCONFIG_ST25R200_DRV=y -#. To build the application with Bluetooth LE transport and UWB, run the following command: - - .. _bluetooth_le_enable: +#. To build the application with Bluetooth LE transport and UWB, run: .. code-block:: bash west build -p -b nrf5340dk/nrf5340/cpuapp app -- -DCONFIG_DOOR_LOCK_BLE_UWB=y -#. You can also apply optional configurations depending on the modules used: +.. note:: + The above command builds the application with Bluetooth LE transport and UWB interface enabled but to operate properly it requires the implementation of the UWB interface to be provided by the application. + The default implementation of the UWB interface is based on the Qorvo QM35825 UWB Aliro adapter and can we enabled by using the ``uwb_qm35`` application snippet. + Note that the ``uwb_qm35`` snippet has the ``CONFIG_DOOR_LOCK_BLE_UWB`` Kconfig option enabled already. + +#. Refer to the build variants for additional transports and protocols: - * If you are using the `QM35825`_ UWB module with the Qorvo Arduino Interface Board, execute the following command to build the application with UWB enabled. + .. list-table:: + :header-rows: 1 + :widths: 40 60 - .. code-block:: bash + * - Configuration + - Build command + * - QM35825 UWB module using the Qorvo Arduino Interface Board + - ``west build -p -b nrf5340dk/nrf5340/cpuapp app -- -Dapp_SNIPPET=uwb_qm35`` + * - Matter over Thread + - ``west build -p -b nrf5340dk/nrf5340/cpuapp app -- -DSNIPPET='matter'`` + * - QM35825 UWB module with Matter over Thread + - ``west build -p -b nrf5340dk/nrf5340/cpuapp app -- -Dapp_SNIPPET=uwb_qm35 -DSNIPPET='matter'`` - west build -p -b nrf5340dk/nrf5340/cpuapp app -- -Dapp_SNIPPET=uwb_qm35 + The ``uwb_qm35`` snippet configures the NFC and UWB modules to share the same SPI bus. - .. note:: - The ``uwb_qm35`` snippet configures both NFC and UWB modules to share the same SPI bus. +Building debug and release versions +************************************ - * For Matter over Thread, execute the following command to build the application: +In the release configuration, the application is built with the following characteristics: - .. code-block:: bash +* Aliro stack logs are disabled. +* RFAL NFC driver logs are disabled. +* Power-management options are enabled. +* Unused peripherals are disabled using the board-specific ``*_release.overlay``. +* The device resets automatically on a fatal error. - west build -p -b nrf5340dk/nrf5340/cpuapp app -- -DSNIPPET='matter' +Note that shell commands availability differs by variant: - * To build the application with `QM35825`_ UWB module support and Matter over Thread enabled, run: +* In the Matter variant, the UART shell is disabled by default in release builds. +* In the Aliro standalone variant, the ``dl`` shell remains enabled in release builds to allow provisioning the required keys (see :ref:`testing_provisioning_cli`). - .. code-block:: bash +To build the application in release mode: - west build -p -b nrf5340dk/nrf5340/cpuapp app -- -Dapp_SNIPPET=uwb_qm35 -DSNIPPET='matter' + .. code-block:: bash + + west build -p -b app -- -DFILE_SUFFIX=release #. Once you have built the application, flash it: @@ -79,40 +102,56 @@ To build and run the application on one of the :ref:`supported development kits west flash -#. To verify if the application runs, connect to the DK with a terminal emulator that supports VT100/ANSI escape characters. - It is recommended to use the `Serial Terminal app`_. - See the `Testing and optimization`_ page in the |NCS| documentation for the required settings. +#. Verify if the application runs correctly: - .. note:: - |app_hwfc_enabled| + .. tabs:: -#. Press the **RESET** button on the DK in order to refresh the application. - You should see the following logs: + .. tab:: Debug configuration - .. code-block:: console + Connect to the DK with a terminal emulator that supports VT100/ANSI escape characters. + It is recommended to use the `Serial Terminal app`_. + See the `Testing and optimization`_ page in the |NCS| documentation for the required settings. - *** Booting My Application v0.1.0-f0e5cf444fb0 *** - *** Using nRF Connect SDK v2.9.0-7787b2649840 *** - *** Using Zephyr OS v3.7.99-1f8f3dc29142 *** - Starting nRF Door Lock Reference Application for the nRF Connect SDK + .. note:: + |app_hwfc_enabled| - Optionally, if you activated QM35 UWB support, you should also see the following logs: + Press the **RESET** button on the DK in order to refresh the application. + You should see the following logs: - .. code-block:: console + .. code-block:: console - uwb: Initializing UWB device... - hsspi_helpers: Awake frame sending supported by FW - hsspi_helpers: Awake frame not received - uwb: UWB device initialized successfully. + *** Booting My Application v0.1.0-f0e5cf444fb0 *** + *** Using nRF Connect SDK v2.9.0-7787b2649840 *** + *** Using Zephyr OS v3.7.99-1f8f3dc29142 *** + Starting nRF Door Lock Reference Application for the nRF Connect SDK - Additionally, if you enabled Matter, you should also see the logs: + * Additionally, depending on the activated options, you will see the following: - .. code-block:: console + * For QM35 UWB: - Init CHIP stack - [DL]OpenThread started: OK - ... - [ZCL]Door Lock server initialized + .. code-block:: console + + uwb: Initializing UWB device... + hsspi_helpers: Awake frame sending supported by FW + hsspi_helpers: Awake frame not received + uwb: UWB device initialized successfully. + + * For Matter: + + .. code-block:: console + + Init CHIP stack + [DL]OpenThread started: OK + ... + [ZCL]Door Lock server initialized + + .. tab:: Release configuration + + UART logs are disabled. + Complete the following steps to verify if the application runs correctly: + + * Aliro standalone - Connect to the serial console and verify that the ``dl`` shell command is available and responds (see :ref:`testing_provisioning_cli`). + * Matter - Verify the LED and button behavior described in :ref:`matter_ui`. Building QM35 host driver from source ************************************* @@ -135,13 +174,11 @@ If you have an access to the Qorvo repository with UWB stack and QM35 driver sou .. note:: To get an access to the ``nrfconnect-sdk-qorvo`` repository with UWB stack and QM35 driver source code, contact your local Qorvo support team. + In case your access to the ``nrfconnect-sdk-qorvo`` is revoked, updating the west workspace will fail unless you remove the repository from the west manifest group filter by running the following command: - .. note:: - If your access to the ``nrfconnect-sdk-qorvo`` is revoked, updating the west workspace will faill unless you remove the repository from the west manifest group filter by running the following command: - - .. code-block:: bash + .. code-block:: bash - west config manifest.group-filter -- -nrfconnect-sdk-qorvo + west config manifest.group-filter -- -nrfconnect-sdk-qorvo .. _flashing_qm35_using_nrf53_dk: diff --git a/docs/conf.py b/docs/conf.py index a6e2897d..7ec4ded8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,7 +32,8 @@ # ones. extensions = [ 'sphinx_tabs.tabs', - 'sphinx_copybutton' + 'sphinx_copybutton', + 'sphinx_togglebutton' ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/doc_aliro.zip b/docs/doc_aliro.zip deleted file mode 100644 index dbab8041..00000000 Binary files a/docs/doc_aliro.zip and /dev/null differ diff --git a/docs/door_lock_app_arch.rst b/docs/door_lock_app_arch.rst index 1dd26441..419f51ac 100644 --- a/docs/door_lock_app_arch.rst +++ b/docs/door_lock_app_arch.rst @@ -1,15 +1,23 @@ .. _addon_architecture: -|APP_NAME| architecture -####################### +|APP_NAME| architecture and configuration +######################################### .. contents:: :local: :depth: 2 -The |APP_NAME| runs on the Nordic Semiconductor's :ref:`supported SoCs ` and utilizes the Aliro stack for access protocol and communication with a User Device over Near Field Communication (NFC) or Bluetooth® LE. +The |APP_NAME| runs on the Nordic Semiconductor's :ref:`supported SoCs ` and utilizes the Aliro stack for access protocol and communication with a User Device over Near Field Communication (NFC) or Bluetooth® LE (paired with ultra wideband). +In addition, the application supports optional Bluetooth® LE–based features, such as the Nordic UART Service (NUS) and Bluetooth® LE Secure Device Firmware Update (SMP DFU), depending on the configuration. You can also use the application with Matter for provisioning the Aliro-specific credentials by the smart home ecosystem. -See the following diagram for an architecture overview: +The following page describes the application's architecture and its available configuration options. + +To understand how the |APP_NAME| interacts with Aliro stack, refer to the :ref:`reference_application_interactions` page. + +Overview +******** + +The |APP_NAME| architecture can be represented as follows: .. _arch_overview: @@ -19,10 +27,16 @@ See the following diagram for an architecture overview: |APP_NAME| architecture overview. -The |APP_NAME| is built using the :ref:`nRF Connect SDK `, which includes the Zephyr RTOS with all necessary modules. +The application is built using the :ref:`nRF Connect SDK `, which includes the Zephyr RTOS with all necessary modules. The Aliro stack implements the Access Protocol logic, Aliro-specific cryptographic primitives, and communication with the User Device. -The interfaces layer is a bridge connecting the Aliro stack to the Zephyr OS modules through specific backends that implement the following components required by the Aliro: crypto, NFC and, ultra wideband (UWB). +The interfaces layer is a bridge connecting the Aliro stack to the application through specific backends that implement the following components required by the Aliro: + +* NFC +* Ultra wideband (UWB) +* Bluetooth LE +* Crypto + This layer additionally allows to utilize other, custom backends for the crypto, NFC and UWB components by implementing the provided API. By default, the |APP_NAME| uses backends shown in the :ref:`architecture overview `. The Aliro stack library files are placed in the :file:`lib/aliro` directory. @@ -31,16 +45,32 @@ The RF Abstraction Layer (NFC RFAL) handles communication with the STMicroelectr This layer contains drivers, platform abstraction layer (PAL) and the NFC protocol stack, covering evrything from physical characteristic to the application layer. You can choose three NFC sensitivity options, configurable through Kconfig (see :file:`drivers/nfc/stm/nfc_configs/Kconfig`): -* ``CONFIG_RFAL_WAKE_UP_MODE_STRICT`` - Offers lower sensitivity for increased robustness against noise. -* ``CONFIG_RFAL_WAKE_UP_MODE_RELAXED`` - Provides higher sensitivity, which allows the detection of weaker NFC signals but may also increase sensitivity to noise. -* ``CONFIG_RFAL_WAKE_UP_MODE_DEFAULT`` - Uses the default RFAL configuration, suitable for general use cases where no specific tuning is required. +.. list-table:: + :header-rows: 1 + + * - Kconfig option + - Description + * - ``CONFIG_RFAL_WAKE_UP_MODE_STRICT`` + - Offers lower sensitivity for increased robustness against noise. + * - ``CONFIG_RFAL_WAKE_UP_MODE_RELAXED`` + - Provides higher sensitivity, allowing detection of weaker NFC signals, but may increase sensitivity to noise. + * - ``CONFIG_RFAL_WAKE_UP_MODE_DEFAULT`` + - Uses the default RFAL configuration, suitable for general use cases where no specific tuning is required. Communication with external IC's is done through the Serial Peripheral Interface (SPI) bus. The `Platform Security Architecture (PSA)`_ API provides a portable programming interface for cryptographic operations and key storage across a wide range of hardware. It is designed to be user-friendly while still providing access to the low-level primitives essential for modern cryptography. -The `Bluetooth LE Controller`_ provides the necessary functionality for Bluetooth LE communication. +The `Bluetooth LE stack `_ provides the necessary functionality for Bluetooth LE communication. + +When the Aliro service is stopped and other Bluetooth LE services, such as Bluetooth NUS or SMP DFU, are active, the advertising payload is updated dynamically to reflect the active services. + +.. _door_lock_app_arch_bluetooth_le: + +Bluetooth LE transport +********************** + Bluetooth LE transport is supported on the `nRF5340 DK`_ platform and is configured to operate in the peripheral role, allowing the device to advertise its presence and accept connections from Bluetooth LE central devices. The advertising payload is generated according to the Aliro specification, based on the ``reader group identifier`` and ``reader group sub-identifier``. The payload updates automatically whenever these values change, ensuring that the device always advertises the latest group information. @@ -51,24 +81,41 @@ The payload updates automatically whenever these values change, ensuring that th For more information about setting these values, see the :ref:`testing_environment_configuration` section. When a Bluetooth LE central device connects, the Aliro stack manages the Bluetooth LE session, handling connection events, security, and data exchange over a dedicated L2CAP channel with Aliro-specific GATT services. +This Bluetooth LE channel is a prerequisite for establishing the subsequent UWB session. Each session uses unique keys, which are destroyed after termination. In case of NFC transport, only one session can be active at a time. -For Bluetooth LE transport, the maximum number of concurrent sessions is limited by the ``CONFIG_DOOR_LOCK_BLE_UWB_MAX_SESSIONS`` Kconfig option and defaults to the value of ``CONFIG_BT_MAX_CONN``. -To configure number of Bluetooth LE sessions, set the ``CONFIG_BT_MAX_CONN`` to maximum number of connections and optionally use the ``CONFIG_DOOR_LOCK_BLE_UWB_MAX_SESSIONS`` to limit the number of sessions. +Kconfig options +=============== -.. note:: - Each session consumes resources, such as RAM and PSA key slots, therefore the maximum number of the active sessions should be determined empirically based on the requirements of the final application. +When additional BLE services (such as NUS or DFU SMP) are enabled alongside Aliro BLE/UWB transport, ``CONFIG_BT_MAX_CONN`` must be increased to support concurrent connections. +The application automatically sets ``CONFIG_BT_MAX_CONN=2`` when ``CONFIG_DOOR_LOCK_BLE_UWB`` and either ``CONFIG_DOOR_LOCK_BLE_NUS`` or ``CONFIG_DOOR_LOCK_DFU_BLE_SMP`` are enabled. +You can override this default by explicitly setting ``CONFIG_BT_MAX_CONN`` to a different value in your project’s :file:`prj.conf` file. You can configure Bluetooth LE transport parameters, such as buffer sizes, MTU, GATT database, L2CAP channels, TX power, and PHY through Kconfig (see :file:`lib/aliro/Kconfig.ble.defconfig`). +.. list-table:: + :header-rows: 1 + + * - Kconfig option + - Description + * - ``CONFIG_BT_MAX_CONN`` + - Sets the maximum number of simultaneous Bluetooth LE connections supported by the system. + This value defines the upper bound for the number of concurrent Aliro Bluetooth LE sessions. + * - ``CONFIG_DOOR_LOCK_BLE_UWB_MAX_SESSIONS`` + - Limits the maximum number of concurrent Aliro Bluetooth LE sessions. + By default, this option follows the value of ``CONFIG_BT_MAX_CONN``. + You can use it to further restrict the number of active sessions without reducing the total number of Bluetooth LE connections supported by the system. + + * Each session consumes resources, such as RAM and PSA key slots, therefore the maximum number of the active sessions should be determined empirically based on the requirements of the final application. + .. _uwb_interface: UWB interface ************* -The UWB interface (:file:`uwb.h`) allows you to use an UWB hardware module with the Aliro stack and Access Manager. -This interface operates when the Bluetooth LE transport is :ref:`enabled `. +The UWB interface allows you to use an UWB hardware module with the Aliro stack and Access Manager. +This interface operates when the Bluetooth LE transport is :ref:`enabled `. Using ranging measurements, the Access Manager can detect the distance between the User Device and the Reader and grant or deny access based on the access policy. Qorvo QM35 interface implementation @@ -84,136 +131,163 @@ Kconfig options You can configure the Qorvo QM35 interface implementation using the following Kconfig options located under the :file:`app/src/platform/uwb_impl/uwb_qm35_impl/Kconfig` path: -* ``CONFIG_DOOR_LOCK_UWB_MIN_RAN_MULTIPLIER`` - This option specifies the minimum RAN multiplier for UWB ranging blocks. - In practice, this option allows you to configure the frequency of UWB ranging measurements received by the Reader. - The time range is between 96 ms (RAN multiplier is 1) and 24480 ms (RAN multiplier is 255). - Default value is ``2`` which corresponds to a measurement frequency of approximately 5 Hz. - -* ``CONFIG_DOOR_LOCK_UWB_MAC_MODE_OFFSET`` - This option sets the offset between the 2 ranging blocks in MAC mode. - The range is from 0 to 63, with a default value of ``0``. +.. list-table:: + :header-rows: 1 -* ``CONFIG_DOOR_LOCK_UWB_MAC_MODE_RANGING_ROUNDS`` - This option specifies the number of ranging rounds used in a ranging block. - Available values are: + * - Kconfig option + - Description + * - ``CONFIG_DOOR_LOCK_UWB_MIN_RAN_MULTIPLIER`` + - Specifies the minimum RAN multiplier for UWB ranging blocks. + This option controls the frequency of UWB ranging measurements received by the Reader. + The supported time range is from 96 milliseconds, when the RAN multiplier is 1, to 24480 milliseconds, when the RAN multiplier is 255. + The default value is ``2``, which corresponds to a measurement frequency of approximately 5 Hz. + * - ``CONFIG_DOOR_LOCK_UWB_MAC_MODE_OFFSET`` + - Sets the offset between the two ranging blocks in MAC mode. + The supported range is from 0 to 63, with a default value of ``0``. + * - ``CONFIG_DOOR_LOCK_UWB_MAC_MODE_RANGING_ROUNDS`` + - Specifies the number of ranging rounds used in a ranging block. - * ``0`` - 1 ranging round (default) - * ``1`` - 2 ranging rounds - -* ``CONFIG_DOOR_LOCK_UWB_SESSION_LOGGING`` - This option enables logging for UWB session states including status codes. - Use it for debugging and monitoring UWB session behavior. - -.. note:: - The number of simultaneous UWB ranging sessions is currently not limited by the application. - Each Aliro BLE session can have its own UWB ranging session. - Testing has confirmed that two simultaneous UWB ranging sessions can be established without issues. - Support for more than two simultaneous UWB ranging sessions has not been verified. + Available values include: -.. _Access_decision_indicator: + * ``0`` – one ranging round (default) + * ``1`` – two ranging rounds -Access decision indicator -************************* + * - ``CONFIG_DOOR_LOCK_UWB_SESSION_LOGGING`` + - Enables logging of UWB session states, including status codes. + This option is intended for debugging and monitoring UWB session behavior. -When access is granted and door lock is unlocked, a generic indicator is activated for a predefined period to visually confirm successful authentication. -By default, this indicator is a dedicated LED, but it can be adapted to other types of indicators such as a buzzer. +The number of simultaneous UWB ranging sessions is currently not limited by the application. +Each Aliro Bluetooth LE session can have its own UWB ranging session. +You can establish two simultaneous UWB ranging sessions. +Support for more than two simultaneous UWB ranging sessions has not been verified. -You can set the duration of the indication in the :file:`app/src/platform/access_decision_indicator/Kconfig` file. -Use the ``CONFIG_ACCESS_DECISION_INDICATOR_STATE_DELAY_MS`` Kconfig option to specify the time in milliseconds. +.. _lock_simulator: -You can select the hardware resource used for this indication by going to the device tree source file and setting the ``access-decision-indicator`` alias in the corresponding node. - -.. code-block:: dts - - /{ - aliases { - access-decision-indicator = &led2; // green LED2 - }; - }; +Lock simulator +************** -Aliro Access Manager -******************** +The lock simulator provides a software implementation of a door lock actuator with configurable behavior. +It simulates the physical movement of a lock mechanism and provides visual feedback about the lock state. -The Access Manager interface (:file:`access_manager.h`) provides a unified API for handling access control logic in the |APP_NAME|. -It allows the application to use different access control strategies by providing the appropriate implementation. +Configuration options +===================== -Selecting implementation -======================== +All lock simulator configuration options are located in the :file:`app/src/aliro/lock_sim/Kconfig` file. -You can select an Access Manager implementation by enabling one of the following Kconfig options: +Movement time +------------- -* ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT`` (:file:`access_manager_impl_default`) - This option is the default implementation, designed to cover typical access control scenarios. - It makes access decisions based on the proximity of the Aliro User Device (as measured by UWB ranging) and the stored public keys. - You can adjust this implementation to your needs through Kconfig options. - This implementation is integrated with the Access Decision Indicator module, which provides visual feedback about the result of access decisions (for example, LED indicator). - To see available Kconfig options, refer to the :ref:`addon_architecture_kconfig_default` subsection. +The ``CONFIG_DOOR_LOCK_LOCK_SIM_MOVEMENT_TIME_MS`` Kconfig option specifies the movement time of the lock actuator in milliseconds. +This is the time it takes for the lock to move from one state to another (from locked to unlocked or unlocked to locked). +The default value is ``2000`` ms (2 seconds). -* ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_CUSTOM`` (:file:`access_manager_impl_custom`) - This option allows you to provide your own logic by implementing the interface defined in the :file:`access_manager.h` file. - Use this if you need to integrate the |APP_NAME| with custom access policies. +Auto relock +----------- -.. _addon_architecture_kconfig_default: +The lock simulator supports automatic relocking functionality. +When enabled, the lock will automatically relock itself after being unlocked for a specified period of time. -Kconfig options for default implementation ------------------------------------------- +* ``CONFIG_DOOR_LOCK_LOCK_SIM_AUTO_RELOCK`` - This option enables or disables the auto relock feature. + It is enabled by default. -You can configure the default implementation through Kconfig options located under the following path: :file:`app/src/aliro/access_manager_impl_default/Kconfig`: +* ``CONFIG_DOOR_LOCK_LOCK_SIM_AUTO_RELOCK_TIME_MS`` - This option specifies the auto relock time in milliseconds. + The auto relock time is the duration the lock remains unlocked before automatically relocking. + This option is only available when ``CONFIG_DOOR_LOCK_LOCK_SIM_AUTO_RELOCK`` is enabled. + The default value is ``5000`` ms (5 seconds). -* ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM`` - This option specifies the maximum allowed distance (in centimeters) measured by UWB ranging required for granting access to the User Device. - If the distance exceeds this value, access is denied. - If the Aliro User Device is within this distance, access is granted. +Indicator +--------- -* ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_EXIT_MARGIN_CM`` - This option specifies an additional margin (in centimeters) added to ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM`` that is used to determine when the door should be locked (exit range). - This margin prevents rapid open/close toggling when the measured distance fluctuates around the maximum allowed distance threshold. +The lock simulator indicator provides visual feedback about the current lock state. +When enabled, the indicator shows the lock state continuously: it is activated when the lock is unlocked and deactivated when the lock is secured. - Behavior: - - Unlock (enter range): distance <= MAX_ALLOWED_DISTANCE_CM - - Lock (exit range): distance > MAX_ALLOWED_DISTANCE_CM + EXIT_MARGIN_CM +You can enable or disable the lock simulator indicator using the ``CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR`` Kconfig option. +This option is enabled by default. - The exit margin is applied per UWB ranging session based on the session's previous state. - When a session transitions from "out of range" to "in range", the unlock threshold is MAX_ALLOWED_DISTANCE_CM. - Once unlocked, the session must exceed MAX_ALLOWED_DISTANCE_CM + EXIT_MARGIN_CM to trigger locking. +You can select the hardware resource used for this indication by going to the device tree source file and setting the ``lock-sim-indicator`` alias in the corresponding node. - Set to 0 to disable the exit margin (door will lock immediately when distance exceeds MAX_ALLOWED_DISTANCE_CM). +.. code-block:: dts -* ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS`` - This option sets the maximum number of public keys that can be stored in the Access Manager. - It determines the size of the statically allocated memory for Access Manager cache. + /{ + aliases { + lock-sim-indicator = &led2; // green LED2 + }; + }; -* ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION`` - This Kconfig choice allows you to select the method of terminating the UWB ranging session by the Aliro Reader. - Such mechanism is important if the User Device does not terminate the ranging session on its own. - You can choose between the following options: +Aliro Access Manager +******************** - * ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_DISABLED`` - The Access Manager will never automatically terminate UWB ranging sessions. - This is the default option. - Sessions will only be terminated when explicitly requested by the User Device or when the BLE connection is lost. - This option is useful when you want the User Device to have full control over session lifecycle. +The Access Manager interface (:file:`access_manager.h`) provides a unified API for handling access control logic in the |APP_NAME|. +The default implementation (:file:`access_manager`) is designed to cover typical access control scenarios. +It makes access decisions based on the proximity of the Aliro User Device, as measured by UWB ranging, and the stored public keys. +It integrates with the Access Decision Indicator module, which provides visual feedback about access decisions, for example using LED indicator. - * ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_ACCESS_GRANTED`` - The session is terminated immediately after access is granted for a given ranging session. +.. _addon_architecture_kconfig_default: - * ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT`` - The session is terminated after a specified timeout. - The timeout, in milliseconds, is defined by the ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_SESSION_TIMEOUT_MS`` Kconfig option. - By default, the timeout is set to ``10000`` ms (10 seconds). +Kconfig options +=============== + +You can configure the Access Manager through Kconfig options located under the following path: :file:`app/src/aliro/access_manager/Kconfig`: + +.. list-table:: + :header-rows: 1 + + * - Kconfig option + - Description + * - ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM`` + - Specifies the maximum allowed distance, in centimeters, measured by UWB ranging for granting access to the User Device. + If the measured distance exceeds this value, access is denied. + If the Aliro User Device is within this distance, access is granted. + * - ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_EXIT_MARGIN_CM`` + - * Specifies an additional margin, in centimeters, added to ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM`` to determine when the door should be locked, referred to as the exit range. + This margin prevents rapid open and close toggling when the measured distance fluctuates around the maximum allowed distance threshold. + + * Unlock behavior applies when the distance is less than or equal to ``MAX_ALLOWED_DISTANCE_CM``. + * Lock behavior applies when the distance exceeds ``MAX_ALLOWED_DISTANCE_CM + EXIT_MARGIN_CM``. + * The exit margin applies per UWB ranging session based on the previous session state. + + Set this option to ``0`` to disable the exit margin, causing the door to lock immediately when the distance exceeds ``MAX_ALLOWED_DISTANCE_CM``. + * - ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS`` + - Sets the maximum number of public keys that can be stored in the Access Manager. + This option determines the size of the statically allocated memory for the Access Manager cache. + * - ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION`` + - Selects the method used by the Aliro Reader to terminate the UWB ranging session. + This mechanism is important when the User Device does not terminate the ranging session on its own. + + Available suboptions include: + + * ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_DISABLED`` + Prevents the Access Manager from automatically terminating UWB ranging sessions. + This is the default behavior. + Sessions terminate only when explicitly requested by the User Device or when the BLE connection is lost. + This option is useful when the User Device controls the session lifecycle. + + * ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_ACCESS_GRANTED`` + Terminates the UWB ranging session immediately after access is granted for the session. + + * ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_ON_TIMEOUT`` + Terminates the UWB ranging session after a specified timeout. + The timeout, in milliseconds, is defined by the + ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_SESSION_TIMEOUT_MS`` Kconfig option. + By default, the timeout is set to ``10000`` milliseconds. Configuring Access Manager ========================== To configure the Access Manager behavior, set options in your project’s :file:`prj.conf` file. -For example, to allow a maximum distance of 50 cm, add the following line to your prj.conf: +For example, to allow a maximum distance of 50 cm, add the following line: .. code-block:: kconfig CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM=50 -To use a custom implementation, provide your own implementation by modifying the files in the :file:`access_manager_impl_custom` file, and build the application with the ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_CUSTOM`` Kconfig option enabled. - -For instance: - -.. code-block:: - - west build -b nrf5340dk/nrf5340/cpuapp app -- -DCONFIG_DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_CUSTOM=y - Matter support ************** +.. note:: + Currently, the |APP_NAME| supports Matter only with Thread technology. + The |APP_NAME| integrates with the `Matter`_ protocol stack provided by the |NCS| to enable seamless provisioning of Aliro-specific credentials through smart home ecosystems. -This method is preferred over manual provisioning using the ``dl install`` and ``dl provisioning`` commands through the serial console. -Currently, the |APP_NAME| supports only Matter over Thread technology. The |APP_NAME| implements smart home access control using the Matter door lock cluster, which is natively supported in the |NCS| by the `Matter door lock`_ sample. |APP_NAME| uses most of the functionality of the Matter door lock sample. @@ -263,10 +337,21 @@ For example, to build the application with Bluetooth LE and UWB transport suppor Configuration ============= -You can change the maximum number of Kpersistent keys that can be stored by adjusting the ``CONFIG_MAX_NUMBER_OF_KPERSISTENT`` Kconfig option. -When using the default Access Manager implementation (``CONFIG_DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT``), this value is constrained by the ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS`` option, which defines the maximum number of Access Credential public keys that can be stored. +You can apply the following configuration options: + +.. list-table:: + :header-rows: 1 -.. important:: + * - Kconfig option + - Description + * - ``CONFIG_MAX_NUMBER_OF_KPERSISTENT`` + - Sets the maximum number of persistent keys that can be stored by the system. + This option defines the upper limit for the number of keys allocated in persistent storage. + * - ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS`` + - Defines the maximum number of Access Credential public keys that can be stored in the Access Manager. + This value constrains the effective maximum number of persistent keys used for access credentials. + +.. note:: The maximum number of Kpersistent keys must match the maximum number of stored Access Credential public keys. Configurations with mismatched limits are not supported and may result in undefined behavior. @@ -312,12 +397,20 @@ Configuration ============= The Step-up phase uses the Access Manager interface to make authorization decisions based on the verified Access Document. -When using the default Access Manager implementation (``CONFIG_DOOR_LOCK_ACCESS_MANAGER_IMPLEMENTATION_DEFAULT``), the maximum number of stored Access Credential public keys is controlled by the ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS`` option. - -The Step-up phase requires Credential Issuer public keys to verify the digital signature of Access Documents. -You can configure the maximum number of Credential Issuer public keys that can be stored using the ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_CREDENTIAL_ISSUER_MAX_STORED_KEYS`` Kconfig option (see :file:`app/src/aliro/access_manager_impl_default/Kconfig`). -The Credential Issuer public keys must be provisioned into the Reader before the Step-up phase can be used. +.. list-table:: + :header-rows: 1 + + * - Kconfig option + - Description + * - ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_ACCESS_CREDENTIAL_MAX_STORED_KEYS`` + - Sets the maximum number of Access Credential public keys that can be stored in the Access Manager. + This limit applies during the Step-up phase, where authorization decisions are made based on the verified Access Document. + * - ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_CREDENTIAL_ISSUER_MAX_STORED_KEYS`` + - Configures the maximum number of Credential Issuer public keys that can be stored. + These keys are required during the Step-up phase to verify the digital signature of Access Documents. + The keys must be provisioned into the Reader before the Step-up phase can be used. + See :file:`app/src/aliro/access_manager/Kconfig` for details. .. note:: Manual provisioning of Credential Issuer public keys is required only when Matter support is disabled. diff --git a/docs/firmware_update.rst b/docs/firmware_update.rst index 08bb6d19..9901a09c 100644 --- a/docs/firmware_update.rst +++ b/docs/firmware_update.rst @@ -17,12 +17,8 @@ Matter OTA If you build an application with Matter support (see :ref:`building_and_running` and the ``-DSNIPPET='matter'`` option), you can update the firmware of the device using the Matter Over-The-Air (OTA) update mechanism. This allows you to remotely upgrade the door lock firmware without physical access to the device. -.. note:: - This guide uses CHIP Tool as a Matter controller and assumes you have a commissioned Matter device. - For details on CHIP Tool usage, see the `Matter chip-tool guide`_. - -.. note:: - Currently, Matter OTA does not support updating the firmware of the QM35 UWB module. +This guide uses CHIP Tool as a Matter controller and assumes you have a commissioned Matter device. +For details on CHIP Tool usage, see the `Matter chip-tool guide`_. Prerequisites ============= @@ -33,9 +29,7 @@ Before starting the OTA update process, ensure that: * You have built the new Matter application with a higher version number than the one that is currently running on the device to be updated. The version is configured in the :file:`app/VERSION` file. The :file:`matter.ota` file generated in the build directory will be used as the OTA firmware image file. * You have the Matter OTA provider application available on your PC. - -.. note:: - You can download the precompiled OTA provider application from the `Matter fork release`_ page. + You can download the precompiled OTA provider application from the `Matter fork release`_ page. OTA update process ================== @@ -49,7 +43,6 @@ The following steps will guide you through the complete OTA update process: chip-ota-provider-app -f Where ```` is a path to the OTA firmware image file. - For example: .. code-block:: console @@ -157,7 +150,6 @@ The DFU over Bluetooth LE Simple Management Protocol (SMP) allows for seamless f By building the application with the ``-Dapp_SNIPPET=dfu_smp`` option, you can enable this feature and update your device's firmware wirelessly. .. note:: - For applications without Matter support, DFU over Bluetooth LE SMP is supported only on the nRF5340 DK. To build Matter applications with DFU over Bluetooth LE SMP, add the ``-DCONFIG_CHIP_DFU_OVER_BT_SMP=y`` option to the build command. Enabling Bluetooth LE SMP advertising @@ -224,10 +216,19 @@ To enable QM35 firmware upgrade support, build the application with the ``uwb_qm west build -b nrf5340dk/nrf5340/cpuapp app -- -DSNIPPET='uwb_qm35_dfu' -Dapp_SNIPPET='uwb_qm35_src;dfu_smp' +Configuration options +===================== + +You can control the QM35 DFU behavior with the following Kconfig options: + +* ``CONFIG_DOOR_LOCK_UWB_QM35_DFU`` - Enables QM35 firmware upgrade support. +* ``CONFIG_DOOR_LOCK_UWB_QM35_DFU_VERSION_COMPARISON_HIGHER`` - Perform an update only if the new version is higher (default). +* ``CONFIG_DOOR_LOCK_UWB_QM35_DFU_VERSION_COMPARISON_DIFFERENT`` - Perform an update if the version differs. + Flashing ======== -When flashing the application, use the following command: +When flashing the application, run the following command: .. code-block:: console @@ -235,6 +236,14 @@ When flashing the application, use the following command: The QM35 firmware is automatically programmed to external flash together with the main application. +.. note:: + For the nRF54LM20 DK, external flash support in ``west flash`` is not in a production state yet. + Therefore, using ``nrfutil`` is currently required to program the firmware: + + .. code-block:: console + + nrfutil device --x-ext-mem-config-file applications/doorlock/app/boards/nrf54lm20dk_spi_nrfutil_config.json program --firmware build/merged.hex --options verify=VERIFY_READ,ext_mem_erase_mode=ERASE_RANGES_TOUCHED_BY_FIRMWARE,reset=RESET_SOFT + Firmware upgrade procedure ========================== @@ -247,12 +256,3 @@ At runtime, the application performs the following steps: .. note:: During the update, the QM35 is temporarily unavailable. - -Configuration options -===================== - -The following Kconfig options control QM35 DFU behavior: - -* ``CONFIG_DOOR_LOCK_UWB_QM35_DFU`` - Enables QM35 firmware upgrade support. -* ``CONFIG_DOOR_LOCK_UWB_QM35_DFU_VERSION_COMPARISON_HIGHER`` - Perform an update only if the new version is higher (default). -* ``CONFIG_DOOR_LOCK_UWB_QM35_DFU_VERSION_COMPARISON_DIFFERENT`` - Perform an update if the version differs. diff --git a/docs/hardware_requirements.rst b/docs/hardware_requirements.rst index 2f084c75..e8cb56e9 100644 --- a/docs/hardware_requirements.rst +++ b/docs/hardware_requirements.rst @@ -14,7 +14,7 @@ To run and test the |APP_NAME|, you must have the required hardware. Development kit *************** -The application supports the following development kit (DK): +The application supports the following development kits (DK): .. list-table:: :header-rows: 1 @@ -42,8 +42,9 @@ The application supports the following development kit (DK): .. _hw_requirements_vddio_configuration: -Configuring VDDIO voltage -========================= +Configuring VDDIO voltage for nRF54L Series devices +=================================================== + The nRF54L15 DK and the nRF54LM20 DK operate at default voltage level of 1.8V. Some expansion boards, especially Arduino-style boards, require higher voltage to operate properly. @@ -107,9 +108,7 @@ The application supports the following NFC readers and their corresponding devel +===================+=================================+ | `ST25R100`_ | `X-NUCLEO-NFC09A1`_ | +-------------------+---------------------------------+ -| `ST25R3916B`_ | `X-NUCLEO-NFC08A1`_ | -+-------------------+---------------------------------+ -| `ST25R3911B`_ | `X-NUCLEO-NFC05A1`_ | +| `ST25R300`_ | `X-NUCLEO-NFC12A1`_ | +-------------------+---------------------------------+ .. note:: @@ -117,16 +116,21 @@ The application supports the following NFC readers and their corresponding devel The `X-NUCLEO-NFC09A1`_ board requires a minimum of 2.7V to operate. Because of that, you must adjust the GPIO voltage for the `nRF54L15 DK`_ and the `nRF54LM20 DK`_ as outlined in the :ref:`hw_requirements_vddio_configuration` section. -The `nRF54L15 DK`_ and the `nRF54LM20 DK`_ do not have Arduino-compatible header, therefore, you must connect your board using wires. -To connect the NFC reader expansion board to the DK, refer to the following pin mapping. -The pinout is applicable for each of the supported NFC reader expansion boards: +Connecting the NFC reader +========================= + +Follow the guidelines to connect the NFC reader expansion board based on your development kit: .. tabs:: - .. tab:: nRF54L15 DK + .. tab:: nRF54LM20 DK + + The `nRF54LM20 DK`_ does not have Arduino-compatible header, therefore, you must connect your board using wires. + To connect the NFC reader expansion board to the DK, refer to the following pin mapping. + The pinout is applicable for each of the supported NFC reader expansion boards: +-------------------+-----------------------+ - | nRF54L15 DK | X-NUCLEO board | + | nRF54LM20 DK | X-NUCLEO board | +===================+=======================+ | P1.13 | SCK MCU (D13) | +-------------------+-----------------------+ @@ -134,9 +138,9 @@ The pinout is applicable for each of the supported NFC reader expansion boards: +-------------------+-----------------------+ | P1.11 | MOSI MCU (D11) | +-------------------+-----------------------+ - | P2.08 | /SS MCU (D10) | + | P1.10 | /SS MCU (D10) | +-------------------+-----------------------+ - | P0.04 | IRQ MCU (A0) | + | P1.14 | IRQ MCU (A0) | +-------------------+-----------------------+ | VBUS | +5V | +-------------------+-----------------------+ @@ -145,16 +149,14 @@ The pinout is applicable for each of the supported NFC reader expansion boards: | GND | GND | +-------------------+-----------------------+ - .. figure:: /images/nRF54L_X_NUCLEO_connection.png - :scale: 50% - :alt: Expansion board connection to the nRF54L15 DK. - - X-NUCLEO expansion board connection to the nRF54L15 DK. + .. tab:: nRF54L15 DK - .. tab:: nRF54LM20 DK + The `nRF54L15 DK`_ does not have Arduino-compatible header, therefore, you must connect your board using wires. + To connect the NFC reader expansion board to the DK, refer to the following pin mapping. + The pinout is applicable for each of the supported NFC reader expansion boards: +-------------------+-----------------------+ - | nRF54LM20 DK | X-NUCLEO board | + | nRF54L15 DK | X-NUCLEO board | +===================+=======================+ | P1.13 | SCK MCU (D13) | +-------------------+-----------------------+ @@ -162,7 +164,7 @@ The pinout is applicable for each of the supported NFC reader expansion boards: +-------------------+-----------------------+ | P1.11 | MOSI MCU (D11) | +-------------------+-----------------------+ - | P1.24 | /SS MCU (D10) | + | P2.08 | /SS MCU (D10) | +-------------------+-----------------------+ | P0.04 | IRQ MCU (A0) | +-------------------+-----------------------+ @@ -173,20 +175,30 @@ The pinout is applicable for each of the supported NFC reader expansion boards: | GND | GND | +-------------------+-----------------------+ -When using the `nRF5340 DK`_ or `nRF52840 DK`_, you can connect the NFC reader expansion board directly using the Arduino-compatible header available on the DK. + .. figure:: /images/nRF54L_X_NUCLEO_connection.png + :scale: 50% + :alt: Expansion board connection to the nRF54L15 DK. -.. note:: + X-NUCLEO expansion board connection to the nRF54L15 DK. + + .. tab:: nRF5340 DK + + The `nRF5340 DK `_ has **P5** and **P20** connectors located between their Arduino headers. + These connectors might cause electrical shorts with the NFC reader expansion board, which can lead to NFC driver initialization failures and application crashes. + To prevent shorts, ensure proper connection of the NFC reader expansion board and maintain adequate clearance between the connectors and the expansion board. - The `nRF5340 DK `_ and `nRF52840 DK `_ have P5 and P20 connectors located between their Arduino headers. - These connectors might cause electrical shorts with the NFC reader expansion board, which can lead to NFC driver initialization failures and application crashes. - To prevent shorts, ensure proper connection of the NFC reader expansion board and maintain adequate clearance between the connectors and the expansion board. + .. tab:: nRF52840 DK + + The `nRF52840 DK `_ has **P5** and **P20** connectors located between their Arduino headers. + These connectors might cause electrical shorts with the NFC reader expansion board, which can lead to NFC driver initialization failures and application crashes. + To prevent shorts, ensure proper connection of the NFC reader expansion board and maintain adequate clearance between the connectors and the expansion board. .. _hw_requirements_uwb_module: Ultra wideband (UWB) module *************************** -The application supports the following UWB modules: +The application supports the following UWB module: +-------------------+-------------------------------------------------------------+ | UWB module | UWB module expansion board | @@ -197,19 +209,17 @@ The application supports the following UWB modules: With the Qorvo Arduino Interface Board, you can use the UWB module with the `nRF5340 DK`_ without requiring any additional hardware, as it connects directly to the Arduino header of the DK. .. note:: - To run the application with the QM35825 UWB module, you need the Qorvo Arduino Interface Board with special rework done on the board. - To obtain a reworked board, contact your local Qorvo support team. + Contact your local Qorvo support team in the following cases: -.. note:: - The QM35825 SoC requires Aliro-specific firmware to work with the Aliro access protocol. - To obtain this firmware, contact your local Qorvo support team. + * To obtain the Qorvo Arduino Interface Board with special rework done on the board. + You need it to run the application with the QM35825 UWB module. .. _hw_requirements_uwb_compatibility: UWB compatibility matrix ======================== -The following table shows the compatibility between the versions of the |APP_NAME|, UWB Aliro SDK, UWB FW, and |NCS|: +The following table shows the compatibility between the versions of the |APP_NAME|, UWB Aliro SDK, UWB FW, and the |NCS|: .. list-table:: UWB compatibility matrix :header-rows: 1 @@ -241,8 +251,7 @@ The following table shows the compatibility between the versions of the |APP_NAM - 3.2.0 .. note:: - The compatibility matrix shows tested and verified combinations of |APP_NAME| version, UWB Aliro SDK, UWB firmware, and |NCS| version. - + The compatibility matrix shows tested and verified combinations of |APP_NAME| version, UWB Aliro SDK, UWB firmware, and the |NCS| version. .. _hw_requirements_test_harness: diff --git a/docs/images/aliro-add-ons.svg b/docs/images/aliro-add-ons.svg index 9a89c274..faa48090 100644 --- a/docs/images/aliro-add-ons.svg +++ b/docs/images/aliro-add-ons.svg @@ -1,442 +1,507 @@ - - - - - - - - - - - - - - - - - Infocenter diagram - - - - - - - Sheet.1 - Max 750 px - - Sheet.2 - - - - Sheet.3 - - - - - Sheet.4 - - - - - - Max 750 px - - - - Sheet.5 - Max 700 px - - Sheet.6 - - - - Sheet.7 - - - - - Sheet.8 - - - - - - Max 700 px - - - Sheet.9 - - Sheet.10 - - - - Sheet.11 - - - - - - Sheet.12 - - Sheet.13 - - - - Sheet.14 - - - - - - Nordic Blue - - - - Sheet.16 - Aliro reference application - - - - - nRF Door Lock Reference Application - - Nordic Blueslate - Application - - - - - Application - - Block Double Arrow.1006 - - - - - - - Nordic Lake.1007 - Aliro stack - - - - - Aliro stack - - Nordic Lake.1010 - NFC reader - - - - - NFC reader - - Nordic Sky.1011 - STM RFAL API - - - - - STM RFAL API - - Nordic Sky.1012 - Zephyr PAL - - - - - Zephyr PAL - - Nordic Sky.1013 - STM RFAL - - - - - STM RFAL - - Nordic Sky.1014 - STM25R drivers - - - - - STM25R drivers - - Nordic Sky.1015 - Aliro stack API - - - - - Aliro stack API - - Nordic Sky.1016 - Aliro stack binary library - - - - - Aliro stack binary library - - Sheet.27 - west.yaml - - - - - west.yaml - - Sheet.28 - west.yaml - - - - - west.yaml - - Nordic Blueslate.1020 - nRF Connect SDK - - - - - nRF Connect SDK - - Sheet.30 - west.yaml - - - - - west.yaml - - - - - - - Line Arrow.1022 - - Sheet.32 - - - - Sheet.33 - - - - Sheet.34 - - Sheet.35 - - - - Sheet.36 - - - - - - - Sheet.37 - revision ”x” - - - - - revision ”x” - - Sheet.38 - revision ”y” - - - - - revision ”y” - - Nordic Lake.1026 - Nordic Aliro stack - - - - - Nordic Aliro stack - - Sheet.40 - west.yaml - - - - - west.yaml - - Sheet.41 - private - - - - - private - - - - - - - Line Arrow.1033 - - Sheet.43 - - - - Sheet.44 - - - - Sheet.45 - - Sheet.46 - - - - Sheet.47 - - - - - - - Nordic Blueslate.1034 - Zephyr OS - - - - - Zephyr OS - - Sheet.49 - revision ”a” (fixed, depends on the NCS revision) - - - - - revision ”a”(fixed, depends on the NCS revision) - - - - - - - Line Arrow.1036 - - Sheet.51 - - - - Sheet.52 - - - - Sheet.53 - - Sheet.54 - - - - Sheet.55 - - - - - - - Nordic Lake.1037 - UWB driver (currently not available) - - - - - Aliro UWB adapter(currently not available) - - - - - - - Line Arrow.1038 - - Sheet.58 - - - - Sheet.59 - - - - Sheet.60 - - Sheet.61 - - - - Sheet.62 - - - - - - - Sheet.63 - revision ”z” - - - - - revision ”z” - - Sheet.64 - west.yaml - - - - - west.yaml - - + + + + + + + + + + + + + + + + + Infocenter diagram + + + + + + + Arkusz.1 + Max 750 px + + Arkusz.2 + + + + Arkusz.3 + + Arkusz.4 + + + + Arkusz.5 + + + + + + Arkusz.6 + + + + + + Max 750 px + + + + Arkusz.7 + Max 700 px + + Arkusz.8 + + + + Arkusz.9 + + Arkusz.10 + + + + Arkusz.11 + + + + + + Arkusz.12 + + + + + + Max 700 px + + + Arkusz.13 + + Arkusz.14 + + + + Arkusz.15 + + Arkusz.16 + + + + Arkusz.17 + + + + + + + Arkusz.18 + + Arkusz.19 + + + + Arkusz.20 + + Arkusz.21 + + + + Arkusz.22 + + + + + + + Nordic Blue + + + + Arkusz.24 + Aliro reference application + + + + + nRF Door Lock Reference Application + + Nordic Blueslate + Application + + + + + Application + + Block Double Arrow.1006 + + + + + + + Nordic Lake.1007 + Aliro stack + + + + + Aliro stack + + Nordic Lake.1010 + NFC reader + + + + + NFC reader + + Nordic Sky.1011 + STM RFAL API + + + + + STM RFAL API + + Nordic Sky.1012 + Zephyr PAL + + + + + Zephyr PAL + + Nordic Sky.1013 + STM RFAL + + + + + STM RFAL + + Nordic Sky.1014 + STM25R drivers + + + + + STM25R drivers + + Nordic Sky.1015 + Aliro stack API + + + + + Aliro stack API + + Nordic Sky.1016 + Aliro stack binary library + + + + + Aliro stack binary library + + Arkusz.35 + west.yaml + + + + + west.yaml + + Arkusz.36 + west.yaml + + + + + west.yaml + + Nordic Blueslate.1020 + nRF Connect SDK + + + + + nRF Connect SDK + + Arkusz.38 + west.yaml + + + + + west.yaml + + + + + + + Line Arrow.1022 + + Arkusz.40 + + + + Arkusz.41 + + + + Arkusz.42 + + Arkusz.43 + + + + Arkusz.44 + + Arkusz.45 + + + + Arkusz.46 + + + + + + + + Arkusz.47 + revision ”x” + + + + + revision ”x” + + Arkusz.48 + revision ”y” + + + + + revision ”y” + + Nordic Lake.1026 + Nordic Aliro stack + + + + + Nordic Aliro stack + + Arkusz.50 + west.yaml + + + + + west.yaml + + Arkusz.51 + private + + + + + private + + + + + + + Line Arrow.1033 + + Arkusz.53 + + + + Arkusz.54 + + + + Arkusz.55 + + Arkusz.56 + + + + Arkusz.57 + + Arkusz.58 + + + + Arkusz.59 + + + + + + + + Nordic Blueslate.1034 + Zephyr OS + + + + + Zephyr OS + + Arkusz.61 + revision ”a” (fixed, depends on the NCS revision) + + + + + revision ”a”(fixed, depends on the NCS revision) + + + + + + + Line Arrow.1036 + + Arkusz.63 + + + + Arkusz.64 + + + + Arkusz.65 + + Arkusz.66 + + + + Arkusz.67 + + Arkusz.68 + + + + Arkusz.69 + + + + + + + + Nordic Lake.1037 + UWB driver (currently not available) + + + + + Aliro UWB adapter + + + + + + + Line Arrow.1038 + + Arkusz.72 + + + + Arkusz.73 + + + + Arkusz.74 + + Arkusz.75 + + + + Arkusz.76 + + Arkusz.77 + + + + Arkusz.78 + + + + + + + + Arkusz.79 + revision ”z” + + + + + revision ”z” + + Arkusz.80 + west.yaml + + + + + west.yaml + + Nordic Lake.81 + Aliro stack + + + + + UWB integration + + diff --git a/docs/images/door_lock_app_arch.svg b/docs/images/door_lock_app_arch.svg index 960fb113..1d0526e3 100644 --- a/docs/images/door_lock_app_arch.svg +++ b/docs/images/door_lock_app_arch.svg @@ -1,9 +1,9 @@ - + + viewBox="0 0 510.236 555.591" xml:space="preserve" color-interpolation-filters="sRGB" class="st38"> @@ -19,35 +19,37 @@ .st5 {fill:#c8ced3;font-family:Arial;font-size:0.916672em} .st6 {fill:#ffffff;stroke:none;stroke-linecap:butt;stroke-width:1} .st7 {fill:#00a9ce;stroke:none;stroke-width:1} - .st8 {fill:#ffffff;font-family:Arial;font-size:1.16666em} + .st8 {fill:#ffffff;font-family:Arial;font-size:1.00001em} .st9 {fill:#0033a0;stroke:none;stroke-width:1} - .st10 {fill:#ffffff;font-family:Arial;font-size:0.916672em} + .st10 {fill:#ffffff;font-family:Arial;font-size:0.833336em} .st11 {fill:#0077c8;stroke:#0033a0;stroke-width:1} - .st12 {fill:#005996;stroke:none;stroke-width:1} - .st13 {fill:#0070c0;stroke:#0033a0;stroke-width:1} - .st14 {fill:#feffff;font-family:Arial;font-size:0.916672em} - .st15 {fill:#333f48;stroke:none;stroke-width:1} - .st16 {fill:#ffffff;font-family:Arial;font-size:0.833336em} - .st17 {font-size:1em} - .st18 {fill:#ffcd00;stroke:none;stroke-width:1} + .st12 {fill:#ffffff;font-family:Arial;font-size:0.916672em} + .st13 {fill:#005996;stroke:none;stroke-width:1} + .st14 {font-size:1em} + .st15 {fill:#0070c0;stroke:#0033a0;stroke-width:1} + .st16 {fill:#feffff;font-family:Arial;font-size:0.916672em} + .st17 {fill:#333f48;stroke:none;stroke-width:1} + .st18 {fill:#ea700d;stroke:none;stroke-width:1} .st19 {fill:#3a4349;font-family:Arial;font-size:0.916672em} .st20 {fill:#c4d6a0;stroke:#3f3f3f;stroke-dasharray:15,9;stroke-width:1} - .st21 {fill:#ffcd00;stroke:#3f3f3f;stroke-dasharray:15,9;stroke-width:1} + .st21 {fill:#ea700d;stroke:#ffffff;stroke-dasharray:15,9;stroke-width:1} .st22 {fill:#9cdfeb;stroke:none;stroke-width:1} - .st23 {fill:#007e9a;stroke:none;stroke-width:1} - .st24 {fill:#ffffff;font-family:Arial;font-size:1.00001em} - .st25 {fill:#ffffff;stroke:#0033a0;stroke-width:1} - .st26 {fill:#333f48;font-family:Arial;font-size:0.916672em} - .st27 {fill:#eaf6f9;stroke:#eaf6f9;stroke-width:1} - .st28 {fill:#595959;font-family:Arial;font-size:0.916672em} - .st29 {fill:#57646e;stroke:none;stroke-width:1} - .st30 {fill:none;stroke:#57646e;stroke-linecap:butt;stroke-width:1} - .st31 {stroke:#57646e;stroke-linecap:butt;stroke-width:1} - .st32 {fill:#ffffff;stroke:#3f3f3f;stroke-dasharray:15,9;stroke-width:1} - .st33 {fill:#789440;stroke:none;stroke-width:1} - .st34 {fill:#ee2f4e;stroke:#000000;stroke-dasharray:11.25,6.75;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75} - .st35 {fill:#ffffff;font-family:Arial;font-size:0.75em} - .st36 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3} + .st23 {fill:#3a4349;font-family:Arial;font-size:0.75em} + .st24 {fill:#007e9a;stroke:none;stroke-width:1} + .st25 {fill:#ffffff;font-family:Arial;font-size:0.75em} + .st26 {fill:#ffcd00;stroke:none;stroke-width:1} + .st27 {fill:#ffffff;stroke:#0033a0;stroke-width:1} + .st28 {fill:#333f48;font-family:Arial;font-size:0.916672em} + .st29 {fill:#ffcd00;stroke:#3f3f3f;stroke-dasharray:15,9;stroke-width:1} + .st30 {fill:#eaf6f9;stroke:#eaf6f9;stroke-width:1} + .st31 {fill:#595959;font-family:Arial;font-size:0.916672em} + .st32 {fill:#57646e;stroke:none;stroke-width:1} + .st33 {fill:none;stroke:#57646e;stroke-linecap:butt;stroke-width:1} + .st34 {stroke:#57646e;stroke-linecap:butt;stroke-width:1} + .st35 {fill:#ffffff;stroke:#3f3f3f;stroke-dasharray:15,9;stroke-width:1} + .st36 {fill:#789440;stroke:none;stroke-width:1} + .st37 {fill:#ee2f4e;stroke:#000000;stroke-dasharray:11.25,6.75;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75} + .st38 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3} ]]> @@ -63,510 +65,606 @@ - Sheet.1 + Arkusz.1 Max 750 px - - Sheet.2 + + Arkusz.2 - - Sheet.3 + + Arkusz.3 - Sheet.4 + Arkusz.4 - Sheet.5 + Arkusz.5 - Sheet.6 + Arkusz.6 - Sheet.7 + Arkusz.7 - Sheet.8 + Arkusz.8 - Sheet.9 + Arkusz.9 - Sheet.10 + Arkusz.10 - - Sheet.11 - - + + Arkusz.11 + + Arkusz.12 + + + + Arkusz.13 + + Arkusz.14 + + + + Arkusz.15 + + Arkusz.16 + + + + Arkusz.17 + + + + + - - Sheet.12 + + Arkusz.14 - + Max 750 px - + - Sheet.13 + Arkusz.15 Max 700 px - - Sheet.14 + + Arkusz.16 - - Sheet.15 - - Sheet.16 + + Arkusz.17 + + Arkusz.18 - - Sheet.17 - - Sheet.18 + + Arkusz.19 + + Arkusz.20 - - Sheet.19 - - Sheet.20 + + Arkusz.21 + + Arkusz.22 - - Sheet.21 - - Sheet.22 + + Arkusz.23 + + Arkusz.24 - - Sheet.23 - - + + Arkusz.25 + + Arkusz.26 + + + + Arkusz.27 + + Arkusz.30 + + + + Arkusz.31 + + Arkusz.34 + + + + Arkusz.35 + + + + + - - Sheet.24 + + Arkusz.28 - + Max 700 px - - Sheet.25 - - Sheet.26 + + Arkusz.29 + + Arkusz.30 - - Sheet.27 - - Sheet.28 + + Arkusz.31 + + Arkusz.32 - - Sheet.29 - - Sheet.30 + + Arkusz.33 + + Arkusz.34 - - Sheet.31 - - Sheet.32 + + Arkusz.35 + + Arkusz.36 - - Sheet.33 - - Sheet.34 + + Arkusz.37 + + Arkusz.38 - - Sheet.35 - - + + Arkusz.39 + + Arkusz.40 + + + + Arkusz.41 + + Arkusz.46 + + + + Arkusz.47 + + Arkusz.52 + + + + Arkusz.53 + + + + + - - Sheet.36 - - Sheet.37 + + Arkusz.42 + + Arkusz.43 - - Sheet.38 - - Sheet.39 + + Arkusz.44 + + Arkusz.45 - - Sheet.40 - - Sheet.41 + + Arkusz.46 + + Arkusz.47 - - Sheet.42 - - Sheet.43 + + Arkusz.48 + + Arkusz.49 - - Sheet.44 - - Sheet.45 + + Arkusz.50 + + Arkusz.51 - - Sheet.46 - - + + Arkusz.52 + + Arkusz.53 + + + + Arkusz.54 + + Arkusz.61 + + + + Arkusz.62 + + Arkusz.69 + + + + Arkusz.70 + + + + + - + Nordic Blue - + - - Sheet.48 + + Arkusz.56 nRF54L15 SoC - - - Supported nRF SoCs - + + + Supported nRF SoCs + Nordic Blueslate Door lock application - - - - nRF Door Lock Reference Application - + + + + nRF Door Lock Reference Application + Nordic Lake Aliro stack - - - Aliro stack - - Nordic Blueslate.136 - - - + + + Aliro stack + Nordic Sky Access protocol - - - Access protocol - + + + Access protocol + Nordic Sky.15 Trusted framework - - - Trusted framework - + + + Trusted framework + Nordic Sky.16 Transport protocol - - - Transport protocol - + + + Transport protocol + Nordic Grass - + - - Sheet.55 + + Arkusz.64 Interfaces - - - Interfaces - + + + Interfaces + Nordic Dark Grey NFC interface - - - NFC interface - + + + NFC interface + Nordic Dark Grey.25 Crypto interface - - - Crypto interface - + + + Crypto interface + Nordic Light Grey UWB interface - - - UWB interface - + + + UWB interface + Nordic Light Grey.30 BLE interface - - - BLE interface - + + + BLE interface + Nordic Sun NFC RFAL - - - NFC RFAL - + + + NFC RFAL + Nordic Light Grey.141 Separate repository - + - + Nordic Light Grey.34 UWB driver - - - Aliro UWB adapter - + + + Aliro UWB adapter + Nordic Middle Grey - + - - Sheet.64 + + Arkusz.73 Zephyr OS - - - nRF Connect SDK based on Zephyr OS - + + + nRF Connect SDK based on Zephyr OS + Nordic Light Grey.38 SPI - - - SPI - + + + SPI + Nordic Light Grey.39 GPIO - - - GPIO - + + + GPIO + Nordic Light Grey.40 BLE - - - BLE - + + + BLE + Nordic Light Grey.41 Crypto - - - Crypto - + + + PSA API + Nordic Light Grey.42 Timers - - - Timers - + + + Timers + Nordic Light Grey.43 Power Management - - - Power management - + + + Power management + Nordic Light Grey.44 Memory - - - Memory - + + + Memory + Nordic Light Grey.45 Threading - - - Threading - + + + Threading + Nordic Light Grey.46 - - - - UART - + + + UART + Nordic Sun.47 ST25R - - - ST25R - + + + ST25R + Nordic Sun.1001 External module - - - External module - + + + External hardware module + Nordic Light Grey.1002 Separate repository - - - Separate repository - + + + Separate repository + Nordic Light Grey.1005 UWB module - - - UWB module - + + + UWB module + Block Double Arrow.1006 - + - - Sheet.79 + + Arkusz.88 SPI - - - SPI - - Sheet.80 + + + SPI + + Arkusz.89 SPI - - - SPI - + + + SPI + Line Double Arrow.1010 - - Sheet.82 - + + Arkusz.91 + - - Sheet.83 - + + Arkusz.92 + - - Sheet.84 - + + Arkusz.93 + - - Sheet.85 - - Sheet.86 + + Arkusz.94 + + Arkusz.95 - - Sheet.87 - - Sheet.88 + + Arkusz.96 + + Arkusz.97 - - Sheet.89 - - Sheet.90 + + Arkusz.98 + + Arkusz.99 - - Sheet.91 - - Sheet.92 + + Arkusz.100 + + Arkusz.101 - - Sheet.93 - - Sheet.94 + + Arkusz.102 + + Arkusz.103 - - Sheet.95 - - + + Arkusz.104 + + Arkusz.105 + + + + Arkusz.106 + + Arkusz.114 + + + + Arkusz.115 + + Arkusz.124 + + + + Arkusz.125 + + + + + @@ -574,60 +672,81 @@ - + Line Double Arrow.1011 - - Sheet.97 - + + Arkusz.108 + - - Sheet.98 - + + Arkusz.109 + - - Sheet.99 - + + Arkusz.110 + - - Sheet.100 - - Sheet.101 + + Arkusz.111 + + Arkusz.112 - - Sheet.102 - - Sheet.103 + + Arkusz.113 + + Arkusz.114 - - Sheet.104 - - Sheet.105 + + Arkusz.115 + + Arkusz.116 - - Sheet.106 - - Sheet.107 + + Arkusz.117 + + Arkusz.118 - - Sheet.108 - - Sheet.109 + + Arkusz.119 + + Arkusz.120 - - Sheet.110 - - + + Arkusz.121 + + Arkusz.122 + + + + Arkusz.123 + + Arkusz.133 + + + + Arkusz.134 + + Arkusz.145 + + + + Arkusz.146 + + + + + @@ -635,123 +754,125 @@ - + Nordic Light Grey.140 Separate repository - - - Optional component - + + + Optional component + Nordic Light Grey.144 Separate repository - - - Application layer - - Sheet.116 + + + Application layer + + Arkusz.126 Zephyr OS - - - Matter stack - + + + Matter stack + Nordic Light Grey.146 Separate repository - - - Data model - + + + Data model + Nordic Light Grey.147 Separate repository - - - Interaction model - + + + Interaction model + Nordic Light Grey.148 Separate repository - - - Action framing - + + + Action framing + Nordic Light Grey.149 Separate repository - - - Security - + + + Security + Nordic Light Grey.150 Separate repository - - - Message framing + routing - + + + Message framing + routing + Nordic Light Grey.151 Separate repository - - - IP framing - + + + IP framing + Nordic Light Grey.152 Separate repository - - - Interfaces - - Nordic Light Grey.126 + + + Interfaces + + Nordic Light Grey.127 Timers - - - I2C - - Nordic Light Grey.127 - Timers + + + Flash + + Nordic Light Grey.131 + Separate repository - - - Flash - - Nordic Blueslate.134 - - - - Nordic Fall - PSA API + + + OpenThread + + Nordic Sun.139 + NFC RFAL - - - PSA API - - Nordic Light Grey.131 - Separate repository + + + BT NUS + + Nordic Sun.140 + NFC RFAL + + + + + BT DFU SMP + + Nordic Dark Grey.150 + Crypto interface - - - OpenThread + + + ... diff --git a/docs/images/nfc_current_plot_active_rfo_comparison_st25r300.png b/docs/images/nfc_current_plot_active_rfo_comparison_st25r300.png new file mode 100644 index 00000000..5cba7037 Binary files /dev/null and b/docs/images/nfc_current_plot_active_rfo_comparison_st25r300.png differ diff --git a/docs/images/nfc_current_plot_active_worker_interval_comparison.png b/docs/images/nfc_current_plot_active_worker_interval_comparison.png new file mode 100644 index 00000000..80111334 Binary files /dev/null and b/docs/images/nfc_current_plot_active_worker_interval_comparison.png differ diff --git a/docs/images/nfc_current_plot_idle_wum_comparison.png b/docs/images/nfc_current_plot_idle_wum_comparison.png new file mode 100644 index 00000000..4c999c44 Binary files /dev/null and b/docs/images/nfc_current_plot_idle_wum_comparison.png differ diff --git a/docs/images/nfc_current_plot_module_idle_x_nucleo_nfc12a1.png b/docs/images/nfc_current_plot_module_idle_x_nucleo_nfc12a1.png new file mode 100644 index 00000000..4a8b4597 Binary files /dev/null and b/docs/images/nfc_current_plot_module_idle_x_nucleo_nfc12a1.png differ diff --git a/docs/images/nfc_current_plot_soc_idle_nrf5340.png b/docs/images/nfc_current_plot_soc_idle_nrf5340.png new file mode 100644 index 00000000..9cf1fb1f Binary files /dev/null and b/docs/images/nfc_current_plot_soc_idle_nrf5340.png differ diff --git a/docs/images/nfc_current_schematic_module_st25r300_ppk2.png b/docs/images/nfc_current_schematic_module_st25r300_ppk2.png new file mode 100644 index 00000000..5c9ec63b Binary files /dev/null and b/docs/images/nfc_current_schematic_module_st25r300_ppk2.png differ diff --git a/docs/images/nfc_current_schematic_soc_nrf5340_ppk2.png b/docs/images/nfc_current_schematic_soc_nrf5340_ppk2.png new file mode 100644 index 00000000..ed5ba797 Binary files /dev/null and b/docs/images/nfc_current_schematic_soc_nrf5340_ppk2.png differ diff --git a/docs/index.rst b/docs/index.rst index bc7d79c9..e59b6a3b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,7 +9,7 @@ |APP_NAME| is a sample application that demonstrates how to integrate the Aliro and `Matter `_ stacks to implement fully functional door lock firmware. Aliro is the new industry-standard access control and communication protocol, offering a secure, convenient, and consistent experience for users using smartphones, wearables, or other smart digital devices to unlock doors and openings. -Aliro features several key features that distinguish it from existing access protocols: +Aliro includes several key features that distinguish it from existing access protocols: * Interoperability and compatibility - Ensures seamless interaction between access readers, such as electronic locks and access control readers, and User Devices like a smartphone and wearables. The standardized solution allows manufacturer-independent devices and readers to work together without compromising security. @@ -22,12 +22,14 @@ Aliro features several key features that distinguish it from existing access pro :caption: Subpages: door_lock_app_arch.rst + reference_application_interactions.rst other_addons.rst hardware_requirements.rst software_requirements.rst building_and_running.rst + nfc_power_measurements.rst testing.rst firmware_update.rst troubleshooting.rst release_notes.rst - known_issues.rst + known_issues_and_limitations.rst diff --git a/docs/known_issues.rst b/docs/known_issues.rst deleted file mode 100644 index d6a5bddb..00000000 --- a/docs/known_issues.rst +++ /dev/null @@ -1,242 +0,0 @@ -.. _known_issues: - -Known issues -############ - -.. contents:: - :local: - :depth: 2 - -Known issues listed on this page are valid for the current state of development. -Refer to the following sections for more information on specific items. - -A known issue can list one or both of the following entries: - -* **Affected platforms:** - - If a known issue does not have any specific platforms listed, it is valid for all hardware platforms. - -* **Workaround:** - - Some known issues have a workaround. - Sometimes, they are discovered later and added over time. - -The |APP_NAME| v0.6.0 -********************* - -The application crashes when using UWB and NFC on the same SPI bus has been resolved. - The `QM35825`_ UWB module and the X-NUCLEO NFC reader board can now be used together on the same SPI bus. - Use the ``uwb_qm35`` snippet when building the application to configure both NFC and UWB modules to share the same SPI bus. - The snippet configures both devices to use different chip select (CS) pins on the same SPI bus. - -The |APP_NAME| v0.5.0 -********************* - -AL-493: Expedited-fast phase does not work after the Step-up phase - When the Step-up phase is completed, the Kpersistent key is not preserved and the Reader does not proceed to the Expedited-fast phase. - -AL-377: Error messages visible in the Reader's serial console during ranging session - When the Reader is close to the Test Harness during a ranging session, the following error messages may appear in the Reader's serial console: - - .. code-block:: - - uwb: Controlee report error status: 0x22 on slot id: 0 - uwb: Controlee report error status: 0x21 on slot id: 0 - -The application crashes when the UWB module QM35825, used with Qorvo Arduino Interface Board, is connected to the nRF5340 DK and the X-NUCLEO-NFC09A1 board. - This issue arises because the QM35825 module forces the SPI MISO line to remain low at all times, which prevents the X-NUCLEO-NFC09A1 board from initializing properly. - - **Workaround 1:** Use a different SPI bus to connect the X-NUCLEO-NFC09A1 board. - - **Workaround 2:** For test purposes, use the ``uwb_qm35`` snippet to disable the NFC transport protocol. - This way, the X-NUCLEO-NFC09A1 board initialization is not required. - -AL-333: The NFC transport lost error occurs in test cases for granting access to the door lock - When executing test cases that determine if access to the door lock is granted or denied, the ``NFC transport lost`` error occurs. - The expected outcome is ``ACCESS GRANTED`` or the ``Provided User Device public key not found in Access Manager database`` log in the serial output of the DUT. - -AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use - Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. - The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). - - **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. - Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. - -AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step - During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. - -The |APP_NAME| v0.4.0 -********************* - -AL-377: Error messages visible in the Reader's serial console during ranging session. - When the Reader is close to the Test Harness during a ranging session, the following error messages may appear in the Reader's serial console: - - .. code-block:: - - uwb: Controlee report error status: 0x22 on slot id: 0 - uwb: Controlee report error status: 0x21 on slot id: 0 - -The application crashes when the UWB module QM35825, used with Qorvo Arduino Interface Board, is connected to the nRF5340 DK and the X-NUCLEO-NFC09A1 board. - This issue arises because the QM35825 module forces the SPI MISO line to remain low at all times, which prevents the X-NUCLEO-NFC09A1 board from initializing properly. - - **Workaround 1:** Use a different SPI bus to connect the X-NUCLEO-NFC09A1 board. - - **Workaround 2:** For test purposes, use the ``uwb_qm35`` snippet to disable the NFC transport protocol. - This way, the X-NUCLEO-NFC09A1 board initialization is not required. - -AL-333: The NFC transport lost error occurrs in test cases for granting access to the door lock - When executing test cases that determine if access to the door lock is granted or denied, the ``NFC transport lost`` error occurrs. - The expected outcome is ``ACCESS GRANTED`` or the ``Provided User Device public key not found in Access Manager database`` log in the serial output of the DUT. - -AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use - Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. - The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). - - **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. - Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. - -AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step - During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. - -The |APP_NAME| v0.3.1 -********************* - -AL-377: Error messages visible in the Reader's serial console during ranging session. - When the Reader is close to the Test Harness during a ranging session, the following error messages may appear in the Reader's serial console: - - .. code-block:: - - uwb: Controlee report error status: 0x22 on slot id: 0 - uwb: Controlee report error status: 0x21 on slot id: 0 - -The application crashes when the UWB module QM35825, used with Qorvo Arduino Interface Board, is connected to the nRF5340 DK and the X-NUCLEO-NFC09A1 board. - This issue arises because the QM35825 module forces the SPI MISO line to remain low at all times, which prevents the X-NUCLEO-NFC09A1 board from initializing properly. - - **Workaround 1:** Use a different SPI bus to connect the X-NUCLEO-NFC09A1 board. - - **Workaround 2:** For test purposes, use the ``uwb_qm35`` snippet to disable the NFC transport protocol. - This way, the X-NUCLEO-NFC09A1 board initialization is not required. - -AL-333: The NFC transport lost error occurrs in test cases for granting access to the door lock - When executing test cases that determine if access to the door lock is granted or denied, the ``NFC transport lost`` error occurrs. - The expected outcome is ``ACCESS GRANTED`` or the ``Provided User Device public key not found in Access Manager database`` log in the serial output of the DUT. - -AL-335: Watchdog expires for an NFC session upon test execution - When executing the RD-NFC-STDTXN-2.0 test, the ``Watchdog expired for NFC session`` event occasionally occurs. - This issue is caused by missing AUTH1 response from the test harness, despite logs indicating that the AUTH1 response was sent. - -AL-239: Occasional timeout occurs in the Reader when executing the RD-NFC-STDTXN-2.0 test from the test harness - When executing the RD-NFC-STDTXN-2.0 test case, the Reader might report an RX timeout error code in the serial console. - For more details, see `Test Harness issue #191`_. - -AL-282: An undefined access decision occurs when executing the RD-NFC-STDTXN-2.0 test harness case in a loop - When executing the RD-NFC-STDTXN-2.0 test case multiple times in a row (with valid credentials provisioned), the Reader might not report the access decision. - As a result, the ``ACCESS GRANTED`` or ``ACCESS DENIED`` logs are not displayed in the serial output of the Reader under test. - -AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use - Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. - The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). - - **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. - Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. - -AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step - During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. - - -The |APP_NAME| v0.3.0 -********************* - -The application crashes when the UWB module QM35825, used with Qorvo Arduino Interface Board, is connected to the nRF5340 DK and the X-NUCLEO-NFC09A1 board. - This issue arises because the QM35825 module forces the SPI MISO line to remain low at all times, which prevents the X-NUCLEO-NFC09A1 board from initializing properly. - - **Workaround 1:** Use a different SPI bus to connect the X-NUCLEO-NFC09A1 board. - - **Workaround 2:** For test purposes, use the ``uwb_qm35`` snippet to disable the NFC transport protocol. - This way, the X-NUCLEO-NFC09A1 board initialization is not required. - -The QM35825 ranging measurement is not working with the test harness. - Currently, there is no workaround for this issue. - -The current implementation of the Access Manager does not support multiple Aliro sessions. - Even though, the Aliro stack supports multiple sessions, the Access Manager implementation is limited to one session at a time. - -AL-333: The NFC transport lost error occurrs in test cases for granting access to the door lock - When executing test cases that determine if access to the door lock is granted or denied, the ``NFC transport lost`` error occurrs. - The expected outcome is ``ACCESS GRANTED`` or the ``Invalid Signature`` log in the serial output of the DUT. - -AL-335: Watchdog expires for an NFC session upon test execution - When executing the RD-NFC-STDTXN-2.0 test, the ``Watchdog expired for NFC session`` event occasionally occurs. - This issue is caused by missing AUTH1 response from the test harness, despite logs indicating that the AUTH1 response was sent. - -AL-239: Occasional timeout occurs in the Reader when executing the RD-NFC-STDTXN-2.0 test from the test harness - When executing the RD-NFC-STDTXN-2.0 test case, the Reader might report an RX timeout error code in the serial console. - For more details, see `Test Harness issue #191`_. - -AL-282: An undefined access decision occurs when executing the RD-NFC-STDTXN-2.0 test harness case in a loop - When executing the RD-NFC-STDTXN-2.0 test case multiple times in a row (with valid credentials provisioned), the Reader might not report the access decision. - As a result, the ``ACCESS GRANTED`` or ``ACCESS DENIED`` logs are not displayed in the serial output of the Reader under test. - -AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use - Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. - The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). - - **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. - Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. - -AL-159: Cannot provision multiple User Devices - The system allows provisioning of only one User Device to the Reader device. - -AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step - During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. - -The |APP_NAME| v0.2.0 -********************* - -AL-239: Occasional timeout occurs in the Reader when executing the RD-NFC-STDTXN-2.0 test from the test harness - When executing the RD-NFC-STDTXN-2.0 test case, the Reader might report an RX timeout error code in the serial console. - For more details, see `Test Harness issue #191`_. - -AL-282: An undefined access decision occurs when executing the RD-NFC-STDTXN-2.0 test harness case in a loop - When executing the RD-NFC-STDTXN-2.0 test case multiple times in a row (with valid credentials provisioned), the Reader might not report the access decision. - As a result, the ``ACCESS GRANTED`` or ``ACCESS DENIED`` logs are not displayed in the serial output of the Reader under test. - -AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use - Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. - The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). - - **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. - Attach the X-NUCLEO-NFC09A1 shield to the supported Nordic development kit, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and reflash the DK. - -AL-158: Access control is insufficient due to relying only on signature verification - The Reader device does not have the capability to configure additional access rules. - Access Manager is not implemented. - -AL-159: Cannot provision multiple User Devices - The system allows provisioning of only one User Device to the Reader device. - -AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step - During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. - - **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. - Attach the X-NUCLEO-NFC09A1 shield to the supported Nordic development kit, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and reflash the DK. - -The |APP_NAME| v0.1.0 -********************* - -AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use - Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. - The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). - - **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. - Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. - -AL-158: Access control is insufficient due to relying only on signature verification - The Reader device does not have the capability to configure additional access rules. - Access Manager is not implemented. - -AL-159: Cannot provision multiple User Devices - The system allows provisioning of only one User Device to the Reader device. - -AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step - During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. diff --git a/docs/known_issues_and_limitations.rst b/docs/known_issues_and_limitations.rst new file mode 100644 index 00000000..c16f310a --- /dev/null +++ b/docs/known_issues_and_limitations.rst @@ -0,0 +1,314 @@ +.. _known_issues_and_limitations: + +Known issues and limitations +############################ + +.. contents:: + :local: + :depth: 2 + + +This page describes the current limitations of the application and documents known issues that may affect functionality or platform support. + +Current limitations +******************* + +The following optional Aliro features are out-of-scope for the |APP_NAME|: + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Out-of-scope Aliro feature + - Supported instead + * - Reader Descriptor + - -- + * - Mailbox + - -- + * - Time concept + - -- + * - Revocation Document + - -- + * - Sending the Reader certificate in the AUTH1 command + - Sending the Reader certificate through the LOAD_CERT command + * - Extended length APDU + - APDU Chaining + * - ``key_slot`` value in the AUTH1 command ``command_parameter`` field + - Access Credential Public Key + * - Aliro schedules + - Matter schedules + * - Pass Through Message ID in the BLE Aliro Message + - -- + * - Expedited-Fast Phase authentication based on Credential Issuer CA Public Key + - -- + +Known issues +************ + +Known issues listed on this page are valid for the current state of development. +Refer to the following sections for more information on specific items. + +A known issue can list one or both of the following entries: + +* **Affected platforms:** + + If a known issue does not have any specific platforms listed, it is valid for all hardware platforms. + +* **Workaround:** + + Some known issues have a workaround. + Sometimes, they are discovered later and added over time. + +The |APP_NAME| v1.0.0 +********************* + +.. toggle:: + + AL-717: Matter onboarding with NFC is not supported + Matter onboarding using NFC does not work. + + **Workaround:** + Use QR code scanning mechanism for Matter device commissioning. + + AL-718: Access Document is not stored on the nRF52840 platform + Access Document (AD) is not stored persistently on the nRF52840 platform when using the ``CONFIG_DOOR_LOCK_STEP_UP_PHASE`` Kconfig build option. + As a result, Kpersistent is also not stored persistently on the nRF52840 device when the ``CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE`` Kconfig option is enabled together with ``CONFIG_DOOR_LOCK_STEP_UP_PHASE``. + + **Workaround:** + Implement a custom persistent storage mechanism for the Access Document on the nRF52840 platform. + + **Affected platforms:** nRF52840 DK + +The |APP_NAME| v0.6.0 +********************* + +.. toggle:: + + The application crashes when using UWB and NFC on the same SPI bus has been resolved. + The `QM35825`_ UWB module and the X-NUCLEO NFC reader board can now be used together on the same SPI bus. + Use the ``uwb_qm35`` snippet when building the application to configure both NFC and UWB modules to share the same SPI bus. + The snippet configures both devices to use different chip select (CS) pins on the same SPI bus. + +The |APP_NAME| v0.5.0 +********************* + +.. toggle:: + + AL-493: Expedited-fast phase does not work after the Step-up phase + When the Step-up phase is completed, the Kpersistent key is not preserved and the Reader does not proceed to the Expedited-fast phase. + + AL-377: Error messages visible in the Reader's serial console during ranging session + When the Reader is close to the Test Harness during a ranging session, the following error messages may appear in the Reader's serial console: + + .. code-block:: + + uwb: Controlee report error status: 0x22 on slot id: 0 + uwb: Controlee report error status: 0x21 on slot id: 0 + + The application crashes when the UWB module QM35825, used with Qorvo Arduino Interface Board, is connected to the nRF5340 DK and the X-NUCLEO-NFC09A1 board. + This issue arises because the QM35825 module forces the SPI MISO line to remain low at all times, which prevents the X-NUCLEO-NFC09A1 board from initializing properly. + + **Workaround 1:** Use a different SPI bus to connect the X-NUCLEO-NFC09A1 board. + + **Workaround 2:** For test purposes, use the ``uwb_qm35`` snippet to disable the NFC transport protocol. + This way, the X-NUCLEO-NFC09A1 board initialization is not required. + + AL-333: The NFC transport lost error occurs in test cases for granting access to the door lock + When executing test cases that determine if access to the door lock is granted or denied, the ``NFC transport lost`` error occurs. + The expected outcome is ``ACCESS GRANTED`` or the ``Provided User Device public key not found in Access Manager database`` log in the serial output of the DUT. + + AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use + Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. + The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). + + **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. + Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. + + AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step + During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. + +The |APP_NAME| v0.4.0 +********************* + +.. toggle:: + + AL-377: Error messages visible in the Reader's serial console during ranging session. + When the Reader is close to the Test Harness during a ranging session, the following error messages may appear in the Reader's serial console: + + .. code-block:: + + uwb: Controlee report error status: 0x22 on slot id: 0 + uwb: Controlee report error status: 0x21 on slot id: 0 + + The application crashes when the UWB module QM35825, used with Qorvo Arduino Interface Board, is connected to the nRF5340 DK and the X-NUCLEO-NFC09A1 board. + This issue arises because the QM35825 module forces the SPI MISO line to remain low at all times, which prevents the X-NUCLEO-NFC09A1 board from initializing properly. + + **Workaround 1:** Use a different SPI bus to connect the X-NUCLEO-NFC09A1 board. + + **Workaround 2:** For test purposes, use the ``uwb_qm35`` snippet to disable the NFC transport protocol. + This way, the X-NUCLEO-NFC09A1 board initialization is not required. + + AL-333: The NFC transport lost error occurrs in test cases for granting access to the door lock + When executing test cases that determine if access to the door lock is granted or denied, the ``NFC transport lost`` error occurrs. + The expected outcome is ``ACCESS GRANTED`` or the ``Provided User Device public key not found in Access Manager database`` log in the serial output of the DUT. + + AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use + Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. + The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). + + **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. + Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. + + AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step + During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. + +The |APP_NAME| v0.3.1 +********************* + +.. toggle:: + + AL-377: Error messages visible in the Reader's serial console during ranging session. + When the Reader is close to the Test Harness during a ranging session, the following error messages may appear in the Reader's serial console: + + .. code-block:: + + uwb: Controlee report error status: 0x22 on slot id: 0 + uwb: Controlee report error status: 0x21 on slot id: 0 + + The application crashes when the UWB module QM35825, used with Qorvo Arduino Interface Board, is connected to the nRF5340 DK and the X-NUCLEO-NFC09A1 board. + This issue arises because the QM35825 module forces the SPI MISO line to remain low at all times, which prevents the X-NUCLEO-NFC09A1 board from initializing properly. + + **Workaround 1:** Use a different SPI bus to connect the X-NUCLEO-NFC09A1 board. + + **Workaround 2:** For test purposes, use the ``uwb_qm35`` snippet to disable the NFC transport protocol. + This way, the X-NUCLEO-NFC09A1 board initialization is not required. + + AL-333: The NFC transport lost error occurrs in test cases for granting access to the door lock + When executing test cases that determine if access to the door lock is granted or denied, the ``NFC transport lost`` error occurrs. + The expected outcome is ``ACCESS GRANTED`` or the ``Provided User Device public key not found in Access Manager database`` log in the serial output of the DUT. + + AL-335: Watchdog expires for an NFC session upon test execution + When executing the RD-NFC-STDTXN-2.0 test, the ``Watchdog expired for NFC session`` event occasionally occurs. + This issue is caused by missing AUTH1 response from the test harness, despite logs indicating that the AUTH1 response was sent. + + AL-239: Occasional timeout occurs in the Reader when executing the RD-NFC-STDTXN-2.0 test from the test harness + When executing the RD-NFC-STDTXN-2.0 test case, the Reader might report an RX timeout error code in the serial console. + For more details, see `Test Harness issue #191`_. + + AL-282: An undefined access decision occurs when executing the RD-NFC-STDTXN-2.0 test harness case in a loop + When executing the RD-NFC-STDTXN-2.0 test case multiple times in a row (with valid credentials provisioned), the Reader might not report the access decision. + As a result, the ``ACCESS GRANTED`` or ``ACCESS DENIED`` logs are not displayed in the serial output of the Reader under test. + + AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use + Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. + The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). + + **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. + Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. + + AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step + During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. + + +The |APP_NAME| v0.3.0 +********************* + +.. toggle:: + + The application crashes when the UWB module QM35825, used with Qorvo Arduino Interface Board, is connected to the nRF5340 DK and the X-NUCLEO-NFC09A1 board. + This issue arises because the QM35825 module forces the SPI MISO line to remain low at all times, which prevents the X-NUCLEO-NFC09A1 board from initializing properly. + + **Workaround 1:** Use a different SPI bus to connect the X-NUCLEO-NFC09A1 board. + + **Workaround 2:** For test purposes, use the ``uwb_qm35`` snippet to disable the NFC transport protocol. + This way, the X-NUCLEO-NFC09A1 board initialization is not required. + + The QM35825 ranging measurement is not working with the test harness. + Currently, there is no workaround for this issue. + + The current implementation of the Access Manager does not support multiple Aliro sessions. + Even though, the Aliro stack supports multiple sessions, the Access Manager implementation is limited to one session at a time. + + AL-333: The NFC transport lost error occurrs in test cases for granting access to the door lock + When executing test cases that determine if access to the door lock is granted or denied, the ``NFC transport lost`` error occurrs. + The expected outcome is ``ACCESS GRANTED`` or the ``Invalid Signature`` log in the serial output of the DUT. + + AL-335: Watchdog expires for an NFC session upon test execution + When executing the RD-NFC-STDTXN-2.0 test, the ``Watchdog expired for NFC session`` event occasionally occurs. + This issue is caused by missing AUTH1 response from the test harness, despite logs indicating that the AUTH1 response was sent. + + AL-239: Occasional timeout occurs in the Reader when executing the RD-NFC-STDTXN-2.0 test from the test harness + When executing the RD-NFC-STDTXN-2.0 test case, the Reader might report an RX timeout error code in the serial console. + For more details, see `Test Harness issue #191`_. + + AL-282: An undefined access decision occurs when executing the RD-NFC-STDTXN-2.0 test harness case in a loop + When executing the RD-NFC-STDTXN-2.0 test case multiple times in a row (with valid credentials provisioned), the Reader might not report the access decision. + As a result, the ``ACCESS GRANTED`` or ``ACCESS DENIED`` logs are not displayed in the serial output of the Reader under test. + + AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use + Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. + The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). + + **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. + Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. + + AL-159: Cannot provision multiple User Devices + The system allows provisioning of only one User Device to the Reader device. + + AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step + During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. + +The |APP_NAME| v0.2.0 +********************* + +.. toggle:: + + AL-239: Occasional timeout occurs in the Reader when executing the RD-NFC-STDTXN-2.0 test from the test harness + When executing the RD-NFC-STDTXN-2.0 test case, the Reader might report an RX timeout error code in the serial console. + For more details, see `Test Harness issue #191`_. + + AL-282: An undefined access decision occurs when executing the RD-NFC-STDTXN-2.0 test harness case in a loop + When executing the RD-NFC-STDTXN-2.0 test case multiple times in a row (with valid credentials provisioned), the Reader might not report the access decision. + As a result, the ``ACCESS GRANTED`` or ``ACCESS DENIED`` logs are not displayed in the serial output of the Reader under test. + + AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use + Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. + The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). + + **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. + Attach the X-NUCLEO-NFC09A1 shield to the supported Nordic development kit, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and reflash the DK. + + AL-158: Access control is insufficient due to relying only on signature verification + The Reader device does not have the capability to configure additional access rules. + Access Manager is not implemented. + + AL-159: Cannot provision multiple User Devices + The system allows provisioning of only one User Device to the Reader device. + + AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step + During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. + + **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. + Attach the X-NUCLEO-NFC09A1 shield to the supported Nordic development kit, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and reflash the DK. + +The |APP_NAME| v0.1.0 +********************* + +.. toggle:: + + AL-148: The RD-NFC-STDTXN-1.0 test case fails when the NFC module ST X-NUCLEO-NFC05A1 is in use + Testing RD-NFC-STDTXN-1.0 with the NFC module NFC05A1 results in failure. + The issue arises from an error indicated by the test harness, which detects the presence of an invalid TLV tag in the payload received from the Device Under Test (DUT). + + **Workaround:** Switch to X-NUCLEO-NFC09A1, a newer, recommended revision of the NFC ST module. + Attach X-NUCLEO-NFC09A1 shield to the nRF54L15 DK, rebuild the firmware with the ``CONFIG_ST25R200_DRV`` Kconfig option enabled, and re-flash the nRF54L15 DK. + + AL-158: Access control is insufficient due to relying only on signature verification + The Reader device does not have the capability to configure additional access rules. + Access Manager is not implemented. + + AL-159: Cannot provision multiple User Devices + The system allows provisioning of only one User Device to the Reader device. + + AL-161: The RD-NFC-STDTXN-2.0 [X-NUCLEO-NFC08A1] test exhibits a delay in the transaction initiation step + During the RD-NFC-STDTXN-2.0 test execution, there is a noticeable delay of a few seconds after the transaction initiation step. diff --git a/docs/links.txt b/docs/links.txt index 98cf8012..fcd8aba3 100644 --- a/docs/links.txt +++ b/docs/links.txt @@ -12,6 +12,7 @@ .. _`Setting up the command-line build environment`: https://docs.nordicsemi.com/bundle/ncs-3.2.0/page/nrf/installation/install_ncs.html#set_up_the_command-line_build_environment .. _`Zephyr CMake package`: https://docs.nordicsemi.com/bundle/ncs-3.2.0/page/zephyr/build/zephyr_cmake_package.html#cmake-pkg .. _`Test Harness issue #191`: https://github.com/csa-access-control/aliro-certification-tool/issues/191 +.. _`ncs app index`: https://nrfconnect.github.io/ncs-app-index/ .. ### Source: docs.nordicsemi.com Requires manual update to align it with the NCS version. @@ -36,11 +37,14 @@ .. _`nRF5340 DK`: https://docs.nordicsemi.com/bundle/ncs-3.2.0/page/zephyr/boards/nordic/nrf5340dk/doc/index.html .. _`nrf5340dk`: https://docs.nordicsemi.com/bundle/ncs-3.2.0/page/nrf/app_dev/device_guides/nrf53/index.html .. _`nRF5340 DK connector interface`: https://docs.nordicsemi.com/bundle/ug_nrf5340_dk/page/UG/dk/connector_if.html +.. _`nRF5340 DK external memory`: https://docs.nordicsemi.com/bundle/ug_nrf5340_dk/page/UG/dk/hw_external_memory.html .. _`nRF52840 DK`: https://docs.nordicsemi.com/bundle/ncs-3.2.0/page/zephyr/boards/nordic/nrf52840dk/doc/index.html .. _`nrf52840dk`: https://docs.nordicsemi.com/bundle/ncs-3.2.0/page/nrf/app_dev/device_guides/nrf52/index.html .. _`nRF52840 DK connector interface`: https://docs.nordicsemi.com/bundle/ug_nrf52840_dk/page/UG/dk/connector_if.html +.. _`Power Profiler Kit II`: https://docs.nordicsemi.com/bundle/ug_ppk2/page/UG/ppk/PPK_user_guide_Intro.html + .. _`Testing and optimization`: https://docs.nordicsemi.com/bundle/ncs-3.2.0/page/nrf/test_and_optimize.html .. _`Software maturity`: https://docs.nordicsemi.com/bundle/ncs-3.2.0/page/nrf/releases_and_maturity/software_maturity.html @@ -72,6 +76,7 @@ .. _`Board Configurator app`: https://docs.nordicsemi.com/bundle/nrf-connect-board-configurator/page/index.html .. _`Installing Board Configurator app`: https://docs.nordicsemi.com/bundle/nrf-connect-board-configurator/page/installing.html .. _`Updating the board configuration`: https://docs.nordicsemi.com/bundle/nrf-connect-board-configurator/page/updating.html +.. _`nRF Connect for Desktop`: https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-Desktop .. _`Serial Terminal app`: https://docs.nordicsemi.com/bundle/nrf-connect-serial-terminal/page/index.html .. _`nRF device manager`: https://www.nordicsemi.com/Products/Development-tools/nrf-connect-device-manager .. _`Newt manager`: https://github.com/apache/mynewt-newtmgr @@ -80,10 +85,10 @@ .. _`X-NUCLEO-NFC09A1`: https://www.st.com/en/ecosystems/x-nucleo-nfc09a1.html .. _`ST25R100`: https://www.st.com/en/nfc/st25r100.html -.. _`X-NUCLEO-NFC08A1`: https://www.st.com/en/ecosystems/x-nucleo-nfc08a1.html -.. _`ST25R3916B`: https://www.st.com/en/nfc/st25r3916b.html -.. _`X-NUCLEO-NFC05A1`: https://www.st.com/en/ecosystems/x-nucleo-nfc05a1.html -.. _`ST25R3911B`: https://www.st.com/en/nfc/st25r3911b.html +.. _`X-NUCLEO-NFC12A1`: https://www.st.com/en/ecosystems/x-nucleo-nfc12a1.html +.. _`ST25R300`: https://www.st.com/en/nfc/st25r300.html +.. _`ST25R300 datasheet`: https://www.st.com/resource/en/datasheet/st25r300.pdf +.. _`RFAL documentation`: https://www.st.com/resource/en/user_manual/um2890-rfnfc-abstraction-layer-rfal-stmicroelectronics.pdf .. _`OM27160B1EVK`: https://www.nxp.com/part/OM27160B1EVK .. _`OM27160A1EVK`: https://www.nxp.com/part/OM27160A1EVK diff --git a/docs/nfc_power_measurements.rst b/docs/nfc_power_measurements.rst new file mode 100644 index 00000000..69b4b263 --- /dev/null +++ b/docs/nfc_power_measurements.rst @@ -0,0 +1,258 @@ +.. _nfc_power_measurements: + +NFC current measurements +######################## + +.. contents:: + :local: + :depth: 2 + +This page describes a method for measuring NFC current consumption of an |APP_NAME| reference application using `Power Profiler Kit II`_ (PPK). + +Overview +******** + +The NFC current measurements are separated into two parts: + +* :ref:`nfc_power_measurements_nrf_soc` +* :ref:`nfc_power_measurements_nfc_reader` + +Choose the measurements based on what you want to analyze and optimize: + +* If you wish to check only the SoC current consumption, focus on the :ref:`development kit SoC current measurements `. +* If your focus is the NFC reader, use the :ref:`NFC reader current measurements instead `. +* To evaluate the combined impact of both components, measure the overall power consumption of the system, including both the SoC and the NFC reader. + +The analysis covers two functional modes of the NFC reader: + +* IDLE - Average current over the entire duration of the measurement time window with no NFC tag present (e.g., a 60-second measurement window). +* ACTIVE - Average current when an NFC tag is present in a given time window. + +Prerequisites +************* + +Ensure you have the following hardware: + +* `Power Profiler Kit II`_ +* Development kit (for example, `nRF5340 DK`_ which was used for the measurements in this document) +* NFC reader expansion board (`X-NUCLEO-NFC12A1`_) + +Ensure you have installed the following software: + +* `nRF Connect for Desktop`_ with the Power Profiler app + +Build configuration +******************* + +All measurements in this document were taken with the following build configuration: + +.. code-block:: bash + + west build -b nrf5340dk/nrf5340/cpuapp -p -- -DFILE_SUFFIX=release + +In the Aliro standalone application, the *release* configuration keeps the UART shell enabled (used for provisioning the Aliro Reader). +This adds background UART activity and can impact the measured current consumption. + +To remove this additional current consumption, disable the UART node used by the shell/console in your DTS overlay, for example: + +.. code-block:: dts + + &uart0 { + status = "disabled"; + }; + +Hardware setup +**************** + +The following subsections describe how the development kit and PPK are connected and configured in the given measurement scenario. + +.. _nfc_power_measurements_nrf_soc: + +nRF SoC current measurement +=========================== + +Connect the development kit and the PPK as shown in the following schematic: + +.. figure:: /images/nfc_current_schematic_soc_nrf5340_ppk2.png + :scale: 85% + :alt: Schematic for measuring nRF SoC current. + +In this setup, the DK is powered directly by the PPK, while the NFC module remains powered from the USB port. + +Development kit configuration +============================= + +Configure the DK as follows: + +- **POWER** switch: **ON** +- **nRF power source**: **VDD** +- **VEXT->nRF**: **ON** +- **SW6 (nRF ONLY|DEFAULT)**: **nRF ONLY** (SEGGER, LEDs and external flash powered off) + +In **nRF ONLY** mode, the external flash memory is powered down. +Since the |APP_NAME| uses external flash, it will not start correctly unless you power up the flash. + +Whether the external flash current is included in the measurements depends on how it is powered: + +* Powered from **VDD**: excluded from the measurements +* Powered from **VDD nRF**: included in the measurements + +Choose one of the following approaches: + +* Full development kit current measurement - Set **SW6** to **nRF DEFAULT**. + This measures the full DK current, including SEGGER, external flash, and other peripherals. + +* nRF SoC-only current measurement - Keep **SW6** set to **nRF ONLY**, but power the external flash from **VDD**. + This allows the application to run while minimizing additional current paths. + This option requires a hardware modification of your development kit: + + * Reconfigure the external flash power source solder bridges so that the flash is powered from **VDD**: + + * **SB16 (VDD_PER)**: **OPEN** (remove the solder bridge) + * **SB17 (VDD)**: **SHORT** (add a solder bridge) + * **SB18 (VDD_nRF)**: **OPEN** + + * For details, see `nRF5340 DK external memory`_. + +All measurements in this document were taken using the nRF SoC-only current measurement approach. + +Power Profiler Kit II configuration +=================================== + +Configure the Power Profiler Kit II as follows: + +* Mode: Source Meter +* Supply voltage: ``3000`` mV +* Sampling rate: ``100 000`` samples per second +* Capture duration: ``60`` s + +Measuring current +***************** + +This section describes the step-by-step procedure to capture the current measurements. + +#. Connect PPK to your development kit as the power source. +#. Program the DUT: + + Use the option that matches your hardware setup: + + .. tabs:: + + .. tab:: nRF SoC-only current measurement + + #. Power up external flash using VDD. + #. Set switches to VEXT OFF and nRF DEFAULT. + #. Flash the firmware. + #. Set switches to VEXT ON and nRF ONLY. + + .. tab:: Development kit's entire current measurement + + #. Set switches to VEXT OFF and nRF DEFAULT. + #. Enable PPK power output. + #. Flash the firmware. + +#. Enable power output. +#. Start the capture in Power Profiler. +#. After 60 seconds, the measurement ends automatically. + You can also stop it manually. + +.. _nfc_power_measurements_nfc_reader: + +NFC module current +****************** + +Connect the development kit and the PPK as shown in the following schematic: + +.. figure:: /images/nfc_current_schematic_module_st25r300_ppk2.png + :scale: 85% + :alt: Schematic for measuring NFC module current. + +In this configuration, the NFC module is powered from the development kit, while the development kit itself is powered through USB. + +Development Kit configuration +============================= + +- **POWER** switch: **ON** +- **nRF power source**: **VDD** +- **VEXT->nRF**: **OFF** +- **SW6 (nRF ONLY|DEFAULT)**: **nRF DEFAULT** + +Power Profiler Kit II configuration +=================================== + +- Mode: Ampere Meter +- Capture duration: ``60`` s + +Measuring current +================= + +This section describes the step-by-step procedure used to capture the current measurements. + +#. Power the Development Kit setup using USB. +#. Connect PPK in series between the development kit's VDD and the NFC module's power input as shown in the :ref:`schematic `. +#. Start the capture in Power Profiler app. +#. After 60 seconds, the measurement ends automatically. + You can also stop it manually. + +Analyzing results +***************** + +The following figures show current consumption for an nRF5340 DK and an `X-NUCLEO-NFC12A1`_ expansion board. + +.. figure:: /images/nfc_current_plot_soc_idle_nrf5340.png + :scale: 70% + :alt: nRF5340 SoC current consumption in IDLE mode. + + nRF5340 SoC current consumption in IDLE mode. + +.. figure:: /images/nfc_current_plot_module_idle_x_nucleo_nfc12a1.png + :scale: 70% + :alt: X-NUCLEO-NFC12A1 current consumption in IDLE mode. + + X-NUCLEO-NFC12A1 NFC module current consumption in IDLE mode. + +.. figure:: /images/nfc_current_plot_idle_wum_comparison.png + :scale: 70% + :alt: Average current in IDLE mode as a function of wake-up mode configuration. + + Average current consumption depending on the NFC sensitivity settings in IDLE mode. + +In IDLE mode, the X-NUCLEO-NFC12A1 module current consumption depends on the ``CONFIG_RFAL_WAKE_UP_MODE`` Kconfig choice. +This option adjusts RFAL driver settings that control the wake-up procedure (among other things, it changes ``RFAL_WUM_PERIOD`` (wake-up timer period; how often wake-up measurements are performed) in the ``wakeupConfig`` structure), +which affects the average current consumption of the NFC module in IDLE mode. +You can find available configurations in the :file:`drivers/nfc/stm/nfc_configs/Kconfig` file. + +.. figure:: /images/nfc_current_plot_active_worker_interval_comparison.png + :scale: 70% + :alt: Average current in ACTIVE mode as a function of wake-up mode configuration. + +In ACTIVE mode, the Development Kit current consumption depends on the ``RFAL_NFC_WORKER_INTERVAL_MS`` parameter (the time between consecutive NFC worker executions). +However, the largest source of the total current in this mode is the NFC module itself. + +RFO tuning +*********** + +This section explains how NFC RF output resistance configuration affects ACTIVE‑mode current consumption and how to adjust it while balancing power, sensitivity, and read range. + +The RF output (RFO) driver resistance has a significant impact on the NFC module current consumption in ACTIVE mode. +Increasing the RFO output resistance multiplier reduces the NFC module current consumption in ACTIVE mode, but it also reduces sensitivity and read range. + +.. figure:: /images/nfc_current_plot_active_rfo_comparison_st25r300.png + :scale: 70% + :alt: X-NUCLEO-NFC12A1 average current in ACTIVE mode as a function of RFO driver resistance multiplier. + +You can configure the RFO driver resistance using the RFAL API: + +.. code-block:: c + + rfalChipSetRFO(uint8_t rfo); + +The ``rfo`` parameter is a value from 0 (lowest output resistance) to 15 (highest output resistance). +For details, see the `ST25R300 datasheet`_. + +Other parameters also affect the NFC current consumption. + +You can find them in the :file:`app/src/aliro/platform/nfc/Kconfig` and :file:`drivers/nfc/stm/nfc_configs/Kconfig` files. +For example, ``RFAL_WAKEUP_NPOLLS`` controls how many polling cycles are performed before and after Wake-Up mode, which can impact average current consumption of the NFC module in ACTIVE mode. + +You can find more details about the RFAL driver configuration in the `RFAL documentation`_. diff --git a/docs/other_addons.rst b/docs/other_addons.rst index 4a9faa8b..471e06dd 100644 --- a/docs/other_addons.rst +++ b/docs/other_addons.rst @@ -12,8 +12,8 @@ This page details the deployment of the |APP_NAME| and its dependencies. Overview ******** -The |APP_NAME| functions as the nRF Connect top-level add-on. -The top-level add-on leverages other add-ons, which are typically configured as Zephyr modules, to enhance functionality. +The |APP_NAME| functions as the |NCS| top-level add-on. +The top-level add-on leverages `other add-ons `_, which are typically configured as Zephyr modules, to enhance functionality. All add-ons provide additional software deployed outside of the |NCS|. Each of them operates with its own release cycle but is designed to work with specific versions of the |NCS|. Dependencies between these modules and their specific revisions are managed through the :file:`west.yaml` file using `west, a Zephyr OS meta tool `_. @@ -23,7 +23,6 @@ Aliro add-ons The |APP_NAME| includes a binary library of the Aliro stack. The binary is built from the Aliro add-on, which is a private module with restricted access. - See the following diagram for deployment of software components used by the |APP_NAME|: .. figure:: /images/aliro-add-ons.svg @@ -32,5 +31,6 @@ See the following diagram for deployment of software components used by the |APP Aliro add-ons deployment -The Aliro UWB adapter is not yet available as an add-on repository. +The Aliro UWB adapter is not yet available as a public add-on repository. However, the UWB adapter library is included as part of |APP_NAME| and can be found in the :file:`lib/qm35_uwb` directory. +For information on how to build it, see the :ref:`building_and_running` page. diff --git a/docs/reference_application_interactions.rst b/docs/reference_application_interactions.rst new file mode 100644 index 00000000..c6fec8a5 --- /dev/null +++ b/docs/reference_application_interactions.rst @@ -0,0 +1,135 @@ +.. _reference_application_interactions: + +|APP_NAME| interactions +####################### + +.. contents:: + :local: + :depth: 2 + +The following page describes how the |APP_NAME| interacts with the Aliro stack, and shows examples of specific interaction flows. + +Diagram conventions +******************* + +This section explains the components and layers shown in the interaction diagrams and how responsibilities are divided between the application, the Aliro stack, and the underlying hardware: + +* Application – Represents the application-layer code that owns policy decisions and platform-specific behavior. + It implements the interfaces defined by the Aliro stack and uses the Aliro Stack Public API to interact with the stack. +* Aliro Stack Interface – Represents the set of interfaces defined by the Aliro stack and implemented by the application, such as ``Interface::Os``, ``Interface::Access``, ``Interface::CredentialIssuerCertificate``. + The Aliro stack calls these interfaces, and the application provides their concrete implementations. +* Aliro Stack Public API – Represents public API defined by the Aliro stack. + It serves as the entry point through which the application interacts with the Aliro stack. +* Aliro Stack – Represents the protocol engine that implements session management, internal state machines, data security, and Aliro protocol logic. + It defines both the public API (entry points for the application) and the interface contracts (callbacks and services the stack requires). +* NFC and Bluetooth LE Radio (NFC/BLE) – Represents the hardware component that performs the physical transmission and reception of Aliro session data. + +High-level design: Layers and interface contract +************************************************ + +This section describes the high‑level layering of the |APP_NAME| and the direction of calls between the Application, the Aliro Stack, and the underlying radio. +It shows how responsibilities are separated and how the public API and stack‑defined interfaces are used. + +.. figure:: sequence_diagrams/d2/app_stack_general_interaction.png + :scale: 40% + :alt: High-level API and interfaces flow between |APP_NAME| components. + + Public API and interfaces flow. + +The workflow is as follows: + +* The NFC/Bluetooth LE Radio performs TX/RX. + The Application (which includes the transport abstraction and other hardware-specific code) receives data from the radio and delivers it to the Aliro Stack through the Aliro Stack Public API. + The stack is the only component that interprets protocol and session state. + For session data, the Application acts as the data pipe between NFC/Bluetooth LE Radio and the stack. +* The Application is the caller of the Aliro Stack Public API. + It invokes the stack to start sessions, process events, and drive protocol. + The Public API is defined and implemented entirely within the Aliro Stack. +* When the Aliro Stack needs OS services, access decisions, or cryptographic operations (for example, queueing work, requesting Access Document parameters, verifying certificates), it calls the Aliro Stack Interface. + The interfaces are defined by the Aliro Stack and implemented by the Application. + The stack remains application-agnostic, as all platform-specific behavior lives in the Application implementation of these interfaces. + +Layers are clearly separated. +The Application uses the Aliro Stack Public API to drive the Aliro Stack and implements the stack-defined interfaces. +The Application (transport, crypto, and other hardware and platform code) carries session data between NFC/Bluetooth LE Radio and stack, and provides crypto and platform services. +The Aliro Stack defines both the public API and the interface contracts. +It never contains application logic, only calls into the Application through the Aliro Stack Interface. + +Stack events processing +*********************** + +The |APP_NAME| processes stack events on a dedicated Zephyr work queue. +When the Aliro Stack notifies the Application through the ``Interface::Os::QueueEvent`` callback, the Application implementation queues the event into Zephyr FIFO and submits a work item to the Aliro work queue. + +Some operations, such as preprocessing transport data and managing the Aliro session, are performed directly in the caller’s (Application) context rather than on the dedicated work queue. +The dedicated work queue runs a single thread named ``aliroworkq``. +Its work handler drains the FIFO and calls the Aliro Stack Public API ``ProcessEvent()`` for each queued event. +As a result, all stack event processing within the application runs on this single thread. +Other Aliro‑related work, such as ``Interface::Os`` timers, is also submitted to the same queue using ``AliroWorkSubmit()`` and ``AliroWorkReschedule()``. +This design ensures that the stack never blocks in the caller’s context and that all work items are dispatched on the ``aliroworkq`` thread. + +The Aliro Stack may invoke application callbacks from different contexts. +The FIFO and work‑queue submission mechanism keep the stack non‑blocking while ensuring that events are processed in order on the dedicated thread. + +The following diagram shows how session data flows from reception, through stack‑driven event processing, and back to the application. + +.. figure:: sequence_diagrams/d2/stack_events_handling.png + :scale: 40% + :alt: Stack event processing from radio RX to application work and back. + + Stack event processing. + +The workflow is as follows: + +* The NFC/Bluetooth LE Radio receives data and passes it to the Application. + The Application's Transport implementation delivers the payload to the Aliro Stack through ``HandleSessionData()`` public API function. + The stack then executes the internal session state machine logic to interpret the data and update session state. + For session data, the Application does not interpret protocol, but it forwards data to the stack. +* When the Aliro Stack must notify the Application about an event (for example, session data received, session ended), it calls ``Interface::Os``, which is defined by the stack and implemented by the Application. + The implementation queues the event into a FIFO and submits work, so the stack can return without blocking. + The stack does not know how the Application schedules or prioritizes work. + It only calls the corresponding interface. +* When the Application's work runs, it calls the Aliro Stack Public API ``ProcessEvent()`` function to hand the event back to the Aliro Stack. + The stack then performs event processing (state updates, further interface calls, or session actions). + +Session data flows as follows: NFC/Bluetooth LE Radio → Application (Transport) → Aliro Stack through public API. + +The Application provides Transport and other platform abstractions. +The Aliro Stack turns received data into events and delivers them through ``Interface::Os``. +The Application queues and schedules work, then drives the stack through ``ProcessEvent()`` from the Aliro Stack Public API. +This keeps the stack non-blocking and leaves scheduling and queuing policy in the Application. + +UWB session establishment and handling +************************************** + +This diagram shows how an Ultra Wideband (UWB) ranging session is established and handled when Bluetooth LE or UWB transport is enabled: + +.. figure:: sequence_diagrams/d2/uwb_session_establishment_and_handling.png + :scale: 40% + :alt: UWB session establishment and handling over Bluetooth LE. + + UWB session establishment and handling. + +The workflow is as follows: + +* When the Aliro Stack enters the ``UwbRanging`` state (after Bluetooth LE session setup), it calls the ``StartRangingSession()`` function on the ``Interface::Session`` interface from the Aliro Stack Interface. + The Application ``AccessManager`` creates a ranging session context and calls ``UltraWideBandImpl::ConfigureRangingSession()``, which creates the UWB session utilizing the Qorvo UWB library. + The stack does not manage UWB session objects. + The Application and its UWB implementation own them. +* UWB setup and control messages (for example, M1/M2 and other Aliro UWB protocol messages) are carried over Bluetooth LE Tansport. +* When the Bluetooth LE radio receives data from the User Device, the Application delivers it to the Aliro Stack through ``HandleSessionData()``. + The stack then parses the message and calls the ``HandleBleMessage()`` function on the ``Interface::Uwb`` interface from the Aliro Stack Interface. +* The Application ``UltraWideBandImpl`` passes the payload to the UWB library. + The library generates a response. + The ``TransmitBleMessage()`` callback invokes the ``SendBleMessage`` public API from the Aliro Stack Public API. + The stack then sends the response over Bluetooth through the ``Send()`` function on the ``Interface::Session`` interface from the Aliro Stack Interface. +* The Application Bluetooth LE transport transmits the payload to the User Device. + This request and response cycle continues until the UWB session is fully set up. +* Once the UWB session is active, the UWB library reports session state changes and ranging reports through the ``SessionHandlerCallback()``. + The Application ``UltraWideBandImpl`` maps these to ``AccessManager`` callbacks in the Application (for example, ``HandleRangingSessionStateChanged``, ``HandleRangingSessionData``). + The ``AccessManager`` uses this information for access policy (for example, in-range detection and access decisions). + +To establish UWB session, the Aliro Stack requests session creation through ``Interface::Session::StartRangingSession``. +The Application configures the UWB session through ``UltraWideBandImpl``. +UWB setup messages are exchanged over Bluetooth LE - the stack receives data through ``HandleSessionData`` and forwards the processed data to the Application as events. +The Application uses the UWB library and sends responses back through the Aliro Stack Interface. Ranging state and distance data are delivered from the UWB implementation to the ``AccessManager``, so the Application can enforce access policy based on UWB ranging. diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 179cad83..073f9b72 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -18,40 +18,42 @@ v0.6.0 Changelog ========= -The following updates were introduced in this release. - -* Added: - - * Compliance with the Aliro specification confirmed against Aliro Test Harness (`aliro-sve-v1.0`_ tag). - * Credential Issuer Certificate verification using the Credential Issuer CA Public Key. - * Reader Certificate provisioning. - * Presenting Reader Certificate using the ``LOAD_CERT`` command. - * Support for injecting proprietary NFC technologies. - * The UWB Suspend and Resume functionality. - * Support for testing Aliro over NFC and Aliro over Bluetooth LE + UWB with the Apple Home ecosystem. - * Access Document storage in non-volatile memory. - * Reference implementation of QM35825 firmware update using: - * DFU over Matter - * DFU over Bluetooth LE - -* Updated: - - * Integrated nRF Connect SDK v3.2.0. - * Integrated Matter v1.5.0. - * Integrated QM35825 firmware and software v0.6.0. - * Improved NFC discovery mechanism. - * Improved support for Aliro Expedited-Fast phase and Aliro Step-up phase. - * Updated default UWB session termination to allow User Device–controlled session closure. - * Improved system robustness by avoiding fatal errors when X-NUCLEO-NFC09A1 shield or QM35825 UWB module fail to initialize, preserving operability for potential firmware upgrades. - * Changed default configuration to communicate with X-NUCLEO-NFC09A1 shield and QM35825 UWB module using a single SPI bus. - * Extended documentation with: - * Instructions for flashing the QM35825 device using nRF5340 DK. - * Guidelines for testing with Aliro Test Harness. - * Instructions for testing with the Apple Home ecosystem using Bluetooth LE + UWB. - -* Fixed: - - * Build issues when compiling the project on Windows PCs. +.. toggle:: + + The following updates were introduced in this release. + + * Added: + + * Compliance with the Aliro specification confirmed against Aliro Test Harness (`aliro-sve-v1.0`_ tag). + * Credential Issuer Certificate verification using the Credential Issuer CA Public Key. + * Reader Certificate provisioning. + * Presenting Reader Certificate using the ``LOAD_CERT`` command. + * Support for injecting proprietary NFC technologies. + * The UWB Suspend and Resume functionality. + * Support for testing Aliro over NFC and Aliro over Bluetooth LE + UWB with the Apple Home ecosystem. + * Access Document storage in non-volatile memory. + * Reference implementation of QM35825 firmware update using: + * DFU over Matter + * DFU over Bluetooth LE + + * Updated: + + * Integrated nRF Connect SDK v3.2.0. + * Integrated Matter v1.5.0. + * Integrated QM35825 firmware and software v0.6.0. + * Improved NFC discovery mechanism. + * Improved support for Aliro Expedited-Fast phase and Aliro Step-up phase. + * Updated default UWB session termination to allow User Device–controlled session closure. + * Improved system robustness by avoiding fatal errors when X-NUCLEO-NFC09A1 shield or QM35825 UWB module fail to initialize, preserving operability for potential firmware upgrades. + * Changed default configuration to communicate with X-NUCLEO-NFC09A1 shield and QM35825 UWB module using a single SPI bus. + * Extended documentation with: + * Instructions for flashing the QM35825 device using nRF5340 DK. + * Guidelines for testing with Aliro Test Harness. + * Instructions for testing with the Apple Home ecosystem using Bluetooth LE + UWB. + + * Fixed: + + * Build issues when compiling the project on Windows PCs. v0.5.0 ****** @@ -62,27 +64,29 @@ v0.5.0 Changelog ========= -The following updates were introduced in this release. +.. toggle:: -* Added support for: + The following updates were introduced in this release. - * nRF54LM20A platform. - * Aliro Expedited-fast phase. - * Aliro Step-up phase. - * Aliro Access Document reception and parsing in the application. - * Bluetooth LE Nordic UART Service (NUS) for remote control of the door lock. - * Device Firmware Upgrade (DFU) over Bluetooth LE Simple Management Protocol (SMP). - * Building QM35825 UWB libraries from sources for registered users. + * Added support for: -* Extended the documentation with: + * nRF54LM20A platform. + * Aliro Expedited-fast phase. + * Aliro Step-up phase. + * Aliro Access Document reception and parsing in the application. + * Bluetooth LE Nordic UART Service (NUS) for remote control of the door lock. + * Device Firmware Upgrade (DFU) over Bluetooth LE Simple Management Protocol (SMP). + * Building QM35825 UWB libraries from sources for registered users. - * A guide on how to test the application with the Apple smart home ecosystem. - * Instructions on how to test Matter over-the-air (OTA) updates and DFU over Bluetooth LE SMP. - * Explanation of the new Aliro features and their usage. + * Extended the documentation with: -* Fixed: + * A guide on how to test the application with the Apple smart home ecosystem. + * Instructions on how to test Matter over-the-air (OTA) updates and DFU over Bluetooth LE SMP. + * Explanation of the new Aliro features and their usage. - * Compliance issues with the Apple smart home ecosystem. + * Fixed: + + * Compliance issues with the Apple smart home ecosystem. v0.4.0 ****** @@ -93,26 +97,28 @@ v0.4.0 Changelog ========= -The following updates were introduced in this release. +.. toggle:: + + The following updates were introduced in this release. -* Added: + * Added: - * Matter support. - * Integration of the Matter door lock cluster server in the |APP_NAME|. - * Support for provisioning of Aliro door lock reader and user credentials using Matter. + * Matter support. + * Integration of the Matter door lock cluster server in the |APP_NAME|. + * Support for provisioning of Aliro door lock reader and user credentials using Matter. -* Updated: + * Updated: - * nRF Connect SDK revision to v3.1.0. - * STM RFAL drivers to the STSW-ST25RFAL005 revision. - * QM35 libraries to latest internal Qorvo delivery. - * Moved CLI and persistent storage implementation from Aliro stack to the application layer. + * nRF Connect SDK revision to v3.1.0. + * STM RFAL drivers to the STSW-ST25RFAL005 revision. + * QM35 libraries to latest internal Qorvo delivery. + * Moved CLI and persistent storage implementation from Aliro stack to the application layer. -* Fixed: + * Fixed: - * An issue where the watchdog was expiring for an NFC session upon test execution (AL-335). - * An issue where occasional timeout was occuring in the Reader when executing the RD-NFC-STDTXN-2.0 test from the test harness (AL-239). - * An issue where an undefined access decision was occuring when executing the RD-NFC-STDTXN-2.0 test harness case in a loop (AL-282). + * An issue where the watchdog was expiring for an NFC session upon test execution (AL-335). + * An issue where occasional timeout was occuring in the Reader when executing the RD-NFC-STDTXN-2.0 test from the test harness (AL-239). + * An issue where an undefined access decision was occuring when executing the RD-NFC-STDTXN-2.0 test harness case in a loop (AL-282). v0.3.1 ****** @@ -123,22 +129,24 @@ v0.3.1 Changelog ========= -The following updates were introduced in this release. +.. toggle:: + + The following updates were introduced in this release. -* Fixed: + * Fixed: - * An issue where ranging measurements with the QM35825 module were not delivered to the application. - The QM35825 module's ranging measurements are now correctly delivered to the application. - The distance between the Reader and User Device can now be displayed in the logs. - * An issue with Access Manager not supporting multiple Aliro sessions. - Access Manager now supports multiple Aliro and UWB sessions. - * An issue where only one User Device could be provisioned to the Reader. - The application now allows provisioning multiple User Devices to the Reader. + * An issue where ranging measurements with the QM35825 module were not delivered to the application. + The QM35825 module's ranging measurements are now correctly delivered to the application. + The distance between the Reader and User Device can now be displayed in the logs. + * An issue with Access Manager not supporting multiple Aliro sessions. + Access Manager now supports multiple Aliro and UWB sessions. + * An issue where only one User Device could be provisioned to the Reader. + The application now allows provisioning multiple User Devices to the Reader. -* Changed: + * Changed: - * The Access Manager can now autonomously terminate the Aliro and UWB ranging sessions. - * The ``dl provisioning AC_key`` shell command was extended to support multiple Access Credential public keys. + * The Access Manager can now autonomously terminate the Aliro and UWB ranging sessions. + * The ``dl provisioning AC_key`` shell command was extended to support multiple Access Credential public keys. v0.3.0 ****** @@ -149,17 +157,19 @@ v0.3.0 Changelog ========= -The following updates were introduced in this release. +.. toggle:: -* Added: + The following updates were introduced in this release. - * The EXCHANGE command implementation. - * Support for Bluetooth LE transport protocol. - * Support for ultra wideband (UWB) interface. - * Reference implementation of the UWB module QM35825 example. - The Aliro UWB adapter is provided as precompiled libraries. - * Access Manager interface and its reference implementation. - * Support for LED indicator of access decision. + * Added: + + * The EXCHANGE command implementation. + * Support for Bluetooth LE transport protocol. + * Support for ultra wideband (UWB) interface. + * Reference implementation of the UWB module QM35825 example. + The Aliro UWB adapter is provided as precompiled libraries. + * Access Manager interface and its reference implementation. + * Support for LED indicator of access decision. v0.2.0 ****** @@ -170,24 +180,26 @@ v0.2.0 Changelog ========= -The following updates were introduced in this release. +.. toggle:: + + The following updates were introduced in this release. -* Added: + * Added: - * Experimental support for the following development platforms: + * Experimental support for the following development platforms: - * `nRF52840 DK`_ - * `nRF5340 DK`_ + * `nRF52840 DK`_ + * `nRF5340 DK`_ - * A platform logger implementation. - * A new NFC transport interface integration. - * Simplified Aliro stack API integration. + * A platform logger implementation. + * A new NFC transport interface integration. + * Simplified Aliro stack API integration. -* Improved the implementation of the following components in the RFAL platform abstraction layer: + * Improved the implementation of the following components in the RFAL platform abstraction layer: - * Timers - * Semaphores - * Threading + * Timers + * Semaphores + * Threading v0.1.0 ****** @@ -195,15 +207,20 @@ v0.1.0 .. note:: |EXPERIMENTAL_NOTE| -See the following section for the list of implemented features. +Changelog +========= + +.. toggle:: + + See the following section for the list of implemented features. -* Added experimental support for the following: + * Added experimental support for the following: - * Access Protocol (the expedited standard transaction only). - * Transport Protocol over Near Field Communication (NFC) transport. - * Trusted Framework implementation with the |NCS| PSA API as a cryptography backend. - * The nRF54L15 hardware platform. - * STM NFC reader transceivers: ST25R200, ST25R3911, ST25R3916, ST25R3916B, ST25R200. - * ST Microelectronics R/F Abstraction Layer driver with Zephyr Platform Abstraction Layer integration. - * |APP_NAME| that leverages Aliro stack and supports CLI-based provisioning of the Access Credential public key, the reader's group identifier, and the group sub-identifier. - * Sample applications that uses RFAL driver. + * Access Protocol (the expedited standard transaction only). + * Transport Protocol over Near Field Communication (NFC) transport. + * Trusted Framework implementation with the |NCS| PSA API as a cryptography backend. + * The nRF54L15 hardware platform. + * STM NFC reader transceivers: ST25R200, ST25R3911, ST25R3916, ST25R3916B, ST25R200. + * ST Microelectronics R/F Abstraction Layer driver with Zephyr Platform Abstraction Layer integration. + * |APP_NAME| that leverages Aliro stack and supports CLI-based provisioning of the Access Credential public key, the reader's group identifier, and the group sub-identifier. + * Sample applications that uses RFAL driver. diff --git a/docs/requirements.txt b/docs/requirements.txt index 0045095d..4e216c3f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,3 +3,4 @@ Sphinx==5.3.0 sphinx-copybutton==0.5.2 sphinx-ncs-theme==0.7.4 sphinx-tabs==3.4.0 +sphinx-togglebutton==0.4.4 diff --git a/docs/sequence_diagrams/d2/app_stack_general_interaction.d2 b/docs/sequence_diagrams/d2/app_stack_general_interaction.d2 new file mode 100644 index 00000000..5dd73239 --- /dev/null +++ b/docs/sequence_diagrams/d2/app_stack_general_interaction.d2 @@ -0,0 +1,24 @@ +flow: "API and interface flow" { + shape: sequence_diagram + + app: "Application" { + transport: "Transport (NFC/BLE)" + } + iface: "Aliro Stack Interface" + api: "Aliro Stack Public API" + stack: "Aliro Stack" + radio: "NFC/BLE radio" { + style: { + stroke-dash: 2 + stroke: "#666" + } + } + + name: "Public API and interface" { + radio -> app: "TX/RX data" + app -> api: "calls" + api -> stack: "invokes" + stack -> iface: "calls" + iface -> app: "implemented by" + } +} diff --git a/docs/sequence_diagrams/d2/app_stack_general_interaction.png b/docs/sequence_diagrams/d2/app_stack_general_interaction.png new file mode 100644 index 00000000..207d981f Binary files /dev/null and b/docs/sequence_diagrams/d2/app_stack_general_interaction.png differ diff --git a/docs/sequence_diagrams/d2/stack_events_handling.d2 b/docs/sequence_diagrams/d2/stack_events_handling.d2 new file mode 100644 index 00000000..002e126b --- /dev/null +++ b/docs/sequence_diagrams/d2/stack_events_handling.d2 @@ -0,0 +1,32 @@ +flow: "Aliro Stack events handling" { + shape: sequence_diagram + + app: "Application" { + transport: "Transport (NFC/BLE)" + } + interface: "Aliro Stack Interface" { + os_iface: "Aliro::Interface::Os" + } + api: "Aliro Stack Public API" + stack: "Aliro Stack" + radio: "NFC/BLE radio" { + style: { + stroke-dash: 2 + stroke: "#666" + } + } + + name: "Stack Event Processing" { + + radio -> app: "RX data" + app -> api: "HandleSessionData(handle, data)" + api -> stack: "invokes" + stack -> stack: "Session Execution/Data Handling" + stack -> interface: "QueueEvent(event)" + interface -> app: "FIFO put, submit work" + app -> app: "work runs" + app -> api: "ProcessEvent(event)" + api -> stack: "invokes" + stack -> stack: "Event Processing" + } +} diff --git a/docs/sequence_diagrams/d2/stack_events_handling.png b/docs/sequence_diagrams/d2/stack_events_handling.png new file mode 100644 index 00000000..fbe275a8 Binary files /dev/null and b/docs/sequence_diagrams/d2/stack_events_handling.png differ diff --git a/docs/sequence_diagrams/d2/uwb_session_establishment_and_handling.d2 b/docs/sequence_diagrams/d2/uwb_session_establishment_and_handling.d2 new file mode 100644 index 00000000..e3050cbf --- /dev/null +++ b/docs/sequence_diagrams/d2/uwb_session_establishment_and_handling.d2 @@ -0,0 +1,48 @@ +flow: "UWB session establishment and handling" { + shape: sequence_diagram + + app: "Application" { + access_mgr: "AccessManager" + uwb_impl: "UltraWideBandImpl" + transport: "Transport (BLE)" + } + interface: "Aliro Stack Interface" { + session_iface: "Interface::Session" + uwb_iface: "Interface::Uwb" + } + api: "Aliro Stack Public API" + stack: "Aliro Stack" + radio: "BLE radio" { + style: { + stroke-dash: 2 + stroke: "#666" + } + } + + name: "UWB session establishment and handling" { + + stack.start -> interface: "StartRangingSession(handle, sessionId, ursk, protocolVersion)" + interface -> app.start: "AccessManager.StartRangingSession" + app -> app: "ConfigureRangingSession" + app -> app: "Create UWB session (URSK, protocol); add to list" + app.start -> interface: "return" + interface -> stack.start: "return" + radio -> app: "RX BLE data (UWB message)" + app -> api: "HandleSessionData(handle, data)" + api -> stack: "invokes" + stack -> stack: "UwbRanging state: parse message" + stack -> interface: "HandleBleMessage(handle, data, length)" + interface -> app: "UltraWideBandImpl.HandleBleMessage" + app -> app: "aliro_uwb_session_message_handle" + app -> app: "UWB lib processes; may produce response (e.g. M1)" + app -> app: "TransmitBleMessage callback" + app -> api: "SendBleMessage(handle, response)" + api -> stack: "ProcessUwbData" + stack -> interface: "Session::Send(handle, data)" + interface -> app: "BLE send" + app -> radio: "TX BLE data" + app -> app: "SessionHandlerCallback (state / ranging report)" + app -> app: "HandleRangingSessionStateChanged\nHandleRangingSessionData" + app -> app: "Update in-range, access policy" + } +} diff --git a/docs/sequence_diagrams/d2/uwb_session_establishment_and_handling.png b/docs/sequence_diagrams/d2/uwb_session_establishment_and_handling.png new file mode 100644 index 00000000..524e874d Binary files /dev/null and b/docs/sequence_diagrams/d2/uwb_session_establishment_and_handling.png differ diff --git a/docs/software_requirements.rst b/docs/software_requirements.rst index 88a72740..143abd38 100644 --- a/docs/software_requirements.rst +++ b/docs/software_requirements.rst @@ -14,16 +14,20 @@ This page summarizes the requirements for setting up and working with the |app_n |NCS| ***** -Before you start working with the application, you must have installed the |NCS| development environment and |NCS| toolchain. +Before you start working with the application, you must install the |NCS| development environment and |NCS| toolchain. Prepare the environment: -1. From the `Installing the nRF Connect SDK`_ page, complete the following steps: updating operating system, installing prerequisities, and installing the |NCS| toolchain. +1. From the `Installing the nRF Connect SDK`_ page, complete the following steps: + + a. Updating operating system + #. Installing prerequisities + #. Installing the |NCS| toolchain .. note:: |VSC| does not support private add-ons. - You must use command-line to clone the repository. + You must use command-line to clone and manage the repository. #. Once completed, open the repository and locate the :file:`ncs` directory. By default, this is one level up from the location where you installed the toolchain. @@ -44,7 +48,7 @@ Prepare the environment: This command will clone the `ncs-door-lock-app`_ add-on manifest repository into :file:`door-lock-workspace`. -#. Enter the following command to clone the project repository: +#. Enter the following command to clone the project repository and all of its submodules: .. code-block:: console @@ -80,7 +84,7 @@ Prepare the environment: #. Complete `setting up the command-line build environment`_. -Once you have completed all the steps, the development environment should be correctly configured. + Once you have completed all the steps, the development environment should be correctly configured. Aliro Certification Tool ************************ diff --git a/docs/testing.rst b/docs/testing.rst index cc427e20..9a74e68c 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -1,4 +1,5 @@ .. _testing: +.. _testing_environment: Testing ####### @@ -7,1451 +8,30 @@ Testing :local: :depth: 2 -This page will guide you through the testing instructions for the |app_name|. - -.. _testing_environment: +The following pages will guide you through setting up the test environment, provisioning credentials, and verifying the |app_name| using the Aliro Test Harness and Matter-based tools. -Test environment -**************** +Prerequisites +************* -The test environment consists of two major components: +The test environment consists of the following major components: * The Nordic Semiconductor’s :ref:`development kit (DK) `, which serves as the Reader in the door lock component. It must be attached to an NFC card reader expansion board and optionally to a UWB module. * The Aliro Test Harness, which acts as the User Device and simulates unlocking of the door lock. -* If you are using Matter, you must have the `Matter controller tools`_ and `Matter over Thread tools`_ for testing the provisioning of Aliro credentials from the Matter controller. +* Additionally, if you are using Matter, you must have the `Matter controller tools`_ and `Matter over Thread tools`_ for testing the provisioning of Aliro credentials from the Matter controller. + When testing with the Apple smart home ecosystem, you must have an iPhone running iOS version 26 or later and an Apple Matter controller, such as a HomePod mini. See the :ref:`hw_requirements` page for more details. -.. _testing_environment_configuration: - -.. _setting_up_the_aliro_test_harness: - -Setting up the Aliro Test Harness -********************************* - -This section provides instructions on setting up the Test Harness and getting Access Credentials from it. -In case you do not have access to `Aliro Certification Tool`_ repository, see the :ref:`hw_requirements_test_harness` section for further guidance. - -.. note:: - All examples related to the `Aliro Certification Tool`_ are based on the `aliro-sve-v1.0`_ tag. - -#. Follow the `Test harness usage instructions`_ in the `Aliro Certification Tool`_ repository. - -#. Open your Test Harness project's JSON configuration and locate the ``dut_reader_public_key``, ``th_access_credential_public_key``, ``dut_reader_group_identifier``, and ``dut_reader_group_sub_identifier`` fields. - - .. figure:: /images/th_config.png - :scale: 70% - :alt: Test harness project configuration. - - Test harness project configuration. - - -Aliro door lock provisioning with CLI -************************************* - -This section provides instructions on setting up the Test Harness, selecting tests, and executing them. - -#. Follow the :ref:`setting_up_the_aliro_test_harness` to locate necessary credentials in the Test Harness project configuration. - -#. Obtain the long-term public key and reader group identifier of the Reader device under test (DUT). - You can retrieve this information from the serial console of a DUT. - See more information on :ref:`building, flashing, and accessing the serial console`. - -#. After flashing the door lock firmware with Aliro support onto the device, observe the provisioned key and reader group identifier of the Reader printed on the DUT's serial console: - - .. code-block:: console - - Provision the Test Harness with the following Reader Public Key: - XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - ... - - .. code-block:: console - - Provision the Test Harness with the following Reader Group Identifier: - XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - ... - -#. Set up the test harness by inputting the 65-byte long Reader public key into the ``dut_reader_public_key`` field. - -#. Install the Reader identifier in the Reader device. - The full 32-byte Reader identifier can be set using the following ``dl install`` shell command: - - .. code-block:: console - - uart:~$ dl install identifier <32-byte reader_identifier in hex without 0x> - - For example: - - .. code-block:: console - - uart:~$ dl install identifier 00113344667799AA00113344667799AA113344667799AA00113344667799AA00 - - Alternatively, you can set the Reader group identifier and Reader group sub-identifier individually using the following ``dl install`` shell commands: - - .. code-block:: console - - uart:~$ dl install group_id <16-byte reader_group_identifier in hex without 0x> - uart:~$ dl install group_sub_id <16-byte reader_group_sub_identifier in hex without 0x> - - For example: - - .. code-block:: console - - uart:~$ dl install group_id 00113344667799AA00113344667799AA - uart:~$ dl install group_sub_id 113344667799AA00113344667799AA00 - - Executing the same commands without specifying values will return the stored value. - For example: - - .. code-block:: console - - uart:~$ dl install group_id - 00000000: 00 11 33 44 66 77 99 aa 00 11 33 44 66 77 99 aa |..3Dfw.. ..3Dfw..| - - Alternatively, you can set the Reader group identifier retrieved from DUT in the test harness project configuration, next to the ``dut_reader_group_identifier`` field. - -#. Set the ``th_access_credential_public_key`` in the DUT using the following ``dl provisioning`` shell command: - - .. code-block:: console - - uart:~$ dl provisioning AC_key set <65-byte public key in hex without 0x> - - For example: - - .. code-block:: console - - uart:~$ dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa - - .. note:: - You can also use the following command to clear the public key stored in the DUT by its ID: - - .. code-block:: console - - uart:~$ dl provisioning AC_key clear - - For example: - - .. code-block:: console - - uart:~$ dl provisioning AC_key clear 0 - - To clear all public keys stored in the DUT, use the following command: - - .. code-block:: console - - uart:~$ dl provisioning AC_key clear all - - To list all public keys stored in the DUT, use the following command: - - .. code-block:: console - - uart:~$ dl provisioning AC_key list - - For example: - - .. code-block:: console - - uart:~$ dl provisioning AC_key list - [0]: 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efb - [1]: (null) - [2]: (null) - [3]: (null) - [4]: (null) - [5]: (null) - [6]: (null) - [7]: (null) - [8]: (null) - [9]: (null) - -#. Optionally, set the ``dut_credential_issuer_public_key`` in the DUT using the following ``dl provisioning`` shell command: - - .. code-block:: console - - uart:~$ dl provisioning CI_key set <65-byte public key in hex without 0x> - - - For example: - - .. code-block:: console - - uart:~$ dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 - - .. note:: - - You can also use the following commands: - - * ``uart:~$ dl provisioning CI_key clear `` - This command clears the public key stored in the DUT by its ID (for example, ``uart:~$ dl provisioning CI_key clear 0``). - * ``uart:~$ dl provisioning CI_key clear all`` - This command clears all public keys stored in the DUT. - * ``uart:~$ dl provisioning CI_key list`` - This command lists all public keys stored in the DUT. - - For example: - - .. code-block:: console - - uart:~$ dl provisioning CI_key list - [0]: 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 - -#. Optionally, set the Credential Issuer Certificate Authority (CA) public key in the DUT using the following ``dl provisioning`` shell command: - - .. code-block:: console - - uart:~$ dl provisioning CI_CA_key set <65-byte public key in hex without 0x> - - For example: - - .. code-block:: console - - uart:~$ dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 - - .. note:: - - You can also use the following commands: - - * ``uart:~$ dl provisioning CI_CA_key get`` - This command retrieves the Credential Issuer CA public key stored in the DUT. - * ``uart:~$ dl provisioning CI_CA_key clear`` - This command clears the Credential Issuer CA public key stored in the DUT. - - For example: - - .. code-block:: console - - uart:~$ dl provisioning CI_CA_key get - 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 - -.. _testing_reader_certificates: - -Aliro Reader Certificate Testing -********************************* - -The Aliro Reader can optionally use X.509 certificates for authentication during the expedited standard phase. -When a certificate is provisioned, the Reader sends a ``LOAD_CERT`` command after the ``AUTH0`` exchange, allowing the User Device to verify the Reader's identity. - -This feature is controlled by the ``CONFIG_DOOR_LOCK_READER_CERTIFICATE`` Kconfig option (enabled by default for non-Matter builds). - -Certificate generation and provisioning -======================================== - -Generating certificates ------------------------- - -#. Generate an Issuer key pair for signing certificates: - - .. code-block:: console - - cd scripts - python3 generate_keypair.py --verbose - - Save the output: - - * ``ISSUER_PRIV`` - Issuer Private Key (32 bytes hex) - * ``ISSUER_PUB`` - Issuer Public Key (65 bytes hex) - -#. Generate a Reader certificate. - - You can choose between two certificate sizes: - - * **Short certificate** (~152 bytes) - Does not require APDU chaining - * **Long certificate** (~270 bytes) - Requires APDU chaining (for testing chaining mechanism) - - **For short certificate:** - - .. code-block:: console - - python3 generate_reader_cert.py \ - --subject-pubkey \ - --issuer-privkey \ - --output hex | xargs python3 compress_reader_cert.py - - **For long certificate (tests APDU chaining):** - - .. code-block:: console - - ./generate_max_size_cert.sh - - Save the compressed certificate hex output for provisioning. - -Configuring Test Harness -------------------------- - -Update your Test Harness project configuration with the following fields: - -* ``dut_reader_issuer_public_key`` - Set to ``ISSUER_PUB`` from step 1 -* ``dut_reader_issuer_group_identifier`` - Must be different from ``dut_reader_group_identifier`` - -.. code-block:: json - - { - "config": { - "test_parameters": { - "dut_reader_public_key": "", - "dut_reader_group_identifier": "00113344667799AA00113344667799AB", - "dut_reader_issuer_group_identifier": "FFEEDDCCBBAA998877665544332211FF", - "dut_reader_issuer_public_key": "" - } - } - } - -.. important:: - The ``dut_reader_issuer_group_identifier`` must differ from ``dut_reader_group_identifier`` to properly test certificate-based authentication. - -Provisioning DUT ----------------- - -Connect to the DUT via serial console and provision both the certificate and issuer public key: - -#. Install the proper Reader group identifier (``dut_reader_issuer_group_identifier`` value): - - .. code-block:: console - - uart:~$ dl install group_id - -#. Provision the Issuer Public Key: - - .. code-block:: console - - uart:~$ dl provisioning issuer_pk set - -#. Provision the Reader Certificate (compressed): - - .. code-block:: console - - uart:~$ dl provisioning reader_cert set - -#. Verify provisioning: - - .. code-block:: console - - uart:~$ dl provisioning issuer_pk list - Issuer public key (65 bytes): - - uart:~$ dl provisioning reader_cert list - Reader certificate (XXX bytes): - -.. note:: - The certificate size is limited by ``CONFIG_DOOR_LOCK_READER_CERTIFICATE_MAX_SIZE`` (default: 512 bytes). - If you need to provision larger certificates, increase this value in your project configuration. - -Running tests with certificates -================================ - -Once the certificate is provisioned, execute Test Harness test cases that include the ``LOAD_CERT`` command: - -* ``NFC_RDR_STANDARD_CERT_IN_LOAD_CERT`` - Standard certificate test -* ``NFC_RDR_STANDARD_CERT_IN_LOAD_CERT_WITH_CHAINING`` - Certificate with APDU chaining - -Expected behavior: - -* DUT sends ``LOAD_CERT`` command with the provisioned certificate -* If using a long certificate: APDU chaining is used (multiple chunks) -* Test Harness verifies the certificate signature using ``ISSUER_PUB`` -* Transaction proceeds normally after certificate validation - -Clearing certificates ---------------------- - -To run standard tests without certificates, clear the provisioned data: - -.. code-block:: console - - uart:~$ dl provisioning reader_cert clear - uart:~$ dl provisioning issuer_pk clear - -After clearing, the DUT will skip the ``LOAD_CERT`` state and proceed directly from ``AUTH0`` response to ``AUTH1`` (expedited standard phase). - -.. _testing_verification: - -Aliro door lock provisioning with Matter -**************************************** - -If you are building an application with Matter support (see :ref:`building_and_running` and ``-DSNIPPET='matter'`` option), you can provision Aliro credentials from a Matter controller. -First, ensure that all the necessary software tools and hardware are configured (see :ref:`testing_environment`). - -The following section covers two different approaches for testing the Aliro door lock with Matter: - -* Testing with Matter CHIP Tool - Using the CHIP Tool command-line interface for commissioning and credential provisioning (intended for development and testing). -* Testing with Apple Ecosystem - Using Apple Home app and Apple Wallet for real-world user experience testing with iOS devices. - -.. _testing_with_chip_tool: - -Testing with Matter CHIP Tool -============================== - -This guide uses CHIP Tool as a Matter controller to commission the door lock and provision Aliro credentials through command-line interface. -For details, see the `Matter chip-tool guide`_. - -.. note:: - This approach is recommended for development, testing, and debugging purposes. - -#. Set up the `Thread Border Router`_ with the Nordic Thread coprocessor. - - a. Configure the `Thread radio co-processor`_. - - #. Run the OpenThread Border Router and `form a Thread network using Docker `_. - - #. Obtain the Thread operational dataset for commissioning devices into created Thread network by running the following Docker command: - - .. code-block:: console - - sudo docker exec -it otbr sh -c "sudo ot-ctl dataset active -x" - - Save this operational dataset as it will be needed for commissioning the door lock device. - -#. Commission the Aliro door lock to Matter over Thread network. - - a. Put the door lock device in commissioning mode by pressing the appropriate button on the development kit. - Refer to the :ref:`matter_ui` section for more details. - - #. Use CHIP Tool to commission the device with Thread operational dataset obtained: - - .. code-block:: console - - ./chip-tool pairing ble-thread hex: - - * ```` - A unique node ID for the device (for example, ``1``) - * ```` - The obtained Thread operational dataset - * ```` - Device pairing code (default: ``30033003``) - * ```` - Device discriminator (default: ``3003``) - - For example: - - .. code-block:: console - - ./chip-tool pairing ble-thread 1 hex:0e080000000000010000000300001335060004001fffe002084c70c4ba47c6a0780708fd12345678901234051000112233445566778899aabbccddeeff030e4f70656e5468726561642048423030010212340410445f2b5ca6f2a93a55ce570a70efeecb0c0402a0f7f8 30033003 3003 - - #. Verify successful commissioning by reading basic device information: - - .. code-block:: console - - ./chip-tool basicinformation read vendor-name 1 0 - - You should see the vendor information of the commissioned door lock device. - -#. Configure Aliro reader based on the Test Harness configuration. - - a. Generate an ECC key pair for the Reader by running the following commands: - - .. code-block:: console - - KEY_OUTPUT=$(openssl ecparam -name prime256v1 -genkey | openssl ec -text -noout) && \ - echo "Private Key:" && \ - echo "$KEY_OUTPUT" | sed -n '/priv:/,/pub:/p' | grep -v 'priv:\|pub:' | tr -d ' \n:' && \ - echo -e "\nPublic Key:" && \ - echo "$KEY_OUTPUT" | sed -n '/pub:/,/ASN1/p' | grep -v 'pub:\|ASN1' | tr -d ' \n:' - - See the following example output: - - .. code-block:: console - - Private Key: - 9df123f58dd15f6bab71bb6635827faf25100b043cdf6b62c93ea3c244ad4403 - Public Key: - 043c05b91fc09a84ad2ab7940a1b84f09b8ddf5323f1aac0f6568e1c973f37275dc67500a9df08d1bd69ee04e8641d9cbc73a4c1be30eed64def414f8afdc44642 - - #. Follow the :ref:`setting_up_the_aliro_test_harness` section to locate the necessary credentials in the Test Harness project configuration. - - #. Configure the Test Harness project: - - * Set ``dut_reader_public_key`` to the generated ``Public Key``. - * Note down the values of ``th_access_credential_public_key`` and ``dut_reader_group_identifier``. - You will need them for further configuration. - - #. Set the Aliro Reader configuration remotely with CHIP Tool using the following command: - - .. code-block:: console - - ./chip-tool doorlock set-aliro-reader-config hex: hex: hex: --GroupResolvingKey hex: - - * ```` - Signing key of the Reader device (generated ``Private Key``) - * ```` - Verification key of the Reader device (generated ``Public Key``) - * ```` - Group identifier of the Reader device (corresponds to ``dut_reader_group_identifier``) - * ```` - Unique node ID for the device (for example, ``1``) - * ```` - Endpoint ID of the Reader device (for example, ``1`` for the door lock cluster) - * ```` - Group resolving key of the Reader device - - .. note:: - The ```` option corresponds to ``dut_reader_group_sub_identifier``, which can be set in the Test Harness project configuration. - - See the following example: - - .. code-block:: console - - ./chip-tool doorlock set-aliro-reader-config hex:9df123f58dd15f6bab71bb6635827faf25100b043cdf6b62c93ea3c244ad4403 hex:043c05b91fc09a84ad2ab7940a1b84f09b8ddf5323f1aac0f6568e1c973f37275dc67500a9df08d1bd69ee04e8641d9cbc73a4c1be30eed64def414f8afdc44642 hex:00113344667799AA00113344667799AA 1 1 --GroupResolvingKey hex:00000000000000000000000000000000 --timedInteractionTimeoutMs 5000 - - .. note:: - The ``GroupResolvingKey`` can only be set if the Aliro door lock application is built with Bluetooth LE transport and Ultra-Wideband (UWB) support. - Currently, the Aliro Test Harness expects the ``GroupResolvingKey`` to be set to all zeros. - - - -#. Add door lock user and AliroEvictableEndpointKey credentials using CHIP Tool. - - a. Add the first user (Home user): - - .. code-block:: console - - ./chip-tool doorlock set-user 0 1 Home 0 1 0 0 1 1 --timedInteractionTimeoutMs 5000 - - * Endpoint ID - ``0`` - * User index - ``1`` - * User name - ``Home`` - * User unique ID - ``0`` - * User status (1 = enabled) - ``1`` - * User type (0 = unrestricted) - ``0`` - * Credential rule (0 = single) - ``0`` - * Node ID of the door lock - ``1`` - * Fabric index - ``1`` - - #. Set the first AliroEvictableEndpointKey credential for the Home user: - - .. code-block:: console - - ./chip-tool doorlock set-credential 0 '{"credentialType": 7, "credentialIndex": 1}' hex: 1 null null 1 1 --timedInteractionTimeoutMs 5000 - - * Endpoint ID - ``0`` - * User index - ``1`` - * User name - ``Home`` - * User unique ID - ``0`` - * User status (1 = enabled) - ``1`` - * User type (0 = unrestricted) - ``0`` - * Credential rule (0 = single) - ``0`` - * Node ID of the door lock - ``1`` - * Fabric index - ``1`` - * Credential type - ``7`` - * Credential index - ``1`` - * ```` - An octet string parameter with the secret credential data (corresponds to ``th_access_credential_public_key``) - - See the following example: - - .. code-block:: console - - ./chip-tool doorlock set-credential 0 '{"credentialType": 7, "credentialIndex": 1}' hex:04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa 1 null null 1 1 --timedInteractionTimeoutMs 5000 - - #. Read the status of credential assigned to the user to verify that it was set correctly: - - .. code-block:: console - - ./chip-tool doorlock get-credential-status {"credentialType": 7, "credentialIndex": 1} 1 1 - -#. Add Aliro Credential Issuer key using CHIP Tool. - Once you have a user created on the door lock, you can add the Credential Issuer public key to the Reader using the following command: - - .. code-block:: console - - ./chip-tool doorlock set-credential 0 '{"credentialType": 6, "credentialIndex": 1}' hex: 1 null null 1 1 --timedInteractionTimeoutMs 5000 - - * Operation-type - ``0``. - This operation adds a new credential to a ``User index``. - * Credential type - ``6``. - The value represents the Aliro Credential Issuer public key. - * Credential index - ``1`` - * ```` - Is an octet string parameter with the Credential Issuer public key data. - It corresponds to ``th_access_credential_public_key``. - * User index - ``1``. - * User status - ``null``. - * User type - ``null``. - * Destination ID - ``1``. - * Endpoint ID - ``1``. - - See the following example: - - .. code-block:: console - - ./chip-tool doorlock set-credential 0 '{"credentialType": 6, "credentialIndex": 1}' hex:047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 1 null null 1 1 --timedInteractionTimeoutMs 5000 - -.. _testing_with_apple_ecosystem: - -Testing with Apple ecosystem -============================= - -This guide demonstrates how to test the Aliro door lock with Matter support using Apple's Home App and Wallet on iPhone. - -For additional information about Matter testing with various ecosystems, see the `Matter testing with Apple, Google, and Samsung ecosystems`_ guide. - -.. warning:: - Support for testing with Apple ecosystem is considered experimental and is not optimized with respect to performance and power consumption. - This feature is intended for development and testing purposes only. - -.. note:: - When testing |APP_NAME| with Apple ecosystem, you can use either: - - * **NFC** - Tap to unlock using NFC technology - * **NFC + Bluetooth LE + UWB** - Unlock on approach using Bluetooth LE + UWB ranging (in addition to NFC) - - Both configurations are supported and can be set up during the commissioning process. - -.. note:: - Testing with Apple ecosystem requires both Step-up phase and Expedited-fast phase support to be enabled on the device. - Both ``CONFIG_DOOR_LOCK_STEP_UP_PHASE`` and ``CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE`` Kconfig options are enabled by default when building with Matter support. - -Prerequisites -------------- - -Before you begin testing with Apple ecosystem, ensure you have the following: - -* iPhone with iOS 26 or later - Required for Aliro-related features in the Apple Home and Wallet apps. -* Apple Home Hub - A HomePod Mini (recommended) or an Apple TV 4K, which acts as both a Matter controller and Thread Border Router. -* Apple Home App - Pre-installed on iOS devices. -* Apple Wallet - Pre-installed on iOS devices. -* nRF Door Lock App build with Matter support - A development kit from Nordic Semiconductor equipped with Aliro door lock firmware that supports Matter. - For details, see the :ref:`building_and_running` documentation page. - -Building the firmware -~~~~~~~~~~~~~~~~~~~~~ - -Build the firmware with the appropriate configuration for your testing scenario, for example: - -**For NFC testing:** - -.. code-block:: console - - west build -b nrf54lm20dk/nrf54lm20a/cpuapp -- -DSNIPPET='matter' - -**For NFC + Bluetooth LE + UWB testing:** - -.. code-block:: console - - west build -b nrf54lm20dk/nrf54lm20a/cpuapp -- -DSNIPPET='matter' -Dapp_SNIPPET='uwb_qm35' - -.. note:: - Before commissioning the door lock, ensure that the Apple Home Hub is set up and added to your Apple Home. - It will act as the Thread Border Router and Matter controller for your home network. - -Commissioning the door lock to Apple Home ------------------------------------------- - -The commissioning process allows you to add the door lock accessory into your Apple Home ecosystem using Matter over Thread. -Complete the following steps to commission the device: - -1. Prepare the door lock device: - - .. tabs:: - - .. tab:: NFC - - a. Flash the Aliro door lock firmware with Matter support enabled to your Nordic development kit (see :ref:`building_and_running` with the ``-DSNIPPET='matter'`` option). - #. Connect the required hardware (:ref:`NFC reader expansion board`). - #. Power on the device and connect to the serial console to monitor the commissioning process. - #. Start the commissioning mode on the device. - Refer to the :ref:`matter_ui` section for detailed button assignments and LED indicators. - - .. tab:: NFC + Bluetooth LE + UWB - - a. Flash the Aliro door lock firmware with Matter and UWB support enabled to your Nordic development kit (see :ref:`building_and_running` with the ``-DSNIPPET='matter'`` and ``-Dapp_SNIPPET='uwb_qm35'`` options). - #. Connect the required hardware: - - * :ref:`NFC reader expansion board` (required for NFC unlock) - * :ref:`UWB module` (required for UWB ranging) - - #. Power on the device and connect to the serial console to monitor the commissioning process. - #. Start the commissioning mode on the device. - Refer to the :ref:`matter_ui` section for detailed button assignments and LED indicators. - -#. Initiate Matter pairing in the Apple Home app: - - a. Open the Home App on your iPhone. - #. Ensure your iPhone is connected to the same Wi-Fi network as your Apple Home Hub. - #. Tap the :guilabel:`+` button in the top-right corner of the Home app. - #. Select :guilabel:`Add Accessory` or :guilabel:`Add Device`. - -#. Scan the Matter QR code displayed under the link that appears in the device's serial console after it boots up. - The Home app will activate the camera for QR code scanning. - - .. note:: - During testing, you might see an Uncertified Accessory warning, indicating the device has not yet been certified by the Connectivity Standards Alliance for Matter compatibility. - This behavior is expected for development devices. - If this occurs, tap :guilabel:`Add Anyway` to proceed with commissioning. - - .. figure:: /images/apple_uncertified_accessory.png - :alt: Uncertified Accessory warning in Apple Home app - :scale: 30% - - Uncertified Accessory warning during commissioning - -#. Complete the accessory setup: - - a. Wait while the Home App establishes a connection and commissions the device. - The status message will show ``Setting Up...``. - #. When prompted, assign a location or room for the lock (for example, "Entrance", "Front Door", or "Garage"). - - .. figure:: /images/apple_lock_location_setup.png - :alt: Lock location selection in Apple Home app - :scale: 30% - - Selecting the lock location during setup - - #. Provide a name for the lock (for example, "Aliro lock" or "Front Door Lock"). - #. Review the suggested settings and tap :guilabel:`Continue` or :guilabel:`Done`. - -#. Enable Home Key functionality. - After successful commissioning, the Home app will automatically prompt you to set up the Home Key feature for Apple Wallet: - - .. tabs:: - - .. tab:: NFC - - a. Wait for the :guilabel:`Tap to Unlock` setup screen with options for unlock methods. - - .. figure:: /images/apple_tap_to_unlock_setup.png - :alt: Tap to Unlock setup screen in Apple Home app - :scale: 30% - - Tap to Unlock configuration screen - - #. Tap :guilabel:`Turn On Tap to Unlock` to enable NFC-based unlocking using your iPhone. - #. Choose your preferred authentication methods: - - * :guilabel:`Require Face ID or Passcode` - Adds an additional security layer requiring biometric or PIN authentication before unlocking. - * :guilabel:`Access Codes` - Adds an additional security layer requiring a PIN code to unlock the door lock remotely (through the Matter network). - - #. Tap :guilabel:`Done` to complete the Home Key provisioning. - - .. tab:: NFC + Bluetooth LE + UWB - - a. Wait for the :guilabel:`Express Mode` setup screen with options for unlock methods. - - .. figure:: /images/apple_express_mode_setup.png - :alt: Express Mode setup screen in Apple Home app - :scale: 30% - - Express Mode configuration screen - - #. Tap :guilabel:`Turn On Express Mode` to enable both NFC-based tap to unlock and Bluetooth LE + UWB-based unlock on approach using your iPhone. - #. Accept the prompt to enable proximity-based unlocking during commissioning. - #. Choose your preferred authentication methods: - - * :guilabel:`Require Face ID or Passcode` - Adds an additional security layer requiring biometric or PIN authentication before unlocking. - * :guilabel:`Access Codes` - Adds an additional security layer requiring a PIN code to unlock the door lock remotely (through the Matter network). - - #. Tap :guilabel:`Done` to complete the Home Key provisioning. - -#. Verify successful commissioning: - - a. Wait for the :guilabel:`Lock Added to Home` confirmation pop-up with a message indicating that you and all home members can now unlock the door. - - .. figure:: /images/apple_lock_added_confirmation.png - :alt: Lock Added to Home confirmation - :scale: 30% - - Confirmation that the lock has been successfully added - - The lock should now appear in your Home app within the assigned room, displaying its current state (:guilabel:`Locked` or :guilabel:`Unlocked`). - - .. figure:: /images/apple_home_app_lock_view.png - :alt: Aliro lock in Apple Home app - :scale: 30% - - Aliro lock displayed in the Home app - - #. Ensure that a new home card appears in your Apple Wallet app containing the digital key for the lock. - - .. figure:: /images/apple_wallet_hold_near_lock.png - :alt: Apple Wallet Hold Near Lock instruction - :scale: 30% - - Apple Wallet containing the newly added Home key - -Using the home key for access ------------------------------- - -Once the lock is successfully commissioned to Apple Home, a virtual home key containing Aliro credentials is automatically provisioned and added to your Apple Wallet. - -Accessing the home key in Apple Wallet -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To use your digital home key from Apple Wallet, complete the following: - -#. Open the Wallet app on your iPhone. -#. Locate the new home card displaying a house icon. - This card represents your digital home key and contains the Aliro cryptographic credentials required for secure authentication with the door lock. - -Unlocking the door -~~~~~~~~~~~~~~~~~~ - -.. tabs:: - - .. tab:: NFC - - To unlock the door using your iPhone with NFC, complete the following steps: - - #. Approach the door lock with your iPhone, ensuring the Apple Wallet app is open and the Home Key card is active. - Depending on the security settings you chose during the initial setup, you may be prompted for biometric authentication (Face ID or Touch ID) or passcode entry to make the card active. - - #. Hold your device near the NFC reader antenna on the door lock. - This is a part of the STM Nucleo NFC reader expansion board attached to the development kit. - - Authentication behavior depends on your chosen security setting: - - * Face ID or Passcode required - Your device will ask for biometric authentication (Face ID or Touch ID) or passcode entry before unlocking. - - #. Wait for confirmation: - - * The Apple Wallet will display "Done" message. - - .. figure:: /images/apple_home_key_success.png - :alt: Home key successfully added to Apple Wallet - :scale: 30% - - Apple Wallet displaying confirmation after successful unlock with Home key - - * The device serial console outputs an ``ACCESS GRANTED`` log message. - * The lock status in the Home app updates to :guilabel:`Unlocked`. - - .. tab:: NFC + Bluetooth LE + UWB - - To unlock the door using your iPhone with NFC + Bluetooth LE + UWB, you can use either method: - - **Using NFC (tap to unlock):** - - #. Approach the door lock with your iPhone, ensuring the Apple Wallet app is open and the Home Key card is active. - Depending on the security settings you chose during the initial setup, you may be prompted for biometric authentication (Face ID or Touch ID) or passcode entry to make the card active. - - #. Hold your device near the NFC reader antenna on the door lock. - This is a part of the STM Nucleo NFC reader expansion board attached to the development kit. - - #. Wait for confirmation - The Apple Wallet will display "Done" message, the device serial console outputs an ``ACCESS GRANTED`` log message, and the lock status in the Home app updates to :guilabel:`Unlocked`. - - **Using Bluetooth LE + UWB (unlock on approach):** - - #. Ensure your iPhone has Bluetooth enabled and is within range of the door lock. - The door will automatically unlock when your iPhone is within the configured distance threshold. - - #. Approach the door lock with your iPhone. - The device will automatically detect your proximity using UWB ranging. - - #. Wait for automatic unlock: - - * The door will unlock when your iPhone is within ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM`` (default: 100 cm). - * The device serial console outputs an ``ACCESS GRANTED`` log message. - * The lock status in the Home app updates to :guilabel:`Unlocked`. - - #. To lock the door again: - - * Move your iPhone beyond ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM + CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_EXIT_MARGIN_CM`` (default: 100 cm + 50 cm = 150 cm). - * The door will automatically lock when all active UWB sessions are out of range. - * The device serial console outputs a lock confirmation message. - * The lock status in the Home app updates to :guilabel:`Locked`. - - .. note:: - The exit margin prevents rapid toggling when the distance fluctuates around the threshold. - This ensures stable lock/unlock behavior. - - .. note:: - The behavior of the door lock can be customized and depends on various factors, including the behavior after unlocking. - In the example configuration with ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_DISABLED``, the UWB session is terminated by the User Device (iPhone) after a certain period of time. - The User Device resumes the session either when the Door Lock changes state from Non-secured to Secured (Unlocked => Locked), which is detected by the iPhone via Bluetooth LE, or through motion detection on the User Device side. - -Troubleshooting ---------------- - -If you encounter issues during testing with the Apple ecosystem, refer to the following troubleshooting guidance. - -Commissioning issues -~~~~~~~~~~~~~~~~~~~~ - -If you are facing difficulties during the commissioning process, consider the following steps to diagnose and resolve the issue: - -* Device is not discovered by the Home app: - - * Verify if the device is in active commissioning mode (LED indicators should show commissioning state as described in the :ref:`matter_ui`). - * Ensure Bluetooth is enabled on your iPhone, and that Home app has permission to access location services. - * Confirm your iPhone is running iOS 26 or later. - * Ensure that the HomePod mini is powered on, connected to Wi-Fi, and showing as online in the Home app. - * Attempt to power cycle both the door lock device and the HomePod mini. - -* Receiving an "Uncertified Accessory" warning: - - * This is expected behavior for development devices that are not yet certified by the Connectivity Standards Alliance (CSA). - * Tap :guilabel:`Add Anyway` to proceed with commissioning. - * Once the product is certified, this warning will no longer appear. - -* Commissioning fails or times out: - - * Ensure the HomePod mini is on the same network as your iPhone. - * Check the device serial console for error messages indicating commissioning failures. - * Ensure the firmware was built with Matter support enabled (the ``-DSNIPPET='matter'`` option). - * Ensure the Step-up phase support is enabled (the ``CONFIG_DOOR_LOCK_STEP_UP_PHASE`` Kconfig option). - * Try performing a factory reset of the device and restarting the commissioning process. - * Ensure the Thread network is functioning correctly on the HomePod mini. - -Home Key and Wallet issues -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Problems with the Home Key appearing in Apple Wallet or other related issues can often be resolved by checking a few key settings and configurations: - -* Home key does not appear in the Apple Wallet: - - .. tabs:: - - .. tab:: NFC - - * Ensure you have completed the :guilabel:`Tap to Unlock` setup during the initial commissioning process - * If you skipped this step, you can enable it later by opening the Home app, tapping the lock, going to the settings icon, and clicking :guilabel:`Tap to Unlock`. - * Ensure your iPhone has Apple Wallet enabled and is signed in with your Apple ID. - * Check if iCloud Keychain is enabled by going to iPhone :guilabel:`Settings` → :guilabel:`[Your Name]` → :guilabel:`iCloud` → :guilabel:`Passwords and Keychain`. - - .. tab:: NFC + Bluetooth LE + UWB - - * Ensure you have completed the :guilabel:`Express Mode` setup during the initial commissioning process - * If you skipped this step, you can enable it later by opening the Home app, tapping the lock, going to the settings icon, and clicking :guilabel:`Express Mode`. - * Ensure your iPhone has Apple Wallet enabled and is signed in with your Apple ID. - * Check if iCloud Keychain is enabled by going to iPhone :guilabel:`Settings` → :guilabel:`[Your Name]` → :guilabel:`iCloud` → :guilabel:`Passwords and Keychain`. - -* The "Hold Near Lock" message appears but nothing happens (NFC unlock): - - * Verify if the NFC reader hardware is properly connected to the development kit (check physical connections). - * Ensure the firmware includes the NFC transport support by checking the build configuration. - * Adjust the position of your iPhone relative to the NFC reader antenna to improve connectivity. - * Check the device serial console for NFC reader initialization messages and any error logs. - -* The door does not unlock automatically when approaching (Bluetooth LE + UWB): - - * Verify if the UWB module is properly connected to the development kit (check physical connections). - * Ensure the firmware includes UWB support by checking the build configuration (``-Dapp_SNIPPET='uwb_qm35'``). - * Ensure your iPhone is within ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM`` (default: 100 cm) of the door lock. - * Check the device serial console for UWB ranging messages and distance measurements. - * Verify that Bluetooth is enabled on your iPhone and that the Home app has permission to access location services. - -* Face ID or passcode prompt does not appear when expected: - - * Review the home key security settings by going to Wallet app → :guilabel:`Home card` → :guilabel:`ⓘ` (info) → :guilabel:`Express Mode settings`. - * If you wish to use the :guilabel:`Require Face ID or Passcode` option, ensure it is enabled in the settings. - -For additional troubleshooting, guidance, and tips for various ecosystems, see the `Matter testing with Apple, Google, and Samsung ecosystems`_ guide. - -.. _matter_ui: - -Matter door lock user interface -******************************* - -This section describes the user interface of the |APP_NAME| when Matter is enabled. - - LED 1: - .. include:: /include/matter_state_led.txt - - LED 2: - .. include:: /include/matter_signalling_led.txt - - Button 1: - .. include:: /include/matter_button.txt - - Button 2: - * Changes the lock state to the opposite one. - - -.. note:: - The button and LED numbering differs between development kits. - On the nRF52840 and nRF5340 boards, the numbering starts from 1. - On the nRF54L15 and nRF54LM20 boards, it starts from 0. - -Aliro and Matter lock state synchronization -******************************************* - -When the door lock is unlocked through Aliro, the lock state automatically synchronizes with the Matter door-lock server cluster. -This synchronization ensures that User Devices in the Matter network are notified of lock state changes in real time. - -To verify the current lock state, run the following command: - -.. code-block:: console - - ./chip-tool doorlock read lock-state 1 1 - -After executing the command, find the lock state in the response, for example: - -.. code-block:: console - - [1757071349.943] [949263:949265] [TOO] LockState: 1 - -The response indicates that the lock is locked. - -Next, try to execute, for example, the ``BLEUWB_RDR_EXPEDITED_STANDARD_PHASE`` test with Bluetooth LE + UWB transport (see :ref:`testing_verification_th`) using the Test Harness. -If the test is successful, run again the ``./chip-tool doorlock read lock-state 1 1`` command. -You should see the following status in the response: - -.. code-block:: console - - [1757072050.821] [951199:951201] [TOO] LockState: 2 - -The response indicates that the lock is unlocked. - -.. _testing_verification_th: - -Verification and testing process -******************************** - -Once you have successfully provisioned Aliro credentials, either through CLI or with Matter, perform tests to ensure your device is functioning correctly. -You can see the full list of available tests in the `Aliro Certification Tool`_ repository. -From the :guilabel:`Test Suites` list, choose the tests with the `Reader` in the name, for example, :guilabel:`BLE Reader`. -This will allow you to test the Bluetooth LE + UWB transport. - -.. note:: - You can upload the :file:`applications/doorlock/docs/certification_assets/Aliro PICS v0.9.3.r2.xml` file to the Test Harness to automatically select tests for execution. - However, **there is a known bug in this Test Harness revision** that causes the PICS file to select some incorrect tests and skip some needed to execute. - - **Tests that should be executed** (but may be skipped): - - * ``NFC_RDR_NEG_STEPUP_AD_SCHEDULE_IN_ACCESS_RULE_AND_READER`` - * ``NFC_RDR_NEG_STEPUP_AD_SCHEDULE_TIME_VERIFY_REQUIRED`` - * ``NFC_RDR_NEG_STEPUP_AD_TIME_VERIFICATION_REQUIRED`` - * ``BLEUWB_RDR_ADVERTISEMENT_FORMAT`` - - **Tests that should not be executed** (but may be selected): - - * ``BLEUWB_RDR_CONTROL_FLOW_RDR_DESCRIPTOR_TAG`` - - Always verify that the correct tests are chosen before executing them. - If you don't upload the PICS file, you can choose the tests manually. - -.. figure:: /images/th_test_cases.png - :scale: 70% - :alt: Test suites list. - - Test suites list. - -Thanks to this, the Test Harness device will be operating as the Aliro User Device and communicate with the Reader device. - -For verification, execute the following tests, based on the `aliro-sve-v1.0`_ tag. - -The test results shown below were acquired on an nRF5340 DK build using the following command: - -.. code-block:: bash - - west build -b nrf5340dk/nrf5340/cpuapp -- -Dapp_SNIPPET=uwb_qm35 -DCONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y -DCONFIG_DOOR_LOCK_STEP_UP_PHASE=y -DCONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA=y - -.. tabs:: - - .. tab:: NFC Reader - - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | Test case | Description | Result | Comment | - +===========================================================+=========================================================================================+========+==============+ - | NFC_RDR_STANDARD_NO_CERT | Verify conformance of Reader UT in AUTH1 command. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_AUTH0_EXTRA_TAG | Verify conformance of Reader UT in AUTH1 command. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_AUTH1_EXTRA_TAG | Verify conformance of Reader UT in AUTH1 command. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_AUTH0_WRONG_VALUE | Verify conformance of Reader UT in AUTH1 command. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_SEL_RSP_NO_COMMON_EXPEDITED_PROTOCOL_VERSION | Verify conformance of Reader UT in SELECT command. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_AUTH1_WRONG_VALUES | Verify conformance of Reader UT in AUTH1 command. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_AUTH1_WRONG_UD_SIGNATURE | Verify conformance of Reader UT in AUTH1 command. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_STANDARD_CERT_IN_LOAD_CERT_WITH_CHAINING | Verify conformance of Reader during NFC standard "transaction using LOAD CERT protocol".| PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_FAST | Verify conformance of Reader UT in expedited fast phase. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_DEVICE_KEY_INFO_MISMATCH | Verify rejection of Access Document not associated with the Access Credential. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_DOCTYPE_NOT_ALIROA | Verify rejection of Access Document with invalid docType. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_INVALID_ACCESS_DATA_ELEMENT_VERSION | Verify rejection of Access Document with incorrect Access Data Element version. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_INVALID_HASH_ISSUER_AUTH | Verify rejection of Access Document with an invalid Data Element digest. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_INVALID_SIGNATURE_ISSUER_AUTH | Verify rejection of Access Document with invalid IssuerAuth signature. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_ISSUER_CERTIFICATE_TIME_MISMATCH | Verify rejection of Access Document with issuer certificate time mismatch. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_ISSUER_CERT_INVALID_SIGNATURE | Verify rejection of Access Document with invalid issuer certificate signature. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_ISSUER_DOCTYPE_MISMATCH | Verify rejection of Access Document with incorrect issuerAuth doctype. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_NO_ACCESS_RULE_FOR_READER_ACTION | Verify rejection of Access Document with no Access Rule for the intended action. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_NO_DATA_ELEMENTS | Verify rejection of Access Document with no data elements. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_NO_ISSUER_CERT_NO_KEY_ID | Verify rejection of Access Document with no Issuer Certificate or Key Identifier. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_SCHEDULE_IN_ACCESS_RULE_AND_READER | Verify rejection of Access Document with schedule in access rule and reader. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_SCHEDULE_TIME_VERIFY_REQUIRED | Verify rejection of Access Document requiring schedule time verification. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_TIME_VERIFICATION_REQUIRED | Verify rejection of Access Document requiring time verification. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_UNKNOWN_CRITICAL_ACCESS_EXTENSION | Verify rejection of Access Document with unknown critical access extension. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_UNKNOWN_READER_RULE | Verify rejection of Access Document with unknown reader rule. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_NEG_STEPUP_AD_VALIDITY_ITERATION | Verify rejection of Access Document with validity iteration. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_STEPUP_AD_ACCESS_RULE | Verify parsing of Access Document with Access Rule. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_STEPUP_AD_ISSUER_CERT_KEY_ID | Verify parsing of Access Document with Issuer Certificate Key ID. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_STEPUP_AD_ISSUER_CERT | Verify parsing of Access Document with Issuer Certificate. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_STEPUP_AD_KEY_ID | Verify parsing of Access Document with Key Identifier. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_STEPUP_AD_UNKNOWN_NON_ACCESS_EXTENSION | Verify parsing of Access Document with unknown non-access extension. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - | NFC_RDR_STEPUP_AD_UNKNOWN_NON_CRITICAL_ACCESS_EXTENSION | Verify parsing of Access Document with unknown non-critical access extension. | PASS | | - +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ - - .. tab:: Bluetooth LE + UWB - - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | Test case | Description | Result | Comment | - +===========================================+==========================================================================+========+=============================+ - | BLEUWB_RDR_EXPEDITED_STANDARD_PHASE | Verify conformance of Reader UT in standard phase expedited transaction. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_RANGING_SUSPEND | Verify conformance of Reader UT in ranging suspend functionality. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_RANGING_RESUME | Verify conformance of Reader UT in ranging resume functionality. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_NEG_FAILED_L2CAP | Verify conformance of Reader UT in L2CAP connection failure handling. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_NEG_FAILED_SPSM_L2CAP | Verify conformance of Reader UT in SPSM L2CAP failure handling. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_NEG_TIMEOUT_BEFORE_AUTH0 | Verify conformance of Reader UT in timeout handling before AUTH0. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_TIMEOUT_EXTENSION | Verify conformance of Reader UT in timeout extension handling. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_NEG_M2_MISMATCH_PARAMETER | Verify conformance of Reader UT in M2 parameter mismatch handling. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_NEG_M4_MISMATCH_PARAMETER | Verify conformance of Reader UT in M4 parameter mismatch handling. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_ADVERTISEMENT_FORMAT | Verify conformance of Reader UT in advertisement format. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_NEG_SUSPEND_MISMATCH_PARAMETER | Verify conformance of Reader UT in suspend parameter mismatch handling. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_STEPUP_PHASE | Verify conformance of Reader UT in step-up phase. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - | BLEUWB_RDR_EXPEDITED_FAST_PHASE | Verify conformance of Reader UT in expedited fast phase. | PASS | | - +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ - -Running the test -================ - -Complete the following steps for the required tests: - -#. Navigate to the project you created in the Test Harness web interface and click :guilabel:`Go To Test-Run`. - -#. Click on :guilabel:`Add New Test`. - -#. In the test suites, select either :guilabel:`NFC Reader` or :guilabel:`BLE Reader` and under the :guilabel:`Test Cases` section check the box of the test you wish to run. - -#. Choose the operator and click :guilabel:`Start`. - -#. Complete the test: - - .. tabs:: - - .. tab:: NFC - - a. Position the Reader and Test Harness hardware close to each other, and align them to ensure optimal NFC communication. - #. A notification will appear asking you to set the Reader DUT in the appropriate mode and to place the devices next to each other for automatic NFC detection. - Select :guilabel:`Ok` and click :guilabel:`Submit`. - - .. tab:: Bluetooth LE - - a. Ensure the Reader is advertising and ready to accept Bluetooth LE connections from the Test Harness. - You should see a notification requesting Bluetooth LE visibility for automatic detection. - #. Confirm that the Reader is advertising by checking the DUT console for logs indicating that advertising has started. - - .. code-block:: console - - L2CAP server registered with PSM: 0x0080 - - #. Select :guilabel:`Ok` and click :guilabel:`Submit`. - Restarting the Murata device, if prompted, is optional. - - .. note:: - At the start of each Bluetooth LE test, the firmware is uploaded to the Murata device, which may take some time. - The Murata module (`LBUA0VG2BP-EVK-P`_) is used by the Test Harness to establish and handle Bluetooth LE communication with the device under test. - -#. Depending on the test you executed, you should see results similar to the following examples: - - .. tabs:: - - .. tab:: NFC_RDR_STANDARD_NO_CERT - - .. figure:: /images/NFC_RDR_STANDARD_NO_CERT_selection.png - :scale: 50% - :alt: Tests selection view. - - Tests selection view. - - The Reader device will select the Test Harness User Device and initiate the Aliro Access Protocol commands exchange. - - In the DUT's serial console, you will see logs that indicate the state of the operation and the data payloads transmitted and received by the Reader. - If the results show as ``passed``, you will see the following output in the Test Harness web interface: - - .. figure:: /images/test_results_NFC_RDR_STANDARD_NO_CERT.png - :scale: 50% - :alt: Example of the advanced test results. - - Example of the advanced test results. - - After test execution is complete, you can check DUT logs to verify the communication and data exchange between the Reader and the test harness. - The logs will provide detailed information about the test execution and authorization process (signature verification). - When all installation and provisioning data provided in :ref:`testing_environment_configuration` are correct then you will see the following output in the DUT serial console: - - .. code-block:: console - - ACCESS GRANTED - - .. note:: - When access is granted, the device also signals this event by turning on a dedicated LED. For details, see the :ref:`access decision indicator ` section. - - When the provided Access Credential public key is incorrect the following output will be displayed: - - .. code-block:: console - - ACCESS DENIED - - .. tab:: BLEUWB_RDR_EXPEDITED_STANDARD_PHASE - - .. figure:: /images/BLEUWB_RDR_EXPEDITED_STANDARD_PHASE_selection.png - :scale: 50% - :alt: Tests selection view. - - Tests selection view. - - The Reader device will advertise over Bluetooth LE and the Test Harness will connect as a Bluetooth LE central device, initiating the Aliro Access Protocol commands exchange over Bluetooth LE. - - In the DUT's serial console, you will see logs that indicate the state of the Bluetooth LE connection, protocol execution, and the data payloads transmitted and received by the Reader. - Additionally, the UWB connection will be configured and established. - If the results show as ``passed``, you will see the following output in the Test Harness web interface: - - .. figure:: /images/test_results_BLEUWB_RDR_EXPEDITED_STANDARD_PHASE.png - :scale: 50% - :alt: Basic test results view. - - Basic test results view. - - -.. note:: - If the application is built with Matter support, you can control the door lock remotely. - For details on how to enable this feature, see the |NCS| door lock sample documentation in the `Matter door lock remote access`_ section. - -Guide to execute the Aliro certification tests -============================================== - -This guide assumes PICS file :file:`applications/doorlock/docs/certification_assets/Aliro PICS v0.9.3.r2.xml` was uploaded to the Test Harness. -The user can also select tests manually. - -.. figure:: /images/pics_upload.png - :scale: 70% - :alt: PICS file upload. - - PICS file upload. - -When PICS file is uploaded, all required tests are selected automatically for NFC and BLE Reader suites: - -.. figure:: /images/tests_selected_by_pics.png - :scale: 70% - :alt: BLE selected tests. - - BLE selected tests. - -The table explaining what should be configured before specific test is shown below, some tests may also require post actions. - -.. tabs:: - - .. tab:: NFC Reader - - +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | Test case(s) | Door Lock commands to execute | - +============================================================+===============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================+ - | ALL TESTS | Before test session: | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | - | | * dl install group_id 37652039312061652031642033642065 | - +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_FAST, | Before test: | - | NFC_RDR_NEG_AUTH0_EXTRA_TAG, +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_NEG_AUTH0_WRONG_VALUE, | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | - | NFC_RDR_NEG_AUTH1_EXTRA_TAG, +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_NEG_AUTH1_WRONG_UD_SIGNATURE, | Optional: | - | NFC_RDR_NEG_AUTH1_WRONG_VALUES, +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_NEG_SEL_RSP_NO_COMMON_EXPEDITED_PROTOCOL_VERSION, | * dl install group_id 37652039312061652031642033642065 | - | NFC_RDR_NEG_STEPUP_AD_DEVICE_KEY_INFO_MISMATCH, | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | NFC_RDR_NEG_STEPUP_AD_DOCTYPE_NOT_ALIROA, | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | NFC_RDR_NEG_STEPUP_AD_INVALID_ACCESS_DATA_ELEMENT_VERSION, | | - | NFC_RDR_NEG_STEPUP_AD_INVALID_HASH_ISSUER_AUTH, | | - | NFC_RDR_NEG_STEPUP_AD_INVALID_SIGNATURE_ISSUER_AUTH, | | - | NFC_RDR_NEG_STEPUP_AD_ISSUER_CERT_INVALID_SIGNATURE, | | - | NFC_RDR_NEG_STEPUP_AD_ISSUER_CERTIFICATE_TIME_MISMATCH, | | - | NFC_RDR_NEG_STEPUP_AD_ISSUER_DOCTYPE_MISMATCH, | | - | NFC_RDR_NEG_STEPUP_AD_NO_ACCESS_RULE_FOR_READ, | | - | NFC_RDR_NEG_STEPUP_AD_NO_DATA_ELEMENTS, | | - | NFC_RDR_NEG_STEPUP_AD_NO_ISSUER_CERT_NO_KEY_ID, | | - | NFC_RDR_NEG_STEPUP_AD_UNKNOWN_CRITICAL_ACCESS_EXTENSION, | | - | NFC_RDR_NEG_STEPUP_AD_UNKNOWN_READER_RULE | | - +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_NEG_STEPUP_AD_VALIDITY_ITERATION | Before test: | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | - | | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | Optional: | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | * dl install group_id 37652039312061652031642033642065 | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | After test: | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | * dl provisioning CI_key clear 0 | - +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_STANDARD_CERT_IN_LOAD_CERT_WITH_CHAINING | Before test: | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | * dl provisioning AC_key clear 0 | - | | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | | * dl install group_id 000000000000000000113344667799ab | - | | * dl provisioning issuer_pk set 048608ad7d0bf1258ba6249aa3854b43acef9443cce31b617c32503adb959e5ad7d5915a1ff34ab461b46ff598c5fc72c6a6c8787c886741a4029bb3476eceff26 | - | | * dl provisioning reader_cert set 3082010a040200003082010280145555555555555555555555555555555555555555811e637573746f6d20697373756572206e616d652e2e2e2e2e2e2e2e2e2e2e2e820d3230303130323030303030305a830d3235303530353030303030305a841e637573746f6d207375626a656374206e616d652e2e2e2e2e2e2e2e2e2e2e85420004f27d92b78c0ba00c105dc56b9f5a669d67d44fea5139b85dc5e368c851167c9f40b8d9add7ce7bf846331f1f8fd06e1e15cfd540190f482ec294f52690349f188648003045022051bc8503cc09a80f9f19fa033edcf68fb4c341499d908950de62382e9650ee5b022100a954899fd291c40d4f3f9f749518958678ff5688e7bdaa8a3535faf72450e506| - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | After test: | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | * dl provisioning CI_key clear 0 | - | | * dl install group_id 37652039312061652031642033642065 | - | | * dl provisioning issuer_pk clear | - | | * dl provisioning reader_cert clear | - +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_STANDARD_NO_CERT | Before test: | - | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | | * dl install group_id 37652039312061652031642033642065 | - | | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | - | | | - +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_STEPUP_AD_ACCESS_RULE, | Before test: | - | NFC_RDR_STEPUP_AD_ISSUER_CERT, +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | NFC_RDR_STEPUP_AD_ISSUER_CERT_KEY_ID, | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | NFC_RDR_STEPUP_AD_KEY_ID, | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | NFC_RDR_STEPUP_AD_UNKNOWN_NON_ACCESS_EXTENSION, | * dl install group_id 37652039312061652031642033642065 | - | NFC_RDR_STEPUP_AD_UNKNOWN_NON_CRITICAL_ACCESS_EXTENSION | | - +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - - .. tab:: BLEUWB Reader - - +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | Test case(s) | Door Lock commands to execute | - +=================================+====================================================================================================================================================================+ - | ALL TESTS | Before test session: | - | | | - | | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | - | | * dl install group_id 37652039312061652031642033642065 | - +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | BLEUWB_RDR_EXPEDITED_FAST_PHASE | After test: | - | | | - | | * dl kpersistent clear 0 | - +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | BLEUWB_RDR_STEPUP_PHASE | Before test: | - | | | - | | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | - | | | - | | After test: | - | | | - | | * dl install group_id 37652039312061652031642033642065 | - | | | - +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - - -Additional CLI commands -======================= - -* To check the revision of the Aliro library on your device, run the following command in the device shell: - -.. code-block:: console - - uart:~$ dl info - Aliro version: v0.2.0-22-g7da4b2e - NFC reader: ST25R100 - -* To check the QM35825 SoC firmware version, run the following command in the device shell: - -.. code-block:: console - - uart:~$ uwb qm35_fw_version - 13.1.0rc5_lu - -.. note:: - This command is available only if you build the application with the ``uwb_qm35`` snippet. - -* To list all Kpersistent keys stored in the device, run the following command in the device shell: - -.. code-block:: console - - uart:~$ dl kpersistent list - Number of Kpersistent keys: 1 - Index ID Public Key - -------------------------------- - 0 0x00041000 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa - -* To clear a specific Kpersistent key by its index, run the following command in the device shell: - -.. code-block:: console - - uart:~$ dl kpersistent clear - - For example: - -.. code-block:: console - - uart:~$ dl kpersistent clear 0 - -* To clear all Kpersistent keys stored in the device, run the following command in the device shell: - -.. code-block:: console - - uart:~$ dl kpersistent clear all - -.. note:: - These commands are available only if you build the application with the Expedited-fast phase support enabled (the ``CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE`` Kconfig option). - -Bluetooth LE Nordic UART Service (NUS) -*************************************** - -The Nordic UART Service (NUS) is a Bluetooth LE service that allows for remote control of the door lock using predefined commands. -This feature demonstrates the use of an out-of-band access control mechanism, unrelated to the Aliro and Matter protocols. - -Enabling the NUS feature -======================== - -.. tabs:: - - .. group-tab:: With Matter disabled (Aliro only) - - Build the application with the ``bt_nus`` snippet. - For example, if you are using the nRF5340 DK, run the following command: - - .. code-block:: console - - west build -p -b nrf5340dk/nrf5340/cpuapp app -- -Dapp_SNIPPET=bt_nus - - .. group-tab:: With Matter enabled (Aliro and Matter) - - Build the application with the ``matter`` snippet and enable the ``CONFIG_CHIP_NUS`` Kconfig option. - For example, if you are using the nRF5340 DK, run the following command: - - .. code-block:: console - - west build -p -b nrf5340dk/nrf5340/cpuapp app -- -DSNIPPET=matter -DCONFIG_CHIP_NUS=y - -Using the Bluetooth LE NUS service -================================== - -.. tabs:: - - .. group-tab:: With Matter disabled (Aliro only) - - The application registers an ``Unlock`` command that can be sent from the `nRF Toolbox mobile application`_. - You can register additional custom commands for the NUS service by using the ``RegisterCommand`` method from the ``NUSService`` class in application code. - - 1. **Pairing**: Connect to the device advertised as `AliroDL` using passkey: ``123456``. - 2. **Send Command**: Send "Unlock" command using NUS RX characteristic. - 3. **Result**: You will see the following output in the device serial console: - - .. code-block:: console - - door_lock_app: Unlock command received - - Additionally, if you enable the ``CONFIG_ACCESS_DECISION_INDICATOR`` Kconfig option, the access decision indicator (green **LED 1**) lights up for a time specified by the ``CONFIG_RESET_ACCESS_DECISION_INDICATOR_STATE_DELAY_MS`` Kconfig option. - - .. group-tab:: With Matter enabled (Aliro and Matter) - - Follow the detailed instructions on how to use NUS service with Matter provided in the `Matter door lock NUS`_ page. +.. toctree:: + :maxdepth: 1 + :glob: + :caption: Subpages: + + testing/setting_up_test_harness + testing/cli_provisioning + testing/testing_certificate_reader + testing/provisioning_with_matter + testing/verification_and_testing + testing/cli_reference + testing/nus diff --git a/docs/testing/cli_provisioning.rst b/docs/testing/cli_provisioning.rst new file mode 100644 index 00000000..b6679120 --- /dev/null +++ b/docs/testing/cli_provisioning.rst @@ -0,0 +1,180 @@ +.. _testing_provisioning_cli: + +Provisioning with CLI +##################### + +.. contents:: + :local: + :depth: 2 + +The following page describes the available methods for provisioning Aliro credentials on the door lock device using the command-line interface. +Once you have :ref:`Set up the Aliro Test Harness `, complete the following steps: + +#. Generate a Reader key pair: + + .. code-block:: console + + cd scripts + python3 generate_keypair.py --verbose + + Save both values: + + * ``READER_PRIV`` - Reader private signing key (32 bytes, hex). + * ``READER_PUB`` - Reader public key (65 bytes, hex). + +#. Set the ``dut_reader_public_key`` field in the Test Harness project configuration to ``READER_PUB``. + +#. Provision the Reader private signing key (``READER_PRIV``) in the DUT using the following ``dl provisioning`` shell command: + + .. code-block:: console + + uart:~$ dl provisioning reader_prv set <32-byte private key in hex without 0x> + + .. note:: + + Once the required credentials are provisioned (Reader identifier, Reader private signing key, and at least one Access Credential public key), the device will automatically provision and start the Aliro stack. + +#. Install the Reader identifier in the Reader device. + + .. tabs:: + + .. tab:: Full Reader identifier + + Install the complete 32-byte Reader identifier using a single command. + + .. code-block:: console + + dl install identifier <32-byte_reader_identifier_in_hex_without_0x> + + Example: + + .. code-block:: console + + dl install identifier 00113344667799AA00113344667799AA113344667799AA00113344667799AA00 + + .. tab:: Group and sub-identifier + + Install the Reader group identifier and Reader group sub-identifier separately. + Each value is 16 bytes. + + .. code-block:: console + + dl install group_id <16-byte_reader_group_identifier_in_hex_without_0x> + dl install group_sub_id <16-byte_reader_group_sub_identifier_in_hex_without_0x> + + Example: + + .. code-block:: console + + dl install group_id 00113344667799AA00113344667799AA + dl install group_sub_id 113344667799AA00113344667799AA00 + + Running the same commands without specifying a value returns the currently stored identifier. + + Example: + + .. code-block:: console + + dl install group_id + + Example output: + + .. code-block:: console + + 00113344667799AA00113344667799AA + + .. tab:: Test harness configuration + + Instead of installing the identifier directly on the device, you can configure the Reader group identifier in the test harness project configuration. + + Set the value next to the ``dut_reader_group_identifier`` field. + +#. Set the ``th_access_credential_public_key`` in the DUT using the following ``dl provisioning`` shell command: + + .. code-block:: console + + uart:~$ dl provisioning AC_key set <65-byte public key in hex without 0x> + + For example: + + .. code-block:: console + + uart:~$ dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa + + .. note:: + + You can also use the following commands: + + * ``uart:~$ dl provisioning AC_key clear `` - This command clears the public key stored in the DUT by its ID (for example, uart:~$ dl provisioning AC_key clear 0). + * ``uart:~$ dl provisioning AC_key clear all`` - This command clears all public keys stored in the DUT. + * ``uart:~$ dl provisioning AC_key list`` - This command lists all public keys stored in the DUT. + + For example: + + .. code-block:: console + + uart:~$ dl provisioning AC_key list + [0]: 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efb + [1]: (null) + [2]: (null) + [3]: (null) + [4]: (null) + [5]: (null) + [6]: (null) + [7]: (null) + [8]: (null) + [9]: (null) + +#. Optionally, set the ``dut_credential_issuer_public_key`` in the DUT using the following ``dl provisioning`` shell command: + + .. code-block:: console + + uart:~$ dl provisioning CI_key set <65-byte public key in hex without 0x> + + + For example: + + .. code-block:: console + + uart:~$ dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 + + .. note:: + + You can also use the following commands: + + * ``uart:~$ dl provisioning CI_key clear `` - This command clears the public key stored in the DUT by its ID (for example, ``uart:~$ dl provisioning CI_key clear 0``). + * ``uart:~$ dl provisioning CI_key clear all`` - This command clears all public keys stored in the DUT. + * ``uart:~$ dl provisioning CI_key list`` - This command lists all public keys stored in the DUT. + + For example: + + .. code-block:: console + + uart:~$ dl provisioning CI_key list + [0]: 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 + +#. Optionally, set the Credential Issuer Certificate Authority (CA) public key in the DUT using the following ``dl provisioning`` shell command: + + .. code-block:: console + + uart:~$ dl provisioning CI_CA_key set <65-byte public key in hex without 0x> + + For example: + + .. code-block:: console + + uart:~$ dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 + + .. note:: + + You can also use the following commands: + + * ``uart:~$ dl provisioning CI_CA_key get`` - This command retrieves the Credential Issuer CA public key stored in the DUT. + * ``uart:~$ dl provisioning CI_CA_key clear`` - This command clears the Credential Issuer CA public key stored in the DUT. + + For example: + + .. code-block:: console + + uart:~$ dl provisioning CI_CA_key get + 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 diff --git a/docs/testing/cli_reference.rst b/docs/testing/cli_reference.rst new file mode 100644 index 00000000..71938123 --- /dev/null +++ b/docs/testing/cli_reference.rst @@ -0,0 +1,56 @@ +.. _testing_cli_ref: + +Command-line reference +###################### + +.. contents:: + :local: + :depth: 2 + +This page lists commonly used command-line commands for inspecting device state, checking firmware versions, and managing stored keys on the door lock device. + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - Command + - Description + * - ``dl info`` + - Displays the Aliro library revision and the NFC reader in use. + + Example output:: + + Aliro version: v0.2.0-22-g7da4b2e + NFC reader: ST25R100 + + * - ``dl btaddr`` + - Displays the Bluetooth LE address of the device. + + * - ``uwb qm35_fw_version`` + - Displays the firmware version of the QM35825 SoC. + + This command is available only when the application is built with the ``uwb_qm35`` snippet. + * - ``dl kpersistent list`` + - Lists all Kpersistent keys currently stored on the device, including their index, ID, and public key. + * - ``dl kpersistent clear `` + - Clears a specific Kpersistent key by its index. + + Example:: + + dl kpersistent clear 0 + + * - ``dl provisioning`` + - Sets or gets provisioning credentials required by the Aliro protocol for authentication. + + * - ``dl install`` + - Sets or gets reader identifiers (group identifier and group sub-identifier). + + * - ``dl kpersistent clear all`` + - Clears all Kpersistent keys stored on the device. + + * - ``dl factory_reset`` + - Performs a factory reset on the device, clearing all stored data and settings. + +.. note:: + The ``dl kpersistent`` commands are available only if the application is built with the + ``CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE`` Kconfig option enabled. diff --git a/docs/testing/nus.rst b/docs/testing/nus.rst new file mode 100644 index 00000000..c0ea8d4c --- /dev/null +++ b/docs/testing/nus.rst @@ -0,0 +1,62 @@ +.. _testing_ble_nordic_uart: + +Bluetooth LE Nordic UART Service (NUS) +###################################### + +.. contents:: + :local: + :depth: 2 + +The Nordic UART Service (NUS) is a Bluetooth LE service that allows for remote control of the door lock using predefined commands. +This feature demonstrates the use of an out-of-band access control mechanism, unrelated to the Aliro and Matter protocols. + +Enabling the NUS feature +************************ + +Enable the NUS feature depending on your configuration: + +.. tabs:: + + .. group-tab:: With Matter disabled (Aliro only) + + Build the application with the ``bt_nus`` snippet. + For example, if you are using the nRF5340 DK, run the following command: + + .. code-block:: console + + west build -p -b nrf5340dk/nrf5340/cpuapp app -- -Dapp_SNIPPET=bt_nus + + .. group-tab:: With Matter enabled (Aliro and Matter) + + Build the application with the ``matter`` snippet and enable the ``CONFIG_CHIP_NUS`` Kconfig option. + For example, if you are using the nRF5340 DK, run the following command: + + .. code-block:: console + + west build -p -b nrf5340dk/nrf5340/cpuapp app -- -DSNIPPET=matter -DCONFIG_CHIP_NUS=y + +Using the Bluetooth LE NUS service +********************************** + +You can use the service in the following ways, depending on your configuration: + +.. tabs:: + + .. group-tab:: With Matter disabled (Aliro only) + + The application registers ``Lock`` and ``Unlock`` commands that can be sent from the `nRF Toolbox mobile application`_. + You can register additional custom commands for the NUS service by using the ``RegisterCommand`` method from the ``NUSService`` class in application code. + + 1. Pairing - Connect to the device advertised as `AliroDL` using passkey: ``123456``. + 2. Send Command - Send "Unlock" command using NUS RX characteristic. + 3. Result - You will see the following output in the device serial console: + + .. code-block:: console + + door_lock_app: Unlock command received + + Additionally, if you enable the ``CONFIG_DOOR_LOCK_LOCK_SIM_INDICATOR`` Kconfig option, the lock simulator indicator (green **LED 2**) lights up when the lock is unlocked. + + .. group-tab:: With Matter enabled (Aliro and Matter) + + Follow the detailed instructions on how to use NUS service with Matter provided in the `Matter door lock NUS`_ page. diff --git a/docs/testing/provisioning_with_matter.rst b/docs/testing/provisioning_with_matter.rst new file mode 100644 index 00000000..759df940 --- /dev/null +++ b/docs/testing/provisioning_with_matter.rst @@ -0,0 +1,608 @@ +.. _testing_door_lock_provisioning_with_matter: +.. _testing_verification: + +Aliro door lock provisioning with Matter +######################################## + +.. contents:: + :local: + :depth: 2 + +The following page covers two different approaches for testing the Aliro door lock with Matter: + +* :ref:`testing_with_chip_tool` - Using the CHIP Tool command-line interface for commissioning and credential provisioning (intended for development and testing). +* :ref:`testing_with_apple_ecosystem` - Using Apple Home app and Apple Wallet for real-world user experience testing with iOS devices. + +If you are building an application with Matter support (see :ref:`building_and_running` and ``-DSNIPPET='matter'`` option), you can provision Aliro credentials from a Matter controller. +First, ensure that all the necessary software tools and hardware are configured (see :ref:`testing_environment`). + +.. _matter_ui: + +Matter door lock user interface +******************************* + +To test the device, familiarize yourself with the user interface of the |APP_NAME| when Matter is enabled. + +.. note:: + The button and LED numbering differs between development kits. + On the nRF52840 and nRF5340 boards, the numbering starts from 1. + On the nRF54L15 and nRF54LM20 boards, it starts from 0. + +LED 1: + .. include:: /include/matter_state_led.txt + +LED 2: + .. include:: /include/matter_signalling_led.txt + +Button 1: + .. include:: /include/matter_button.txt + +Button 2: + * Changes the lock state to the opposite one. + +.. _testing_with_chip_tool: + +Testing with Matter CHIP Tool +***************************** + +This guide uses CHIP Tool as a Matter controller to commission the door lock and provision Aliro credentials through command-line interface. +For details, see the `Matter chip-tool guide`_. + +.. note:: + This approach is recommended for development, testing, and debugging purposes. + +#. Set up the `Thread Border Router`_ with the Nordic Thread coprocessor. + + a. Configure the `Thread radio co-processor`_. + + #. Run the OpenThread Border Router and `form a Thread network using Docker `_. + + #. Obtain the Thread operational dataset for commissioning devices into created Thread network by running the following Docker command: + + .. code-block:: console + + sudo docker exec -it otbr sh -c "sudo ot-ctl dataset active -x" + + Save this operational dataset as it will be needed for commissioning the door lock device. + +#. Commission the Aliro door lock to Matter over Thread network. + + a. Put the door lock device in commissioning mode by pressing the appropriate button on the development kit. + Refer to the :ref:`matter_ui` section for more details. + + #. Use CHIP Tool to commission the device with Thread operational dataset obtained: + + .. code-block:: console + + ./chip-tool pairing ble-thread hex: + + * ```` - A unique node ID for the device (for example, ``1``) + * ```` - The obtained Thread operational dataset + * ```` - Device pairing code (default: ``30033003``) + * ```` - Device discriminator (default: ``3003``) + + For example: + + .. code-block:: console + + ./chip-tool pairing ble-thread 1 hex:0e080000000000010000000300001335060004001fffe002084c70c4ba47c6a0780708fd12345678901234051000112233445566778899aabbccddeeff030e4f70656e5468726561642048423030010212340410445f2b5ca6f2a93a55ce570a70efeecb0c0402a0f7f8 30033003 3003 + + #. Verify successful commissioning by reading basic device information: + + .. code-block:: console + + ./chip-tool basicinformation read vendor-name 1 0 + + You should see the vendor information of the commissioned door lock device. + +#. Configure the Aliro Reader based on the Test Harness configuration. + + a. Generate an ECC key pair for the Reader by running the following commands: + + .. code-block:: console + + KEY_OUTPUT=$(openssl ecparam -name prime256v1 -genkey | openssl ec -text -noout) && \ + echo "Private Key:" && \ + echo "$KEY_OUTPUT" | sed -n '/priv:/,/pub:/p' | grep -v 'priv:\|pub:' | tr -d ' \n:' && \ + echo -e "\nPublic Key:" && \ + echo "$KEY_OUTPUT" | sed -n '/pub:/,/ASN1/p' | grep -v 'pub:\|ASN1' | tr -d ' \n:' + + See an example output: + + .. code-block:: console + + Private Key: + 9df123f58dd15f6bab71bb6635827faf25100b043cdf6b62c93ea3c244ad4403 + Public Key: + 043c05b91fc09a84ad2ab7940a1b84f09b8ddf5323f1aac0f6568e1c973f37275dc67500a9df08d1bd69ee04e8641d9cbc73a4c1be30eed64def414f8afdc44642 + + #. Follow the :ref:`setting_up_the_aliro_test_harness` section to locate the necessary credentials in the Test Harness project configuration. + + #. Configure the Test Harness project: + + * Set ``dut_reader_public_key`` to the generated ``Public Key``. + * Note down the values of ``th_access_credential_public_key`` and ``dut_reader_group_identifier``. + You will need them for further configuration. + + #. Set the Aliro Reader configuration remotely with CHIP Tool using the following command: + + .. code-block:: console + + ./chip-tool doorlock set-aliro-reader-config hex: hex: hex: --GroupResolvingKey hex: + + * ```` - Signing key of the Reader device (generated ``Private Key``) + * ```` - Verification key of the Reader device (generated ``Public Key``) + * ```` - Group identifier of the Reader device (corresponds to ``dut_reader_group_identifier``) + * ```` - Unique node ID for the device (for example, ``1``) + * ```` - Endpoint ID of the Reader device (for example, ``1`` for the door lock cluster) + * ```` - Group resolving key of the Reader device. + This option corresponds to ``dut_reader_group_sub_identifier``, which can be set in the Test Harness project configuration. + + See the following example: + + .. code-block:: console + + ./chip-tool doorlock set-aliro-reader-config hex:9df123f58dd15f6bab71bb6635827faf25100b043cdf6b62c93ea3c244ad4403 hex:043c05b91fc09a84ad2ab7940a1b84f09b8ddf5323f1aac0f6568e1c973f37275dc67500a9df08d1bd69ee04e8641d9cbc73a4c1be30eed64def414f8afdc44642 hex:00113344667799AA00113344667799AA 1 1 --GroupResolvingKey hex:00000000000000000000000000000000 --timedInteractionTimeoutMs 5000 + + .. note:: + The ``GroupResolvingKey`` can only be set if the Aliro door lock application is built with Bluetooth LE transport and Ultra-Wideband (UWB) support. + Currently, the Aliro Test Harness expects the ``GroupResolvingKey`` to be set to all zeros. + +#. Add door lock user and ``AliroEvictableEndpointKey`` credentials using CHIP Tool. + + a. Add the first user (Home user): + + .. code-block:: console + + ./chip-tool doorlock set-user 0 1 Home 0 1 0 0 1 1 --timedInteractionTimeoutMs 5000 + + * OperationType (0 = Add) - ``0`` + * UserIndex - ``1`` + * UserName - ``Home`` + * UserUniqueID - ``0`` + * UserStatus (1 = enabled) - ``1`` + * UserType (0 = unrestricted) - ``0`` + * CredentialRule (0 = single) - ``0`` + * Node ID of the door lock - ``1`` + * Endpoint ID - ``1`` + + #. Set the first ``AliroEvictableEndpointKey`` credential for the Home user: + + .. code-block:: console + + ./chip-tool doorlock set-credential 0 '{"credentialType": 7, "credentialIndex": 1}' hex: 1 null null 1 1 --timedInteractionTimeoutMs 5000 + + * OperationType (0 = Add) - ``0`` + * credentialType (7 = AliroEvictableEndpointKey) - ``7`` + * credentialIndex - ``1`` + * ```` - An octet string parameter with the Access Credential public key data. + It corresponds to ``th_access_credential_public_key``. + * UserIndex - ``1`` + * UserStatus - ``null`` + * UserType - ``null`` + * Node ID of the door lock - ``1`` + * Endpoint ID - ``1`` + + See the following example: + + .. code-block:: console + + ./chip-tool doorlock set-credential 0 '{"credentialType": 7, "credentialIndex": 1}' hex:04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa 1 null null 1 1 --timedInteractionTimeoutMs 5000 + + #. Read the status of credential assigned to the user to verify that it was set correctly: + + .. code-block:: console + + ./chip-tool doorlock get-credential-status {"credentialType": 7, "credentialIndex": 1} 1 1 + +#. Add Aliro Credential Issuer key using CHIP Tool. + Once you have a user created on the door lock, you can add the Credential Issuer public key to the Reader using the following command: + + .. code-block:: console + + ./chip-tool doorlock set-credential 0 '{"credentialType": 6, "credentialIndex": 1}' hex: 1 null null 1 1 --timedInteractionTimeoutMs 5000 + + * OperationType (0 = Add) - ``0`` + * credentialType (6 = AliroCredentialIssuerKey) - ``6`` + * credentialIndex - ``1`` + * ```` - An octet string parameter with the Credential Issuer public key data. + It corresponds to ``dut_credential_issuer_public_key``. + * UserIndex - ``1`` + * UserStatus - ``null`` + * UserType - ``null`` + * Node ID of the door lock - ``1`` + * Endpoint ID - ``1`` + + See the following example: + + .. code-block:: console + + ./chip-tool doorlock set-credential 0 '{"credentialType": 6, "credentialIndex": 1}' hex:047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 1 null null 1 1 --timedInteractionTimeoutMs 5000 + +.. _testing_with_apple_ecosystem: + +Testing with Apple ecosystem +**************************** + +.. note:: + Support for testing with Apple ecosystem is `experimental `_ and is not optimized with respect to performance and power consumption. + This feature is intended for development and testing purposes only. + +This guide demonstrates how to test the Aliro door lock with Matter support using Apple's Home App and Wallet on iPhone. +For additional information about Matter testing with various ecosystems, see the `Matter testing with Apple, Google, and Samsung ecosystems`_ guide. + +When testing |APP_NAME| with Apple ecosystem, you can use either: + +* NFC - Tap to unlock using NFC technology +* NFC + Bluetooth LE + UWB - Unlock on approach using Bluetooth LE + UWB ranging (in addition to NFC) + +Both configurations are supported and can be set up during the commissioning process. + +.. note:: + Testing with Apple ecosystem requires both Step-up phase and Expedited-fast phase support to be enabled on the device. + Both ``CONFIG_DOOR_LOCK_STEP_UP_PHASE`` and ``CONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE`` Kconfig options are enabled by default when building with Matter support. + +Prerequisites +============= + +Before you begin testing with Apple ecosystem, ensure you have the following: + +* iPhone with iOS 26 or later - It is required for Aliro-related features in the Apple Home and Wallet apps. +* Apple Home Hub - A HomePod Mini (recommended) or an Apple TV 4K, which acts as both a Matter controller and Thread Border Router. + You can test the application with iPhone only, however, having an Apple Home Hub is recommended for best experience. +* Apple Home App - Pre-installed on iOS devices. +* Apple Wallet - Pre-installed on iOS devices. +* nRF Door Lock App build with Matter support - A development kit from Nordic Semiconductor equipped with Aliro door lock firmware :ref:`that supports Matter `. + +Building the firmware +===================== + +Build the firmware with the appropriate configuration for your testing scenario, for example: + +* For NFC testing, run: + + .. code-block:: console + + west build -b nrf54lm20dk/nrf54lm20a/cpuapp -- -DSNIPPET='matter' + +* For NFC + Bluetooth LE + UWB testing, run: + + .. code-block:: console + + west build -b nrf54lm20dk/nrf54lm20a/cpuapp -- -DSNIPPET='matter' -Dapp_SNIPPET='uwb_qm35' + +.. note:: + Before commissioning the door lock, ensure that the Apple Home Hub is set up and added to your Apple Home. + It will act as the Thread Border Router and Matter controller for your home network. + +Commissioning the door lock to Apple Home +========================================= + +The commissioning process allows you to add the door lock accessory into your Apple Home ecosystem using Matter over Thread. +Complete the following steps to commission the device: + +1. Prepare the door lock device: + + .. tabs:: + + .. tab:: NFC + + a. Flash the Aliro door lock firmware with Matter support enabled to your Nordic development kit (see :ref:`building_and_running` with the ``-DSNIPPET='matter'`` option). + #. Connect the required hardware (:ref:`NFC reader expansion board`). + #. Power on the device and connect to the serial console to monitor the commissioning process. + #. Start the commissioning mode on the device. + Refer to the :ref:`matter_ui` section for detailed button assignments and LED indicators. + + .. tab:: NFC + Bluetooth LE + UWB + + a. Flash the Aliro door lock firmware with Matter and UWB support enabled to your Nordic development kit (see :ref:`building_and_running` with the ``-DSNIPPET='matter'`` and ``-Dapp_SNIPPET='uwb_qm35'`` options). + #. Connect the required hardware: + + * :ref:`NFC reader expansion board` (required for NFC unlock) + * :ref:`UWB module` (required for UWB ranging) + + #. Power on the device and connect to the serial console to monitor the commissioning process. + #. Start the commissioning mode on the device. + Refer to the :ref:`matter_ui` section for detailed button assignments and LED indicators. + +#. Initiate Matter pairing in the Apple Home app: + + a. Open the Home App on your iPhone. + #. Ensure your iPhone is connected to the same Wi-Fi network as your Apple Home Hub. + #. Tap the :guilabel:`+` button in the top-right corner of the Home app. + #. Select :guilabel:`Add Accessory` or :guilabel:`Add Device`. + +#. Scan the Matter QR code displayed under the link that appears in the device's serial console after it boots up. + The Home app will activate the camera for QR code scanning. + + .. note:: + During testing, you might see an Uncertified Accessory warning, indicating the device has not yet been certified by the Connectivity Standards Alliance for Matter compatibility. + This behavior is expected for development devices. + If this occurs, tap :guilabel:`Add Anyway` to proceed with commissioning. + + .. figure:: /images/apple_uncertified_accessory.png + :alt: Uncertified Accessory warning in Apple Home app + :scale: 30% + + Uncertified Accessory warning during commissioning + +#. Complete the accessory setup: + + a. Wait while the Home App establishes a connection and commissions the device. + The status message will show ``Setting Up...``. + #. When prompted, assign a location or room for the lock (for example, "Entrance", "Front Door", or "Garage"). + + .. figure:: /images/apple_lock_location_setup.png + :alt: Lock location selection in Apple Home app + :scale: 30% + + Selecting the lock location during setup + + #. Provide a name for the lock (for example, "Aliro lock" or "Front Door Lock"). + #. Review the suggested settings and tap :guilabel:`Continue` or :guilabel:`Done`. + +#. Enable Home Key functionality. + After successful commissioning, the Home app will automatically prompt you to set up the Home Key feature for Apple Wallet: + + .. tabs:: + + .. tab:: NFC + + a. Wait for the :guilabel:`Tap to Unlock` setup screen with options for unlock methods. + + .. figure:: /images/apple_tap_to_unlock_setup.png + :alt: Tap to Unlock setup screen in Apple Home app + :scale: 30% + + Tap to Unlock configuration screen + + #. Tap :guilabel:`Turn On Tap to Unlock` to enable NFC-based unlocking using your iPhone. + #. Choose your preferred authentication methods: + + * :guilabel:`Require Face ID or Passcode` - Adds an additional security layer requiring biometric or PIN authentication before unlocking. + * :guilabel:`Access Codes` - Adds an additional security layer requiring a PIN code to unlock the door lock remotely (through the Matter network). + + #. Tap :guilabel:`Done` to complete the Home Key provisioning. + + .. tab:: NFC + Bluetooth LE + UWB + + a. Wait for the :guilabel:`Express Mode` setup screen with options for unlock methods. + + .. figure:: /images/apple_express_mode_setup.png + :alt: Express Mode setup screen in Apple Home app + :scale: 30% + + Express Mode configuration screen + + #. Tap :guilabel:`Turn On Express Mode` to enable both NFC-based tap to unlock and Bluetooth LE + UWB-based unlock on approach using your iPhone. + #. Accept the prompt to enable proximity-based unlocking during commissioning. + #. Choose your preferred authentication methods: + + * :guilabel:`Require Face ID or Passcode` - Adds an additional security layer requiring biometric or PIN authentication before unlocking. + * :guilabel:`Access Codes` - Adds an additional security layer requiring a PIN code to unlock the door lock remotely (through the Matter network). + + #. Tap :guilabel:`Done` to complete the Home Key provisioning. + +#. Verify successful commissioning: + + a. Wait for the :guilabel:`Lock Added to Home` confirmation pop-up with a message indicating that you and all home members can now unlock the door. + + .. figure:: /images/apple_lock_added_confirmation.png + :alt: Lock Added to Home confirmation + :scale: 30% + + Confirmation that the lock has been successfully added + + The lock should now appear in your Home app within the assigned room, displaying its current state (:guilabel:`Locked` or :guilabel:`Unlocked`). + + .. figure:: /images/apple_home_app_lock_view.png + :alt: Aliro lock in Apple Home app + :scale: 30% + + Aliro lock displayed in the Home app + + #. Ensure that a new home card appears in your Apple Wallet app containing the digital key for the lock. + + .. figure:: /images/apple_wallet_hold_near_lock.png + :alt: Apple Wallet Hold Near Lock instruction + :scale: 30% + + Apple Wallet containing the newly added Home key + +Using the home key for access +============================= + +Once the lock is successfully commissioned to Apple Home, a virtual home key containing Aliro credentials is automatically provisioned and added to your Apple Wallet. + +Accessing the home key in Apple Wallet +-------------------------------------- + +To use your digital home key from Apple Wallet, complete the following: + +#. Open the Wallet app on your iPhone. +#. Locate the new home card displaying a house icon. + This card represents your digital home key and contains the Aliro cryptographic credentials required for secure authentication with the door lock. + +Unlocking the door +------------------ + +.. tabs:: + + .. tab:: NFC + + To unlock the door using your iPhone with NFC, complete the following steps: + + #. Approach the door lock with your iPhone, ensuring the Apple Wallet app is open and the Home Key card is active. + Depending on the security settings you chose during the initial setup, you may be prompted for biometric authentication (Face ID or Touch ID) or passcode entry to make the card active. + + #. Hold your device near the NFC reader antenna on the door lock. + This is a part of the STM Nucleo NFC reader expansion board attached to the development kit. + + Authentication behavior depends on your chosen security setting: + + * Face ID or Passcode required - Your device will ask for biometric authentication (Face ID or Touch ID) or passcode entry before unlocking. + + #. Wait for confirmation: + + * The Apple Wallet will display the following message: + + .. figure:: /images/apple_home_key_success.png + :alt: Home key successfully added to Apple Wallet + :scale: 30% + + Apple Wallet displaying confirmation after successful unlock with Home key + + * The device serial console outputs an ``ACCESS GRANTED`` log message. + * The lock status in the Home app updates to :guilabel:`Unlocked`. + + .. tab:: NFC + Bluetooth LE + UWB + + To unlock the door using your iPhone with NFC + Bluetooth LE + UWB, you can use either method: + + Using NFC (tap to unlock): + + #. Approach the door lock with your iPhone, ensuring the Apple Wallet app is open and the Home Key card is active. + Depending on the security settings you chose during the initial setup, you may be prompted for biometric authentication (Face ID or Touch ID) or passcode entry to make the card active. + + #. Hold your device near the NFC reader antenna on the door lock. + This is a part of the STM Nucleo NFC reader expansion board attached to the development kit. + + #. Wait for confirmation - The Apple Wallet will display "Done" message, the device serial console outputs an ``ACCESS GRANTED`` log message, and the lock status in the Home app updates to :guilabel:`Unlocked`. + + Using Bluetooth LE + UWB (unlock on approach): + + #. Ensure your iPhone has Bluetooth enabled and is within range of the door lock. + The door will automatically unlock when your iPhone is within the configured distance threshold. + + #. Approach the door lock with your iPhone. + The device will automatically detect your proximity using UWB ranging. + + #. Wait for automatic unlock: + + * The door will unlock when your iPhone is within ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM`` (default: 100 cm). + * The device serial console outputs an ``ACCESS GRANTED`` log message. + * The lock status in the Home app updates to :guilabel:`Unlocked`. + + #. To lock the door again: + + * Move your iPhone beyond ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM + CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_EXIT_MARGIN_CM`` (default: 100 cm + 50 cm = 150 cm). + * The door will automatically lock when all active UWB sessions are out of range. + * The device serial console outputs a lock confirmation message. + * The lock status in the Home app updates to :guilabel:`Locked`. + + .. note:: + The exit margin prevents rapid toggling when the distance fluctuates around the threshold. + This ensures stable lock and unlock behavior. + + The behavior of the door lock can be customized and depends on various factors, including the behavior after unlocking. + In the example configuration with ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_TERMINATE_SESSION_DISABLED``, the UWB session is terminated by the User Device (iPhone) after a certain period of time. + The User Device resumes the session either when the Door Lock changes state from Non-secured to Secured (Unlocked => Locked), which is detected by the iPhone over Bluetooth LE, or through motion detection on the User Device side. + +Troubleshooting +=============== + +If you encounter issues during testing with the Apple ecosystem, refer to the following troubleshooting guidance. + +Commissioning issues +-------------------- + +If you are facing difficulties during the commissioning process, consider the following steps to diagnose and resolve the issue: + +* Device is not discovered by the Home app: + + * Verify if the device is in active commissioning mode (LED indicators should show commissioning state as described in the :ref:`matter_ui`). + * Ensure Bluetooth is enabled on your iPhone, and that Home app has permission to access location services. + * Confirm your iPhone is running iOS 26 or later. + * Ensure that the HomePod mini is powered on, connected to Wi-Fi, and showing as online in the Home app. + * Attempt to power cycle both the door lock device and the HomePod mini. + +* Receiving an ``Uncertified Accessory`` warning: + + * This is expected behavior for development devices that are not yet certified by the Connectivity Standards Alliance (CSA). + * Tap :guilabel:`Add Anyway` to proceed with commissioning. + * Once the product is certified, this warning will no longer appear. + +* Commissioning fails or times out: + + * Ensure the HomePod mini is on the same network as your iPhone. + * Check the device serial console for error messages indicating commissioning failures. + * Ensure the firmware was built with Matter support enabled (the ``-DSNIPPET='matter'`` option). + * Ensure the Step-up phase support is enabled (the ``CONFIG_DOOR_LOCK_STEP_UP_PHASE`` Kconfig option). + * Try performing a factory reset of the device and restarting the commissioning process. + * Ensure the Thread network is functioning correctly on the HomePod mini. + +Home Key and Wallet issues +-------------------------- + +Problems with the Home Key appearing in Apple Wallet or other related issues can often be resolved by checking a few key settings and configurations: + +* Home key does not appear in the Apple Wallet: + + .. tabs:: + + .. tab:: NFC + + * Ensure you have completed the :guilabel:`Tap to Unlock` setup during the initial commissioning process + * If you skipped this step, you can enable it later by opening the Home app, tapping the lock, going to the settings icon, and clicking :guilabel:`Tap to Unlock`. + * Ensure your iPhone has Apple Wallet enabled and is signed in with your Apple ID. + * Check if iCloud Keychain is enabled by going to iPhone :guilabel:`Settings` → :guilabel:`[Your Name]` → :guilabel:`iCloud` → :guilabel:`Passwords and Keychain`. + + .. tab:: NFC + Bluetooth LE + UWB + + * Ensure you have completed the :guilabel:`Express Mode` setup during the initial commissioning process + * If you skipped this step, you can enable it later by opening the Home app, tapping the lock, going to the settings icon, and clicking :guilabel:`Express Mode`. + * Ensure your iPhone has Apple Wallet enabled and is signed in with your Apple ID. + * Check if iCloud Keychain is enabled by going to iPhone :guilabel:`Settings` → :guilabel:`[Your Name]` → :guilabel:`iCloud` → :guilabel:`Passwords and Keychain`. + +* The *Hold Near Lock* message appears but nothing happens (NFC unlock): + + * Verify if the NFC reader hardware is properly connected to the development kit (check physical connections). + * Ensure the firmware includes the NFC transport support by checking the build configuration. + * Adjust the position of your iPhone relative to the NFC reader antenna to improve connectivity. + * Check the device serial console for NFC reader initialization messages and any error logs. + +* The door does not unlock automatically when approaching (Bluetooth LE + UWB): + + * Verify if the UWB module is properly connected to the development kit (check physical connections). + * Ensure the firmware includes UWB support by checking the build configuration (``-Dapp_SNIPPET='uwb_qm35'``). + * Ensure your iPhone is within ``CONFIG_DOOR_LOCK_ACCESS_MANAGER_MAX_ALLOWED_DISTANCE_CM`` (default: 100 cm) of the door lock. + * Check the device serial console for UWB ranging messages and distance measurements. + * Verify that Bluetooth is enabled on your iPhone and that the Home app has permission to access location services. + +* Face ID or passcode prompt does not appear when expected: + + * Review the home key security settings by going to Wallet app → :guilabel:`Home card` → :guilabel:`ⓘ` (info) → :guilabel:`Express Mode settings`. + * If you wish to use the :guilabel:`Require Face ID or Passcode` option, ensure it is enabled in the settings. + +For additional troubleshooting, guidance, and tips for various ecosystems, see the `Matter testing with Apple, Google, and Samsung ecosystems`_ guide. + +Aliro and Matter lock state synchronization +******************************************* + +When the door lock is unlocked through Aliro, the lock state automatically synchronizes with the Matter door-lock server cluster. +This synchronization ensures that User Devices in the Matter network are notified of lock state changes in real time. + +1. To verify the current lock state, run the following command: + + .. code-block:: console + + ./chip-tool doorlock read lock-state 1 1 + +#. After executing the command, find the lock state in the response, for example: + + .. code-block:: console + + [1757071349.943] [949263:949265] [TOO] LockState: 1 + + The response indicates that the lock is locked. + +#. Execute a test, for example, ``BLEUWB_RDR_EXPEDITED_STANDARD_PHASE`` with Bluetooth LE + UWB transport (see :ref:`testing_verification_th`) using the Test Harness. + If the test is successful, rerun the ``./chip-tool doorlock read lock-state 1 1`` command. + + You should see the following status in the response: + + .. code-block:: console + + [1757072050.821] [951199:951201] [TOO] LockState: 2 + + The response indicates that the lock is unlocked. diff --git a/docs/testing/setting_up_test_harness.rst b/docs/testing/setting_up_test_harness.rst new file mode 100644 index 00000000..0a1c9d6d --- /dev/null +++ b/docs/testing/setting_up_test_harness.rst @@ -0,0 +1,26 @@ +.. _testing_test_harness: +.. _setting_up_the_aliro_test_harness: +.. _testing_environment_configuration: + +Setting up the Aliro Test Harness +################################# + +.. contents:: + :local: + :depth: 2 + +This page provides instructions on setting up the Test Harness and getting Access Credentials from it. +In case you do not have access to `Aliro Certification Tool`_ repository, see the :ref:`hw_requirements_test_harness` section for further guidance. + +.. note:: + All examples related to the `Aliro Certification Tool`_ are based on the `aliro-sve-v1.0`_ tag. + +#. Follow the `Test harness usage instructions`_ in the `Aliro Certification Tool`_ repository. + +#. Open your Test Harness project's JSON configuration and locate the ``dut_reader_public_key``, ``th_access_credential_public_key``, ``dut_reader_group_identifier``, and ``dut_reader_group_sub_identifier`` fields. + + .. figure:: /images/th_config.png + :scale: 70% + :alt: Test harness project configuration. + + Test harness project configuration. diff --git a/docs/testing/testing_certificate_reader.rst b/docs/testing/testing_certificate_reader.rst new file mode 100644 index 00000000..77491db2 --- /dev/null +++ b/docs/testing/testing_certificate_reader.rst @@ -0,0 +1,143 @@ +.. _testing_reader_certificate: + +Testing Aliro Reader Certificate +################################ + +.. contents:: + :local: + :depth: 2 + +The Aliro Reader Certificate is an optional X.509 certificate that allows a Reader device to authenticate itself to the User Device during the expedited standard phase of the Aliro protocol. +When a certificate is provisioned, the Reader sends a ``LOAD_CERT`` command after the ``AUTH0`` exchange, allowing the User Device to verify the Reader's identity. + +This feature is controlled by the ``CONFIG_DOOR_LOCK_READER_CERTIFICATE`` Kconfig option (enabled by default for non-Matter builds). + +Certificate generation and provisioning +*************************************** + +This section describes how to generate Reader certificates and provision them on the device under test (DUT). + +Generating certificates +======================= + +#. Generate an Issuer key pair for signing certificates: + + .. code-block:: console + + cd scripts + python3 generate_keypair.py --verbose + +#. Save the output: + + * ``ISSUER_PRIV`` - Issuer Private Key (32 bytes hex) + * ``ISSUER_PUB`` - Issuer Public Key (65 bytes hex) + +#. Generate a Reader certificate. + You can choose between two certificate sizes: + + * Short certificate (~152 bytes) - It does not require APDU chaining. + You can generate it by running the following command: + + .. code-block:: console + + python3 generate_reader_cert.py \ + --subject-pubkey \ + --issuer-privkey \ + --output hex | xargs python3 compress_reader_cert.py + + + * Long certificate (~270 bytes) - It requires APDU chaining (for testing chaining mechanism). + You can generate it by running the following command: + + .. code-block:: console + + ./generate_max_size_cert.sh + +#. Save the compressed certificate HEX output for provisioning. + +Configuring Test Harness +======================== + +Update your Test Harness project configuration with the following fields: + +* ``dut_reader_issuer_public_key`` - Set to the ``ISSUER_PUB``. +* ``dut_reader_issuer_group_identifier`` - Set it to a value different from ``dut_reader_group_identifier``. + Otherwise, you will not be able to test certificate-based authentication. + + .. code-block:: json + + { + "config": { + "test_parameters": { + "dut_reader_public_key": "", + "dut_reader_group_identifier": "00113344667799AA00113344667799AB", + "dut_reader_issuer_group_identifier": "FFEEDDCCBBAA998877665544332211FF", + "dut_reader_issuer_public_key": "" + } + } + } + +Provisioning DUT +================ + +Connect to the DUT through serial console and provision both the certificate and issuer public key: + +#. Install the proper Reader group identifier (``dut_reader_issuer_group_identifier`` value): + + .. code-block:: console + + uart:~$ dl install group_id + +#. Provision the Issuer Public Key: + + .. code-block:: console + + uart:~$ dl provisioning issuer_pk set + +#. Provision the Reader Certificate (compressed): + + .. code-block:: console + + uart:~$ dl provisioning reader_cert set + +#. Verify provisioning: + + .. code-block:: console + + uart:~$ dl provisioning issuer_pk list + Issuer public key (65 bytes): + + uart:~$ dl provisioning reader_cert list + Reader certificate (XXX bytes): + +.. note:: + The certificate size is limited by ``CONFIG_DOOR_LOCK_READER_CERTIFICATE_MAX_SIZE``. + By default it is set to 512 bytes. + If you need to provision larger certificates, increase this value in your project configuration. + +Running tests with certificates +******************************* + +Once the certificate is provisioned, execute Test Harness test cases that include the ``LOAD_CERT`` command: + +* ``NFC_RDR_STANDARD_CERT_IN_LOAD_CERT`` - Standard certificate test +* ``NFC_RDR_STANDARD_CERT_IN_LOAD_CERT_WITH_CHAINING`` - Certificate with APDU chaining + +You can expect the following behavior: + +* DUT sends ``LOAD_CERT`` command with the provisioned certificate. +* If you are using a long certificate, APDU chaining will be used (in multiple chunks). +* Test Harness verifies the certificate signature using ``ISSUER_PUB``. +* Transaction proceeds normally after validating the certificate. + +Clearing certificates +===================== + +To run standard tests without certificates, clear the provisioned data: + +.. code-block:: console + + uart:~$ dl provisioning reader_cert clear + uart:~$ dl provisioning issuer_pk clear + +After clearing, the DUT will skip the ``LOAD_CERT`` state and proceed directly from ``AUTH0`` response to ``AUTH1`` (expedited standard phase). diff --git a/docs/testing/verification_and_testing.rst b/docs/testing/verification_and_testing.rst new file mode 100644 index 00000000..3582997c --- /dev/null +++ b/docs/testing/verification_and_testing.rst @@ -0,0 +1,383 @@ +.. _testing_verification_and_testing: +.. _testing_verification_th: + +Verification and testing +######################## + +.. contents:: + :local: + :depth: 2 + +Once you have successfully provisioned Aliro credentials, either through CLI or with Matter, perform tests to ensure your device is functioning correctly. + +Select tests for running +************************ + +You can see the full list of available tests in the `Aliro Certification Tool`_ repository. +From the :guilabel:`Test Suites` list, choose the tests with the `Reader` in the name, for example, :guilabel:`BLE Reader`. +This will allow you to test the Bluetooth LE + UWB transport. + +.. note:: + You can upload the :file:`applications/doorlock/docs/certification_assets/Aliro PICS v0.9.3.r2.xml` file to the Test Harness to automatically select tests for execution. + However, there is a known bug in this Test Harness revision that causes the PICS file to select some incorrect tests and skip some needed to execute. + + * Tests that should be executed (but might be skipped): + + * ``NFC_RDR_NEG_STEPUP_AD_SCHEDULE_IN_ACCESS_RULE_AND_READER`` + * ``NFC_RDR_NEG_STEPUP_AD_SCHEDULE_TIME_VERIFY_REQUIRED`` + * ``NFC_RDR_NEG_STEPUP_AD_TIME_VERIFICATION_REQUIRED`` + * ``BLEUWB_RDR_ADVERTISEMENT_FORMAT`` + + * Tests that should not be executed (but might be selected): + + * ``BLEUWB_RDR_CONTROL_FLOW_RDR_DESCRIPTOR_TAG`` + +Always verify that the correct tests are chosen before executing them. +If you do not upload the PICS file, you can choose the tests manually. + +.. figure:: /images/th_test_cases.png + :scale: 70% + :alt: Test suites list. + + Test suites list. + +The Test Harness device will be operating as the Aliro User Device and communicate with the Reader device. +For verification, execute the following tests, based on the `aliro-sve-v1.0`_ tag. +The test results shown below were acquired on the nRF5340 DK build using the following command: + +.. code-block:: bash + + west build -b nrf5340dk/nrf5340/cpuapp -- -Dapp_SNIPPET=uwb_qm35 -DCONFIG_DOOR_LOCK_EXPEDITED_FAST_PHASE=y -DCONFIG_DOOR_LOCK_STEP_UP_PHASE=y -DCONFIG_DOOR_LOCK_CREDENTIAL_ISSUER_CA=y + +.. tabs:: + + .. tab:: NFC Reader + + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | Test case | Description | Result | Comment | + +===========================================================+=========================================================================================+========+==============+ + | NFC_RDR_STANDARD_NO_CERT | Verify conformance of Reader UT in AUTH1 command. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_AUTH0_EXTRA_TAG | Verify conformance of Reader UT in AUTH1 command. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_AUTH1_EXTRA_TAG | Verify conformance of Reader UT in AUTH1 command. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_AUTH0_WRONG_VALUE | Verify conformance of Reader UT in AUTH1 command. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_SEL_RSP_NO_COMMON_EXPEDITED_PROTOCOL_VERSION | Verify conformance of Reader UT in SELECT command. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_AUTH1_WRONG_VALUES | Verify conformance of Reader UT in AUTH1 command. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_AUTH1_WRONG_UD_SIGNATURE | Verify conformance of Reader UT in AUTH1 command. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_STANDARD_CERT_IN_LOAD_CERT_WITH_CHAINING | Verify conformance of Reader during NFC standard "transaction using LOAD CERT protocol".| PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_FAST | Verify conformance of Reader UT in expedited fast phase. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_DEVICE_KEY_INFO_MISMATCH | Verify rejection of Access Document not associated with the Access Credential. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_DOCTYPE_NOT_ALIROA | Verify rejection of Access Document with invalid docType. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_INVALID_ACCESS_DATA_ELEMENT_VERSION | Verify rejection of Access Document with incorrect Access Data Element version. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_INVALID_HASH_ISSUER_AUTH | Verify rejection of Access Document with an invalid Data Element digest. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_INVALID_SIGNATURE_ISSUER_AUTH | Verify rejection of Access Document with invalid IssuerAuth signature. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_ISSUER_CERTIFICATE_TIME_MISMATCH | Verify rejection of Access Document with issuer certificate time mismatch. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_ISSUER_CERT_INVALID_SIGNATURE | Verify rejection of Access Document with invalid issuer certificate signature. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_ISSUER_DOCTYPE_MISMATCH | Verify rejection of Access Document with incorrect issuerAuth doctype. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_NO_ACCESS_RULE_FOR_READER_ACTION | Verify rejection of Access Document with no Access Rule for the intended action. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_NO_DATA_ELEMENTS | Verify rejection of Access Document with no data elements. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_NO_ISSUER_CERT_NO_KEY_ID | Verify rejection of Access Document with no Issuer Certificate or Key Identifier. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_SCHEDULE_IN_ACCESS_RULE_AND_READER | Verify rejection of Access Document with schedule in access rule and reader. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_SCHEDULE_TIME_VERIFY_REQUIRED | Verify rejection of Access Document requiring schedule time verification. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_TIME_VERIFICATION_REQUIRED | Verify rejection of Access Document requiring time verification. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_UNKNOWN_CRITICAL_ACCESS_EXTENSION | Verify rejection of Access Document with unknown critical access extension. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_UNKNOWN_READER_RULE | Verify rejection of Access Document with unknown reader rule. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_NEG_STEPUP_AD_VALIDITY_ITERATION | Verify rejection of Access Document with validity iteration. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_STEPUP_AD_ACCESS_RULE | Verify parsing of Access Document with Access Rule. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_STEPUP_AD_ISSUER_CERT_KEY_ID | Verify parsing of Access Document with Issuer Certificate Key ID. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_STEPUP_AD_ISSUER_CERT | Verify parsing of Access Document with Issuer Certificate. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_STEPUP_AD_KEY_ID | Verify parsing of Access Document with Key Identifier. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_STEPUP_AD_UNKNOWN_NON_ACCESS_EXTENSION | Verify parsing of Access Document with unknown non-access extension. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + | NFC_RDR_STEPUP_AD_UNKNOWN_NON_CRITICAL_ACCESS_EXTENSION | Verify parsing of Access Document with unknown non-critical access extension. | PASS | | + +-----------------------------------------------------------+-----------------------------------------------------------------------------------------+--------+--------------+ + + .. tab:: Bluetooth LE + UWB + + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | Test case | Description | Result | Comment | + +===========================================+==========================================================================+========+=============================+ + | BLEUWB_RDR_EXPEDITED_STANDARD_PHASE | Verify conformance of Reader UT in standard phase expedited transaction. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_RANGING_SUSPEND | Verify conformance of Reader UT in ranging suspend functionality. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_RANGING_RESUME | Verify conformance of Reader UT in ranging resume functionality. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_NEG_FAILED_L2CAP | Verify conformance of Reader UT in L2CAP connection failure handling. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_NEG_FAILED_SPSM_L2CAP | Verify conformance of Reader UT in SPSM L2CAP failure handling. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_NEG_TIMEOUT_BEFORE_AUTH0 | Verify conformance of Reader UT in timeout handling before AUTH0. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_TIMEOUT_EXTENSION | Verify conformance of Reader UT in timeout extension handling. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_NEG_M2_MISMATCH_PARAMETER | Verify conformance of Reader UT in M2 parameter mismatch handling. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_NEG_M4_MISMATCH_PARAMETER | Verify conformance of Reader UT in M4 parameter mismatch handling. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_ADVERTISEMENT_FORMAT | Verify conformance of Reader UT in advertisement format. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_NEG_SUSPEND_MISMATCH_PARAMETER | Verify conformance of Reader UT in suspend parameter mismatch handling. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_STEPUP_PHASE | Verify conformance of Reader UT in step-up phase. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + | BLEUWB_RDR_EXPEDITED_FAST_PHASE | Verify conformance of Reader UT in expedited fast phase. | PASS | | + +-------------------------------------------+--------------------------------------------------------------------------+--------+-----------------------------+ + +Running the test +**************** + +Complete the following steps for the required tests: + +#. Navigate to the project you created in the Test Harness web interface and click :guilabel:`Go To Test-Run`. + +#. Click on :guilabel:`Add New Test`. + +#. In the test suites, select either :guilabel:`NFC Reader` or :guilabel:`BLE Reader` and under the :guilabel:`Test Cases` section check the box of the test you wish to run. + +#. Choose the operator and click :guilabel:`Start`. + +#. Complete the test: + + .. tabs:: + + .. tab:: NFC + + a. Position the Reader and Test Harness hardware close to each other, and align them to ensure optimal NFC communication. + #. A notification will appear asking you to set the Reader DUT in the appropriate mode and to place the devices next to each other for automatic NFC detection. + Select :guilabel:`Ok` and click :guilabel:`Submit`. + + .. tab:: Bluetooth LE + + a. Ensure the Reader is advertising and ready to accept Bluetooth LE connections from the Test Harness. + You should see a notification requesting Bluetooth LE visibility for automatic detection. + #. Confirm that the Reader is advertising by checking the DUT console for logs indicating that advertising has started. + + .. code-block:: console + + L2CAP server registered with PSM: 0x0080 + + #. Select :guilabel:`Ok` and click :guilabel:`Submit`. + Restarting the Murata device, if prompted, is optional. + + .. note:: + At the start of each Bluetooth LE test, the firmware is uploaded to the Murata device, which may take some time. + The Murata module (`LBUA0VG2BP-EVK-P`_) is used by the Test Harness to establish and handle Bluetooth LE communication with the device under test. + +#. Depending on the test you executed, you should see results similar to the following examples: + + .. tabs:: + + .. tab:: NFC_RDR_STANDARD_NO_CERT + + .. figure:: /images/NFC_RDR_STANDARD_NO_CERT_selection.png + :scale: 50% + :alt: Tests selection view. + + Tests selection view. + + The Reader device will select the Test Harness User Device and initiate the Aliro Access Protocol commands exchange. + + In the DUT's serial console, you will see logs that indicate the state of the operation and the data payloads transmitted and received by the Reader. + If the results show as ``passed``, you will see the following output in the Test Harness web interface: + + .. figure:: /images/test_results_NFC_RDR_STANDARD_NO_CERT.png + :scale: 50% + :alt: Example of the advanced test results. + + Example of the advanced test results. + + After test execution is complete, you can check DUT logs to verify the communication and data exchange between the Reader and the test harness. + The logs will provide detailed information about the test execution and authorization process (signature verification). + When all installation and provisioning data provided in :ref:`testing_environment_configuration` are correct then you will see the following output in the DUT serial console: + + .. code-block:: console + + ACCESS GRANTED + + When access is granted and lock is unlocked, the device signals this state by turning on a dedicated LED. + For details, see the :ref:`lock simulator` section. + When the provided Access Credential public key is incorrect the following output will be displayed: + + .. code-block:: console + + ACCESS DENIED + + .. tab:: BLEUWB_RDR_EXPEDITED_STANDARD_PHASE + + .. figure:: /images/BLEUWB_RDR_EXPEDITED_STANDARD_PHASE_selection.png + :scale: 50% + :alt: Tests selection view. + + Tests selection view. + + The Reader device will advertise over Bluetooth LE and the Test Harness will connect as a Bluetooth LE central device, initiating the Aliro Access Protocol commands exchange over Bluetooth LE. + + In the DUT's serial console, you will see logs that indicate the state of the Bluetooth LE connection, protocol execution, and the data payloads transmitted and received by the Reader. + Additionally, the UWB connection will be configured and established. + If the results show as ``passed``, you will see the following output in the Test Harness web interface: + + .. figure:: /images/test_results_BLEUWB_RDR_EXPEDITED_STANDARD_PHASE.png + :scale: 50% + :alt: Basic test results view. + + Basic test results view. + + +.. note:: + If the application is built with Matter support, you can control the door lock remotely. + For details on how to enable this feature, see the |NCS| door lock sample documentation in the `Matter door lock remote access`_ section. + +Execute the Aliro certification tests +************************************* + +This guide assumes that you have uploaded the PICS file :file:`applications/doorlock/docs/certification_assets/Aliro PICS v0.9.3.r2.xml` to the Test Harness. +You can also select tests manually. + +.. figure:: /images/pics_upload.png + :scale: 70% + :alt: PICS file upload. + + PICS file upload. + +When PICS file is uploaded, all required tests are selected automatically for NFC and Bluetooth LE Reader suites: + +.. figure:: /images/tests_selected_by_pics.png + :scale: 70% + :alt: BLE selected tests. + + Bluetooth LE selected tests. + +The following table explains what should be configured before running a specific test. +Some tests may also require post actions. + +.. tabs:: + + .. tab:: NFC Reader + + +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Test case(s) | Door Lock commands to execute | + +============================================================+===============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================+ + | ALL TESTS | Before test session: | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | + | | * dl install group_id 37652039312061652031642033642065 | + +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_FAST, | Before test: | + | NFC_RDR_NEG_AUTH0_EXTRA_TAG, +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_NEG_AUTH0_WRONG_VALUE, | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | + | NFC_RDR_NEG_AUTH1_EXTRA_TAG, +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_NEG_AUTH1_WRONG_UD_SIGNATURE, | Optional: | + | NFC_RDR_NEG_AUTH1_WRONG_VALUES, +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_NEG_SEL_RSP_NO_COMMON_EXPEDITED_PROTOCOL_VERSION, | * dl install group_id 37652039312061652031642033642065 | + | NFC_RDR_NEG_STEPUP_AD_DEVICE_KEY_INFO_MISMATCH, | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | NFC_RDR_NEG_STEPUP_AD_DOCTYPE_NOT_ALIROA, | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | NFC_RDR_NEG_STEPUP_AD_INVALID_ACCESS_DATA_ELEMENT_VERSION, | | + | NFC_RDR_NEG_STEPUP_AD_INVALID_HASH_ISSUER_AUTH, | | + | NFC_RDR_NEG_STEPUP_AD_INVALID_SIGNATURE_ISSUER_AUTH, | | + | NFC_RDR_NEG_STEPUP_AD_ISSUER_CERT_INVALID_SIGNATURE, | | + | NFC_RDR_NEG_STEPUP_AD_ISSUER_CERTIFICATE_TIME_MISMATCH, | | + | NFC_RDR_NEG_STEPUP_AD_ISSUER_DOCTYPE_MISMATCH, | | + | NFC_RDR_NEG_STEPUP_AD_NO_ACCESS_RULE_FOR_READ, | | + | NFC_RDR_NEG_STEPUP_AD_NO_DATA_ELEMENTS, | | + | NFC_RDR_NEG_STEPUP_AD_NO_ISSUER_CERT_NO_KEY_ID, | | + | NFC_RDR_NEG_STEPUP_AD_UNKNOWN_CRITICAL_ACCESS_EXTENSION, | | + | NFC_RDR_NEG_STEPUP_AD_UNKNOWN_READER_RULE | | + +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_NEG_STEPUP_AD_VALIDITY_ITERATION | Before test: | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | + | | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | Optional: | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | * dl install group_id 37652039312061652031642033642065 | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | After test: | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | * dl provisioning CI_key clear 0 | + +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_STANDARD_CERT_IN_LOAD_CERT_WITH_CHAINING | Before test: | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | * dl provisioning AC_key clear 0 | + | | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | | * dl install group_id 000000000000000000113344667799ab | + | | * dl provisioning issuer_pk set 048608ad7d0bf1258ba6249aa3854b43acef9443cce31b617c32503adb959e5ad7d5915a1ff34ab461b46ff598c5fc72c6a6c8787c886741a4029bb3476eceff26 | + | | * dl provisioning reader_cert set 3082010a040200003082010280145555555555555555555555555555555555555555811e637573746f6d20697373756572206e616d652e2e2e2e2e2e2e2e2e2e2e2e820d3230303130323030303030305a830d3235303530353030303030305a841e637573746f6d207375626a656374206e616d652e2e2e2e2e2e2e2e2e2e2e85420004f27d92b78c0ba00c105dc56b9f5a669d67d44fea5139b85dc5e368c851167c9f40b8d9add7ce7bf846331f1f8fd06e1e15cfd540190f482ec294f52690349f188648003045022051bc8503cc09a80f9f19fa033edcf68fb4c341499d908950de62382e9650ee5b022100a954899fd291c40d4f3f9f749518958678ff5688e7bdaa8a3535faf72450e506| + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | After test: | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | * dl provisioning CI_key clear 0 | + | | * dl install group_id 37652039312061652031642033642065 | + | | * dl provisioning issuer_pk clear | + | | * dl provisioning reader_cert clear | + +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_STANDARD_NO_CERT | Before test: | + | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | * dl install group_id 37652039312061652031642033642065 | + | | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | + | | | + +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_STEPUP_AD_ACCESS_RULE, | Before test: | + | NFC_RDR_STEPUP_AD_ISSUER_CERT, +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | NFC_RDR_STEPUP_AD_ISSUER_CERT_KEY_ID, | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | NFC_RDR_STEPUP_AD_KEY_ID, | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | NFC_RDR_STEPUP_AD_UNKNOWN_NON_ACCESS_EXTENSION, | * dl install group_id 37652039312061652031642033642065 | + | NFC_RDR_STEPUP_AD_UNKNOWN_NON_CRITICAL_ACCESS_EXTENSION | | + +------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + .. tab:: Bluetooth LE UWB Reader + + +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Test case(s) | Door Lock commands to execute | + +=================================+====================================================================================================================================================================+ + | ALL TESTS | Before test: | + | | | + | | * dl provisioning AC_key set 0 04742df736d0fc9be978c45b00e8fdf7cea684ea105ae574c1505a2c24ab6198e3125b7f1b7e1d134c55ece69681ba8ecc18a3836dc5199c759f31e8ccf17e3efa | + | | * dl install group_id 37652039312061652031642033642065 | + +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | BLEUWB_RDR_EXPEDITED_FAST_PHASE | After test: | + | | | + | | * dl kpersistent clear 0 | + +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | BLEUWB_RDR_STEPUP_PHASE | Before test: | + | | | + | | * dl provisioning CI_key set 0 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | | * dl provisioning CI_CA_key set 047BA31938492E3F5E97BC91806B5835B5D9E426609139006711E5FB7A670EE4E12FC9F25396C013CC20166029D761A105DEA5E071E84A9E499920524CE2301137 | + | | | + | | After test: | + | | | + | | * dl install group_id 37652039312061652031642033642065 | + | | | + +---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/drivers/nfc/stm/CMakeLists.txt b/drivers/nfc/stm/CMakeLists.txt index 284396ae..28615526 100644 --- a/drivers/nfc/stm/CMakeLists.txt +++ b/drivers/nfc/stm/CMakeLists.txt @@ -13,14 +13,7 @@ set(ST_RFAL_INC_DIR ${ST_RFAL_DIR}/include) set(INCLUDE_DIR include) -if(CONFIG_ST25R3911_DRV) - zephyr_library_compile_definitions(-DST25R3911) - set(ST_DRIVER_INC ${ST_RFAL_SRC_DIR}/st25r3911) -elseif(CONFIG_ST25R3916B_DRV OR CONFIG_ST25R3916_DRV) - zephyr_library_compile_definitions_ifdef(CONFIG_ST25R3916_DRV -DST25R3916) - zephyr_library_compile_definitions_ifdef(CONFIG_ST25R3916B_DRV -DST25R3916B) - set(ST_DRIVER_INC ${ST_RFAL_SRC_DIR}/st25r3916) -elseif(CONFIG_ST25R200_DRV) +if(CONFIG_ST25R200_DRV) zephyr_library_compile_definitions(-DST25R200) set(ST_DRIVER_INC ${ST_RFAL_SRC_DIR}/st25r200) elseif(CONFIG_ST25R500_DRV) diff --git a/drivers/nfc/stm/Kconfig b/drivers/nfc/stm/Kconfig index f244a50a..0ffa66f6 100644 --- a/drivers/nfc/stm/Kconfig +++ b/drivers/nfc/stm/Kconfig @@ -16,16 +16,7 @@ rsource "nfc_configs/Kconfig" choice prompt "Select ST25R driver" - default ST25R200_DRV - -config ST25R3911_DRV - bool "NFC ST25R3911 driver" - -config ST25R3916_DRV - bool "NFC ST25R3916 driver" - -config ST25R3916B_DRV - bool "NFC ST25R3916B driver" + default ST25R500_DRV config ST25R200_DRV bool "NFC ST25R200/ST25R100 driver" diff --git a/drivers/nfc/stm/Kconfig.pal b/drivers/nfc/stm/Kconfig.pal index ea913066..febdefac 100644 --- a/drivers/nfc/stm/Kconfig.pal +++ b/drivers/nfc/stm/Kconfig.pal @@ -10,27 +10,6 @@ config RFAL_MAX_TIMERS_NUM help Defines the size of the timer array, setting the maximum number of timers that can be used by RFAL. -config RFAL_ISR_THREAD_PRIORITY - int "ISR Thread Priority" - default -1 - help - Sets the priority level of the Interrupt Service Routine thread. - -config RFAL_ISR_THREAD_STACK_SIZE - int "ISR Thread Stack Size" - default 1024 - help - Sets the stack size for the Interrupt Service Routine thread. - -config RFAL_WORKER_THREAD_PRIORITY - int "Priority of the thread that the RFAL worker lives in" - range RFAL_ISR_THREAD_PRIORITY NUM_PREEMPT_PRIORITIES - default 0 - -config RFAL_WORKER_THREAD_STACK_SIZE - int "Stack size of the thread that the RFAL worker lives in" - default 4096 - config RFAL_SINGLE_SPI_BUS bool "Configure SPI bus to work with multiple devices on the same bus" help diff --git a/drivers/nfc/stm/PAL/ncs_pal_gpio.c b/drivers/nfc/stm/PAL/ncs_pal_gpio.c index deaa1a62..ae3afbe7 100644 --- a/drivers/nfc/stm/PAL/ncs_pal_gpio.c +++ b/drivers/nfc/stm/PAL/ncs_pal_gpio.c @@ -5,7 +5,8 @@ */ #include "ncs_pal_gpio.h" -#include "ncs_pal_semaphore.h" +#include "ncs_pal_isr.h" +#include "ncs_pal_nfc_worker.h" #include #include @@ -29,7 +30,8 @@ static void irq_pin_cb(const struct device *gpiob, struct gpio_callback *cb, uin ARG_UNUSED(cb); ARG_UNUSED(pins); - ncs_pal_give_semaphore(); + ncs_pal_submit_nfc_work(); + ncs_pal_isr_trigger(); } // TODO: The function should be moved to the nRF54L style shield. diff --git a/drivers/nfc/stm/PAL/ncs_pal_isr.c b/drivers/nfc/stm/PAL/ncs_pal_isr.c index 8cc9fac3..b838661f 100644 --- a/drivers/nfc/stm/PAL/ncs_pal_isr.c +++ b/drivers/nfc/stm/PAL/ncs_pal_isr.c @@ -5,7 +5,6 @@ */ #include "ncs_pal_isr.h" -#include "ncs_pal_semaphore.h" #include @@ -14,26 +13,25 @@ LOG_MODULE_REGISTER(pal_isr, CONFIG_NFC_LOG_LEVEL); static nfc_isr_cb nfc_isr_callback = NULL; +static void nfc_isr_work_handler(struct k_work *work); +K_WORK_DEFINE(nfc_isr_work, nfc_isr_work_handler); + void ncs_pal_isr_cb_set(nfc_isr_cb cb) { nfc_isr_callback = cb; } -static void nfc_isr_poll_fn(void *unused1, void *unused2, void *unused3) +void ncs_pal_isr_trigger(void) { - ARG_UNUSED(unused1); - ARG_UNUSED(unused2); - ARG_UNUSED(unused3); + (void)k_work_submit(&nfc_isr_work); +} - while (true) { - ncs_pal_take_semaphore(K_FOREVER); - LOG_DBG("NFC ISR"); +static void nfc_isr_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); - if (nfc_isr_callback) { - nfc_isr_callback(); - } + LOG_DBG("NFC ISR"); + if (nfc_isr_callback) { + nfc_isr_callback(); } } - -K_THREAD_DEFINE(nfc_isr_poll_thread, CONFIG_RFAL_ISR_THREAD_STACK_SIZE, nfc_isr_poll_fn, NULL, NULL, NULL, - CONFIG_RFAL_ISR_THREAD_PRIORITY, 0, 0); diff --git a/drivers/nfc/stm/PAL/ncs_pal_isr.h b/drivers/nfc/stm/PAL/ncs_pal_isr.h index c95f43ee..139aab61 100644 --- a/drivers/nfc/stm/PAL/ncs_pal_isr.h +++ b/drivers/nfc/stm/PAL/ncs_pal_isr.h @@ -12,8 +12,19 @@ extern "C" { #endif typedef void (*nfc_isr_cb)(void); + +/** @brief Set ST25R IRQ callback executed from NFC ISR worker context. + * + * @param cb Callback invoked on NFC IRQ. + */ void ncs_pal_isr_cb_set(nfc_isr_cb cb); +/** + * @brief Trigger NFC ISR worker execution (submits work item to system + * workqueue). + */ +void ncs_pal_isr_trigger(void); + #ifdef __cplusplus } #endif diff --git a/drivers/nfc/stm/PAL/ncs_pal_nfc_worker.c b/drivers/nfc/stm/PAL/ncs_pal_nfc_worker.c deleted file mode 100644 index a2ce9122..00000000 --- a/drivers/nfc/stm/PAL/ncs_pal_nfc_worker.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "ncs_pal_nfc_worker.h" - -#include - -#include -LOG_MODULE_REGISTER(pal_nfc_stack, CONFIG_NFC_LOG_LEVEL); - -K_THREAD_STACK_DEFINE(pal_nfc_stack, CONFIG_RFAL_WORKER_THREAD_STACK_SIZE); -struct k_thread pal_nfc_thread; - -k_tid_t ncs_pal_nfc_worker_start(thread_func_t thread_func) -{ - const k_tid_t thread = - k_thread_create(&pal_nfc_thread, pal_nfc_stack, CONFIG_RFAL_WORKER_THREAD_STACK_SIZE, thread_func, NULL, - NULL, NULL, K_PRIO_PREEMPT(CONFIG_RFAL_WORKER_THREAD_PRIORITY), 0, K_NO_WAIT); - - if (thread) { - k_thread_name_set(thread, "PAL Worker"); - } - - return thread; -} diff --git a/drivers/nfc/stm/PAL/ncs_pal_nfc_worker.h b/drivers/nfc/stm/PAL/ncs_pal_nfc_worker.h index ad9533c9..aa120b2e 100644 --- a/drivers/nfc/stm/PAL/ncs_pal_nfc_worker.h +++ b/drivers/nfc/stm/PAL/ncs_pal_nfc_worker.h @@ -7,23 +7,18 @@ #ifndef NCS_PAL_NFC_WORKER_H #define NCS_PAL_NFC_WORKER_H -#include #include #ifdef __cplusplus extern "C" { #endif -typedef void (*thread_func_t)(void *, void *, void *); +/** @brief Submit a work item to the dedicated Aliro workqueue. -/** - * @brief Start NFC worker thread with CONFIG_WORKER_THREAD_PRIORITY priority. + * @note Must be implemented by the application. * - * @param thread_func Pointer to the function that will be executed by the NFC worker thread. - * - * @return The thread ID when success, nullptr otherwise. */ -k_tid_t ncs_pal_nfc_worker_start(thread_func_t thread_func); +void ncs_pal_submit_nfc_work(); #ifdef __cplusplus } diff --git a/drivers/nfc/stm/PAL/ncs_pal_semaphore.c b/drivers/nfc/stm/PAL/ncs_pal_semaphore.c deleted file mode 100644 index 788dbaae..00000000 --- a/drivers/nfc/stm/PAL/ncs_pal_semaphore.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "ncs_pal_semaphore.h" - -K_SEM_DEFINE(irq_sem, 0, 1); - -void ncs_pal_take_semaphore(k_timeout_t timeout_ms) -{ - k_sem_take(&irq_sem, timeout_ms); -} - -void ncs_pal_give_semaphore(void) -{ - k_sem_give(&irq_sem); -} diff --git a/drivers/nfc/stm/PAL/ncs_pal_semaphore.h b/drivers/nfc/stm/PAL/ncs_pal_semaphore.h deleted file mode 100644 index 03ade520..00000000 --- a/drivers/nfc/stm/PAL/ncs_pal_semaphore.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#ifndef NCS_PAL_SEMAPHORE_H -#define NCS_PAL_SEMAPHORE_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Take the irq semaphore - * - * @param timeout Maximum time in ms to wait for the semaphore to become available. - * - */ -void ncs_pal_take_semaphore(k_timeout_t timeout_ms); - -/** - * @brief Give the irq semaphore - */ -void ncs_pal_give_semaphore(void); - -#ifdef __cplusplus -} -#endif - -#endif /* NCS_PAL_SEMAPHORE_H */ diff --git a/drivers/nfc/stm/PAL/ncs_pal_timer.c b/drivers/nfc/stm/PAL/ncs_pal_timer.c index 95496e6d..83dbd0e2 100644 --- a/drivers/nfc/stm/PAL/ncs_pal_timer.c +++ b/drivers/nfc/stm/PAL/ncs_pal_timer.c @@ -7,7 +7,6 @@ #include #include -#include "ncs_pal_semaphore.h" #include "ncs_pal_timer.h" #include @@ -25,16 +24,11 @@ uint32_t ncs_pal_get_sys_tick() return k_uptime_get_32(); } -static void timer_expiry_callback(struct k_timer *timer) -{ - ncs_pal_give_semaphore(); -} - void ncs_pal_timers_init() { int timer_id = 0; for (timer_id = 0; timer_id < CONFIG_RFAL_MAX_TIMERS_NUM; timer_id++) { - k_timer_init(&timers[timer_id].timer, timer_expiry_callback, NULL); + k_timer_init(&timers[timer_id].timer, NULL, NULL); timers[timer_id].in_use = false; } LOG_DBG("%d timers initialized", timer_id + 1); diff --git a/drivers/nfc/stm/README.md b/drivers/nfc/stm/README.md index 6b0cc668..cd8853d2 100644 --- a/drivers/nfc/stm/README.md +++ b/drivers/nfc/stm/README.md @@ -13,7 +13,7 @@ There are following components of the code structure and their roles within the ## Supported configuration -See the list of the currently supported NFC configurations: +See the list of the NFC drivers currently supported by RFAL: - ST25R3911 - ST25R3911B @@ -21,15 +21,18 @@ See the list of the currently supported NFC configurations: - ST25R3916B - ST25R100 - ST25R200 -- ST25R500 - ST25R300 +- ST25R500 ## Supported shields -See the list of the currently supported NFC shields: +See the list of the NFC shields currently supported by RFAL: - [X-NUCLEO-NFC05A1](https://www.st.com/en/ecosystems/x-nucleo-nfc05a1.html) - [X-NUCLEO-NFC08A1](https://www.st.com/en/ecosystems/x-nucleo-nfc08a1.html) - [X-NUCLEO-NFC09A1](https://www.st.com/en/ecosystems/x-nucleo-nfc09a1.html) +- [X-NUCLEO-NFC12A1](https://www.st.com/en/ecosystems/x-nucleo-nfc12a1.html) + +> Note: The STM NFC transceivers and shields supported in the nRF Connect Door Lock reference application may not match the full list supported natively by RFAL. ## Integrating a New ST Low Level Driver for NFC IC diff --git a/drivers/nfc/stm/nfc_configs/CMakeLists.txt b/drivers/nfc/stm/nfc_configs/CMakeLists.txt index caf68caa..23842f01 100644 --- a/drivers/nfc/stm/nfc_configs/CMakeLists.txt +++ b/drivers/nfc/stm/nfc_configs/CMakeLists.txt @@ -4,8 +4,11 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -file(GLOB_RECURSE NFC_CONFIGS_SOURCES CONFIGURE_DEPENDS ./*.c) - zephyr_include_directories(.) -zephyr_library_sources(${NFC_CONFIGS_SOURCES}) +if(CONFIG_ST25R500_DRV AND CONFIG_ST25R200_DRV OR NOT CONFIG_ST25R500_DRV AND NOT CONFIG_ST25R200_DRV) + message(FATAL_ERROR "Either CONFIG_ST25R500_DRV or CONFIG_ST25R200_DRV must be enabled, but not both") +endif() + +add_subdirectory_ifdef(CONFIG_ST25R500_DRV st25r500) +add_subdirectory_ifdef(CONFIG_ST25R200_DRV st25r200) diff --git a/drivers/nfc/stm/nfc_configs/Kconfig b/drivers/nfc/stm/nfc_configs/Kconfig index 0d0c7ddc..0868132c 100644 --- a/drivers/nfc/stm/nfc_configs/Kconfig +++ b/drivers/nfc/stm/nfc_configs/Kconfig @@ -7,7 +7,7 @@ choice RFAL_WAKE_UP_MODE prompt "Wake-Up Mode configuration" default RFAL_WAKE_UP_MODE_RELAXED - depends on RFAL_FEATURE_WAKEUP_MODE && ST25R200_DRV + depends on RFAL_FEATURE_WAKEUP_MODE config RFAL_WAKE_UP_MODE_STRICT bool "Strict configuration - less sensitive, more robust" diff --git a/drivers/nfc/stm/nfc_configs/rfal_wum_common.h b/drivers/nfc/stm/nfc_configs/rfal_wum_common.h new file mode 100644 index 00000000..c285e0cd --- /dev/null +++ b/drivers/nfc/stm/nfc_configs/rfal_wum_common.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef RFAL_WUM_COMMON_H_ +#define RFAL_WUM_COMMON_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Base wake-up configuration initializer + * + * Common settings shared between ST25R200 and ST25R500. + * Chip-specific settings (period, delta, threshold, aaWeight) + * must be set after using this initializer. + */ + +// clang-format off +#define RFAL_WUM_CONFIG_BASE_INIT \ + { \ + .irqTout = false, \ + .skipCal = false, \ + .skipReCal = false, \ + .delCal = true, \ + .delRef = true, \ + .autoAvg = true, \ + .measFil = RFAL_WUM_MEAS_FIL_SLOW, \ + .measDur = RFAL_WUM_MEAS_DUR_44_28, \ + .I.enabled = true, \ + .Q.enabled = true, \ + .I.reference = RFAL_WUM_REFERENCE_AUTO, \ + .Q.reference = RFAL_WUM_REFERENCE_AUTO, \ + .I.aaInclMeas = true, \ + .Q.aaInclMeas = true, \ + } +// clang-format on + +#ifdef __cplusplus +} +#endif + +#endif /* RFAL_WUM_COMMON_H_ */ diff --git a/app/src/aliro/platform/access_decision_indicator/CMakeLists.txt b/drivers/nfc/stm/nfc_configs/st25r200/CMakeLists.txt similarity index 55% rename from app/src/aliro/platform/access_decision_indicator/CMakeLists.txt rename to drivers/nfc/stm/nfc_configs/st25r200/CMakeLists.txt index 06eb1d4c..caf68caa 100644 --- a/app/src/aliro/platform/access_decision_indicator/CMakeLists.txt +++ b/drivers/nfc/stm/nfc_configs/st25r200/CMakeLists.txt @@ -4,8 +4,8 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -zephyr_include_directories(.) +file(GLOB_RECURSE NFC_CONFIGS_SOURCES CONFIGURE_DEPENDS ./*.c) -file(GLOB impl_src CONFIGURE_DEPENDS *.cpp) +zephyr_include_directories(.) -zephyr_library_sources(${impl_src}) +zephyr_library_sources(${NFC_CONFIGS_SOURCES}) diff --git a/drivers/nfc/stm/nfc_configs/rfal_nfc_config.c b/drivers/nfc/stm/nfc_configs/st25r200/rfal_nfc_config.c similarity index 78% rename from drivers/nfc/stm/nfc_configs/rfal_nfc_config.c rename to drivers/nfc/stm/nfc_configs/st25r200/rfal_nfc_config.c index 5a9bb77e..6ed0bb79 100644 --- a/drivers/nfc/stm/nfc_configs/rfal_nfc_config.c +++ b/drivers/nfc/stm/nfc_configs/st25r200/rfal_nfc_config.c @@ -5,6 +5,8 @@ */ #include "rfal_nfc_config.h" +#include "rfal_wum_common.h" + #include LOG_MODULE_REGISTER(pal_nfc_config, CONFIG_NFC_LOG_LEVEL); @@ -17,8 +19,6 @@ void rfalNfcWakeupConfig(rfalNfcDiscoverParam *conf) conf->wakeupPollBefore = IS_ENABLED(CONFIG_RFAL_WAKEUP_POLL_BEFORE); conf->wakeupNPolls = CONFIG_RFAL_WAKEUP_NPOLLS; -#ifdef CONFIG_ST25R200_DRV - #ifdef CONFIG_RFAL_WAKE_UP_MODE_DEFAULT conf->wakeupConfigDefault = true; @@ -27,32 +27,11 @@ void rfalNfcWakeupConfig(rfalNfcDiscoverParam *conf) conf->wakeupConfigDefault = false; - rfalWakeUpConfig wakeupConfig = { - - .irqTout = false, - .skipCal = false, - .skipReCal = false, - .delCal = true, - .delRef = true, - .autoAvg = true, - .measFil = RFAL_WUM_MEAS_FIL_SLOW, - .measDur = RFAL_WUM_MEAS_DUR_44_28, - - .I.enabled = true, - .Q.enabled = true, - - .I.reference = RFAL_WUM_REFERENCE_AUTO, - .Q.reference = RFAL_WUM_REFERENCE_AUTO, - - .I.aaInclMeas = true, - .Q.aaInclMeas = true, - - }; + rfalWakeUpConfig wakeupConfig = RFAL_WUM_CONFIG_BASE_INIT; #if defined(CONFIG_RFAL_WAKE_UP_MODE_STRICT) wakeupConfig.period = RFAL_WUM_PERIOD_620MS; - wakeupConfig.I.delta = 2U; wakeupConfig.I.threshold = ((uint8_t)RFAL_WUM_TRE_ABOVE); wakeupConfig.I.aaWeight = RFAL_WUM_AA_WEIGHT_32; @@ -84,6 +63,6 @@ void rfalNfcWakeupConfig(rfalNfcDiscoverParam *conf) #endif // CONFIG_RFAL_WAKE_UP_MODE_STRICT #endif // CONFIG_RFAL_WAKE_UP_MODE_DEFAULT -#endif // CONFIG_ST25R200_DRV + #endif // CONFIG_RFAL_FEATURE_WAKEUP_MODE } diff --git a/app/src/aliro/platform/logger/CMakeLists.txt b/drivers/nfc/stm/nfc_configs/st25r500/CMakeLists.txt similarity index 55% rename from app/src/aliro/platform/logger/CMakeLists.txt rename to drivers/nfc/stm/nfc_configs/st25r500/CMakeLists.txt index 825f1596..caf68caa 100644 --- a/app/src/aliro/platform/logger/CMakeLists.txt +++ b/drivers/nfc/stm/nfc_configs/st25r500/CMakeLists.txt @@ -4,8 +4,8 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -zephyr_include_directories(.) +file(GLOB_RECURSE NFC_CONFIGS_SOURCES CONFIGURE_DEPENDS ./*.c) -file(GLOB logger_src CONFIGURE_DEPENDS *.cpp) +zephyr_include_directories(.) -zephyr_library_sources(${logger_src}) +zephyr_library_sources(${NFC_CONFIGS_SOURCES}) diff --git a/drivers/nfc/stm/nfc_configs/st25r500/rfal_nfc_config.c b/drivers/nfc/stm/nfc_configs/st25r500/rfal_nfc_config.c new file mode 100644 index 00000000..89fcb212 --- /dev/null +++ b/drivers/nfc/stm/nfc_configs/st25r500/rfal_nfc_config.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "rfal_nfc_config.h" +#include "rfal_wum_common.h" + +#include + +LOG_MODULE_REGISTER(pal_nfc_config, CONFIG_NFC_LOG_LEVEL); + +void rfalNfcWakeupConfig(rfalNfcDiscoverParam *conf) +{ +#ifdef CONFIG_RFAL_FEATURE_WAKEUP_MODE + + conf->wakeupEnabled = true; + conf->wakeupPollBefore = IS_ENABLED(CONFIG_RFAL_WAKEUP_POLL_BEFORE); + conf->wakeupNPolls = CONFIG_RFAL_WAKEUP_NPOLLS; + +#ifdef CONFIG_RFAL_WAKE_UP_MODE_DEFAULT + + conf->wakeupConfigDefault = true; + +#else // CONFIG_RFAL_WAKE_UP_MODE_DEFAULT + + conf->wakeupConfigDefault = false; + + rfalWakeUpConfig wakeupConfig = RFAL_WUM_CONFIG_BASE_INIT; + +#if defined(CONFIG_RFAL_WAKE_UP_MODE_STRICT) + + wakeupConfig.period = RFAL_WUM_PERIOD_500MS; + + wakeupConfig.I.delta = 2U; + wakeupConfig.I.threshold = ((uint8_t)RFAL_WUM_TRE_ABOVE); + wakeupConfig.I.aaWeight = RFAL_WUM_AA_WEIGHT_32; + + wakeupConfig.Q.delta = 4U; + wakeupConfig.Q.threshold = ((uint8_t)RFAL_WUM_TRE_BELOW); + wakeupConfig.Q.aaWeight = RFAL_WUM_AA_WEIGHT_32; + + conf->wakeupConfig = wakeupConfig; + +#elif defined(CONFIG_RFAL_WAKE_UP_MODE_RELAXED) + + wakeupConfig.period = RFAL_WUM_PERIOD_300MS; + + /* ST25R500 is more sensitive - use higher thresholds to avoid false wakeups */ + wakeupConfig.I.delta = 6U; + wakeupConfig.I.threshold = ((uint8_t)RFAL_WUM_TRE_ABOVE | (uint8_t)RFAL_WUM_TRE_BELOW); + wakeupConfig.I.aaWeight = RFAL_WUM_AA_WEIGHT_64; + + wakeupConfig.Q.delta = 5U; + wakeupConfig.Q.threshold = ((uint8_t)RFAL_WUM_TRE_ABOVE | (uint8_t)RFAL_WUM_TRE_BELOW); + wakeupConfig.Q.aaWeight = RFAL_WUM_AA_WEIGHT_64; + + conf->wakeupConfig = wakeupConfig; + +#else // CONFIG_RFAL_WAKE_UP_MODE_STRICT + + LOG_ERR("Invalid RFAL wake-up mode configuration"); + +#endif // CONFIG_RFAL_WAKE_UP_MODE_STRICT + +#endif // CONFIG_RFAL_WAKE_UP_MODE_DEFAULT + +#endif // CONFIG_RFAL_FEATURE_WAKEUP_MODE +} diff --git a/drivers/samples/nfc_reader/prj.conf b/drivers/samples/nfc_reader/prj.conf index 7336f53a..a8f9fad0 100644 --- a/drivers/samples/nfc_reader/prj.conf +++ b/drivers/samples/nfc_reader/prj.conf @@ -5,9 +5,6 @@ # # This file contains selected Kconfig options for the application. -# Disable Aliro stack for this sample -CONFIG_NCS_DOOR_LOCK=n - # state machine framework CONFIG_SMF=y diff --git a/drivers/samples/nfc_reader_async/prj.conf b/drivers/samples/nfc_reader_async/prj.conf index f10ea181..59ffce1e 100644 --- a/drivers/samples/nfc_reader_async/prj.conf +++ b/drivers/samples/nfc_reader_async/prj.conf @@ -5,9 +5,6 @@ # # This file contains selected Kconfig options for the application. -# Disable Aliro stack for this sample -CONFIG_NCS_DOOR_LOCK=n - # compiler CONFIG_DEBUG_OPTIMIZATIONS=y diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index ca6dbe4e..687541a0 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -14,5 +14,5 @@ elseif(CONFIG_SOC_NRF54LM20A) set(SOC_NAME "nrf54lm20a") endif() -add_subdirectory_ifdef(CONFIG_NCS_DOOR_LOCK aliro) +add_subdirectory_ifdef(CONFIG_NCS_ALIRO aliro) add_subdirectory_ifdef(CONFIG_QM35_UWB_LIB qm35_uwb) diff --git a/lib/Kconfig b/lib/Kconfig index af401969..e3feccb6 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -4,4 +4,5 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +orsource "aliro/Kconfig" rsource "qm35_uwb/Kconfig" diff --git a/lib/aliro/CMakeLists.txt b/lib/aliro/CMakeLists.txt index d7c505e6..e26619f1 100644 --- a/lib/aliro/CMakeLists.txt +++ b/lib/aliro/CMakeLists.txt @@ -5,13 +5,25 @@ # if(NOT TARGET aliro) - if(CONFIG_DOOR_LOCK_BLE_UWB) + if(CONFIG_NCS_ALIRO_BLE_UWB) set(ALIRO_LIB_NAME libaliro_ble.a) - else(CONFIG_DOOR_LOCK_BLE_UWB) + else(CONFIG_NCS_ALIRO_BLE_UWB) set(ALIRO_LIB_NAME libaliro.a) - endif(CONFIG_DOOR_LOCK_BLE_UWB) + endif(CONFIG_NCS_ALIRO_BLE_UWB) - zephyr_library_import(aliro ${CMAKE_CURRENT_SOURCE_DIR}/bin/${SOC_NAME}/${ALIRO_LIB_NAME}) + if(CONFIG_DOOR_LOCK_RELEASE) + set(BUILD_TYPE "release") + else() + set(BUILD_TYPE "debug") + endif() + + set(ALIRO_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin/${BUILD_TYPE}/${SOC_NAME}/${ALIRO_LIB_NAME}) + + if(NOT EXISTS ${ALIRO_LIB_PATH}) + message(FATAL_ERROR "Aliro library not found. Expected at ${ALIRO_LIB_PATH}") + endif() + + zephyr_library_import(aliro ${ALIRO_LIB_PATH}) endif() if(NOT DEFINED PUBLIC_API_DIR) @@ -20,6 +32,4 @@ endif() zephyr_include_directories( ${PUBLIC_API_DIR}/include - ${PUBLIC_API_DIR}/interfaces - ${PUBLIC_API_DIR}/interfaces/crypto/backend_crypto_psa ) diff --git a/lib/aliro/Kconfig b/lib/aliro/Kconfig new file mode 100644 index 00000000..6037a359 --- /dev/null +++ b/lib/aliro/Kconfig @@ -0,0 +1,77 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-4-Clause +# + +menuconfig NCS_ALIRO + bool "NCS Aliro Stack [EXPERIMENTAL]" + depends on (SOC_SERIES_NRF54LX || SOC_SERIES_NRF53X || SOC_SERIES_NRF52X) + select EXPERIMENTAL + select NFC_T4T_APDU + select SMF + select SMF_ANCESTOR_SUPPORT + select SMF_INITIAL_TRANSITION + select ZCBOR + select ZCBOR_CANONICAL + help + This option enables the Aliro reader stack. + The Aliro stack provides the core functionality for Aliro readers. + +if NCS_ALIRO + +config NCS_ALIRO_RELEASE + bool "Enable Aliro release configuration" + help + This option selects precompiled Aliro stack libraries without logs. + +choice NCS_ALIRO_LOG_LEVEL + prompt "Log level for Aliro module" + default NCS_ALIRO_LOG_LEVEL_OFF if NCS_ALIRO_RELEASE + default NCS_ALIRO_LOG_LEVEL_DBG + +config NCS_ALIRO_LOG_LEVEL_OFF + bool "Aliro module log level set to off" + help + No logging + +config NCS_ALIRO_LOG_LEVEL_ERR + bool "Aliro module log level set to error" + help + Error logging only + +config NCS_ALIRO_LOG_LEVEL_WRN + bool "Aliro module log level set to warning" + help + Warning and error logging + +config NCS_ALIRO_LOG_LEVEL_INF + bool "Aliro module log level set to info" + help + Informational, warning and error logging + +config NCS_ALIRO_LOG_LEVEL_DBG + bool "Aliro module log level set to debug" + help + Debug, informational, warning and error logging + +endchoice + +config NCS_ALIRO_LOG_LEVEL_VALUE + int + default 0 if NCS_ALIRO_LOG_LEVEL_OFF + default 1 if NCS_ALIRO_LOG_LEVEL_ERR + default 2 if NCS_ALIRO_LOG_LEVEL_WRN + default 3 if NCS_ALIRO_LOG_LEVEL_INF + default 4 if NCS_ALIRO_LOG_LEVEL_DBG + +config NCS_ALIRO_BLE_UWB + bool "Aliro Bluetooth LE (BLE) transport together with ultra wideband (UWB)" + depends on (SOC_SERIES_NRF54LX || SOC_SERIES_NRF53X) + help + Enable the Aliro BLE transport protocol (TP). This is the transport layer + used by the Reader to communicate with the User Device. It is used to send and + receive packets over BLE. Additionally the UWB is enabled in order to manage the + ranging between the Reader and the User Device. + +endif # NCS_ALIRO diff --git a/lib/aliro/bin/debug/nrf52840/libaliro.a b/lib/aliro/bin/debug/nrf52840/libaliro.a new file mode 100644 index 00000000..8e8980ae Binary files /dev/null and b/lib/aliro/bin/debug/nrf52840/libaliro.a differ diff --git a/lib/aliro/bin/debug/nrf5340/libaliro.a b/lib/aliro/bin/debug/nrf5340/libaliro.a new file mode 100644 index 00000000..359401ba Binary files /dev/null and b/lib/aliro/bin/debug/nrf5340/libaliro.a differ diff --git a/lib/aliro/bin/debug/nrf5340/libaliro_ble.a b/lib/aliro/bin/debug/nrf5340/libaliro_ble.a new file mode 100644 index 00000000..47e835f5 Binary files /dev/null and b/lib/aliro/bin/debug/nrf5340/libaliro_ble.a differ diff --git a/lib/aliro/bin/debug/nrf54l15/libaliro.a b/lib/aliro/bin/debug/nrf54l15/libaliro.a new file mode 100644 index 00000000..359401ba Binary files /dev/null and b/lib/aliro/bin/debug/nrf54l15/libaliro.a differ diff --git a/lib/aliro/bin/debug/nrf54lm20a/libaliro.a b/lib/aliro/bin/debug/nrf54lm20a/libaliro.a new file mode 100644 index 00000000..359401ba Binary files /dev/null and b/lib/aliro/bin/debug/nrf54lm20a/libaliro.a differ diff --git a/lib/aliro/bin/debug/nrf54lm20a/libaliro_ble.a b/lib/aliro/bin/debug/nrf54lm20a/libaliro_ble.a new file mode 100644 index 00000000..47e835f5 Binary files /dev/null and b/lib/aliro/bin/debug/nrf54lm20a/libaliro_ble.a differ diff --git a/lib/aliro/bin/nrf52840/libaliro.a b/lib/aliro/bin/nrf52840/libaliro.a deleted file mode 100644 index 8e3536dc..00000000 Binary files a/lib/aliro/bin/nrf52840/libaliro.a and /dev/null differ diff --git a/lib/aliro/bin/nrf5340/libaliro.a b/lib/aliro/bin/nrf5340/libaliro.a deleted file mode 100644 index f6dbcdf6..00000000 Binary files a/lib/aliro/bin/nrf5340/libaliro.a and /dev/null differ diff --git a/lib/aliro/bin/nrf5340/libaliro_ble.a b/lib/aliro/bin/nrf5340/libaliro_ble.a deleted file mode 100644 index cc40b7dd..00000000 Binary files a/lib/aliro/bin/nrf5340/libaliro_ble.a and /dev/null differ diff --git a/lib/aliro/bin/nrf54l15/libaliro.a b/lib/aliro/bin/nrf54l15/libaliro.a deleted file mode 100644 index bee9f695..00000000 Binary files a/lib/aliro/bin/nrf54l15/libaliro.a and /dev/null differ diff --git a/lib/aliro/bin/nrf54lm20a/libaliro.a b/lib/aliro/bin/nrf54lm20a/libaliro.a deleted file mode 100644 index fe03efb2..00000000 Binary files a/lib/aliro/bin/nrf54lm20a/libaliro.a and /dev/null differ diff --git a/lib/aliro/bin/nrf54lm20a/libaliro_ble.a b/lib/aliro/bin/nrf54lm20a/libaliro_ble.a deleted file mode 100644 index 32fc51b2..00000000 Binary files a/lib/aliro/bin/nrf54lm20a/libaliro_ble.a and /dev/null differ diff --git a/lib/aliro/bin/release/nrf52840/libaliro.a b/lib/aliro/bin/release/nrf52840/libaliro.a new file mode 100644 index 00000000..1a264652 Binary files /dev/null and b/lib/aliro/bin/release/nrf52840/libaliro.a differ diff --git a/lib/aliro/bin/release/nrf5340/libaliro.a b/lib/aliro/bin/release/nrf5340/libaliro.a new file mode 100644 index 00000000..36d42795 Binary files /dev/null and b/lib/aliro/bin/release/nrf5340/libaliro.a differ diff --git a/lib/aliro/bin/release/nrf5340/libaliro_ble.a b/lib/aliro/bin/release/nrf5340/libaliro_ble.a new file mode 100644 index 00000000..9272d3b0 Binary files /dev/null and b/lib/aliro/bin/release/nrf5340/libaliro_ble.a differ diff --git a/lib/aliro/bin/release/nrf54l15/libaliro.a b/lib/aliro/bin/release/nrf54l15/libaliro.a new file mode 100644 index 00000000..36d42795 Binary files /dev/null and b/lib/aliro/bin/release/nrf54l15/libaliro.a differ diff --git a/lib/aliro/bin/release/nrf54lm20a/libaliro.a b/lib/aliro/bin/release/nrf54lm20a/libaliro.a new file mode 100644 index 00000000..36d42795 Binary files /dev/null and b/lib/aliro/bin/release/nrf54lm20a/libaliro.a differ diff --git a/lib/aliro/bin/release/nrf54lm20a/libaliro_ble.a b/lib/aliro/bin/release/nrf54lm20a/libaliro_ble.a new file mode 100644 index 00000000..9272d3b0 Binary files /dev/null and b/lib/aliro/bin/release/nrf54lm20a/libaliro_ble.a differ diff --git a/lib/aliro/include/aliro/aliro.h b/lib/aliro/include/aliro/aliro.h index 26887615..18ce1b87 100644 --- a/lib/aliro/include/aliro/aliro.h +++ b/lib/aliro/include/aliro/aliro.h @@ -6,57 +6,34 @@ #pragma once -#include "access_manager/access_manager.h" +#include "aliro/connection_handle.h" #include "aliro/errors.h" #include "aliro/protocol_version.h" #include "aliro/types.h" -#include "kpersistent_manager/kpersistent_manager.h" -#include "transport/ble/ble_iface.h" -#ifdef CONFIG_DOOR_LOCK_BLE_UWB +#ifdef CONFIG_NCS_ALIRO_BLE_UWB #include "aliro/ble_types.h" -#endif // CONFIG_DOOR_LOCK_BLE_UWB +#endif // CONFIG_NCS_ALIRO_BLE_UWB #include -namespace Aliro { - -struct AliroConfig { - /** - * @brief Enable NFC transport. - */ - bool mEnableNfc{ true }; +#include - /** - * @brief The Kpersistent manager needed for the Expedited-fast phase. - */ - [[maybe_unused]] KpersistentManager *mKpersistentManager{}; +namespace Aliro { - /** - * @brief The BLE interface. - */ - BleInterface::BleIfc *mBle{}; -}; +/** + * @brief Aliro feature bitmap bit positions. + * + */ +static constexpr uint8_t kFeatureExpeditedFastPhaseSupported = static_cast(BIT(0)); +static constexpr uint8_t kFeatureStepUpPhaseSupported = static_cast(BIT(1)); +static constexpr uint8_t kFeatureBleUwbSupported = static_cast(BIT(2)); /** * @brief Aliro stack. */ class AliroStack { public: - /** - * @brief Aliro stack callbacks. - */ - struct Callbacks { - /** - * @brief Callback for errors. - * - * This callback is called when an error occurs. - * - * @param error The error that occurred. - */ - void (*mOnError)(AliroError error){ nullptr }; - }; - /** * @brief Gets the instance of the Aliro stack. * @@ -71,123 +48,149 @@ class AliroStack { /** * @brief Initializes the Aliro stack. * - * @param callbacks The Access callbacks. - * @param config The Aliro configuration. - * * @return ALIRO_NO_ERROR if the stack was initialized successfully, an error code otherwise. */ - AliroError Init(const Callbacks &callbacks, const AliroConfig &config); + AliroError Init(); /** - * @brief Provision the Reader. - * - * @param privateKeyId The private key ID. - * @param groupResolvingKeyId The group resolving key ID. - * @param identifier The reader identifier. - * @param credentialIssuerCAPublicKeyId The credential issuer CA public key ID. + * @brief Gets the Aliro library version string. * - * @return ALIRO_NO_ERROR if the Reader was provisioned successfully, an error code otherwise. + * @return The Aliro library version. */ - AliroError Provision(CryptoTypes::KeyId privateKeyId, CryptoTypes::KeyId groupResolvingKeyId, - const Identifier &identifier, CryptoTypes::KeyId credentialIssuerCAPublicKeyId); + static const char *GetLibraryVersion(); /** - * @brief Sets the reader identifier. + * @brief Gets the Expedited Standard protocol version. * - * @param identifier The reader identifier. - */ - AliroError SetReaderIdentifier(const Identifier &identifier); - - /** - * @brief Sets the credential issuer CA public key ID. + * @param versionCount The number of protocol versions. * - * @param credentialIssuerCAPublicKeyId The credential issuer CA public key ID. + * @return Pointer to the static Expedited Standard protocol version list. */ - void SetCredentialIssuerCAPublicKeyId(CryptoTypes::KeyId credentialIssuerCAPublicKeyId) const; + const ProtocolVersion *GetExpeditedStandardProtocolVersions(size_t &versionCount) const; /** - * @brief Starts the Aliro stack. + * @brief Gets the enabled features bitmap based on Kconfig options. * - * @return ALIRO_NO_ERROR if the stack was started successfully, an error code otherwise. + * Bit mapping: + * - Bit 0: Expedited-fast Phase + * - Bit 1: Step-up Phase + * - Bit 2: BLE UWB + * + * @return Bitmap of enabled features. */ - AliroError Start() const; + uint8_t GetFeatures() const; /** - * @brief Stops the Aliro stack. + * @brief Create a session for a transport connection. + * + * Call this when an NFC card is detected and activated or when a BLE connection + * is ready to exchange data. * - * @return ALIRO_NO_ERROR if the stack was stopped successfully, an error code otherwise. + * @param connectionHandle The connection handle identifying the transport connection. */ - AliroError Stop() const; + void CreateSession(ConnectionHandle connectionHandle); /** - * @brief Gets the Aliro configuration. + * @brief Destroy the session associated with a connection handle. + * + * Call this when a transport connection is closed or when the application + * needs to terminate a session and free resources. * - * @return The Aliro configuration. + * @param connectionHandle The connection handle identifying the session. */ - const AliroConfig &GetConfig() const { return mConfig; } + void DestroySession(ConnectionHandle connectionHandle); /** - * @brief Gets the Aliro library version string. + * @brief Handle data received over a transport connection. * - * @return The Aliro library version. + * Call this when data is received from an NFC card or a connected BLE device. + * + * @param handle The connection handle identifying the transport connection. + * @param data The received data. */ - static const char *GetLibraryVersion(); + void HandleSessionData(ConnectionHandle handle, Data data); /** - * @brief Gets the Expedited Standard protocol version. - - * @param versionCount The number of protocol versions. + * @brief Process one deferred stack event. * - * @return Pointer to the static Expedited Standard protocol version list. + * Call this from application-owned execution context with an opaque event + * pointer previously queued through `Aliro::Interface::Os::QueueEvent()`. + * + * @param event Opaque pointer to stack event object. */ - const ProtocolVersion *GetExpeditedStandardProtocolVersions(size_t &versionCount) const; + void ProcessEvent(void *event); -#ifdef CONFIG_DOOR_LOCK_BLE_UWB +#ifdef CONFIG_NCS_ALIRO_BLE_UWB /** - * @brief Sends the Reader Status Changed Message ID. + * @brief Generate Aliro BLE advertising service data. * - * @param operationSource The operation source that caused the state change. - * @param readerState The current reader state. - * @param sessionContext Optionally the session context for specific User Device. + * Call this to get the advertising payload when starting or refreshing + * BLE advertising. The application provides BLE-specific parameters + * (address, TX power, notification, expiration time) that it controls. + * + * @param outData The output advertising service data. + * @param address The current BLE advertising address. + * @param txPowerLevel The current TX power level in dBm. + * @param readerIdentifier The reader identifier (32 bytes: reader_group_id | reader_group_sub_id). + * @param notification The notification type. + * @param expirationTime The expiration timestamp. * - * @return ALIRO_NO_ERROR if the Reader Status Changed Message ID is sent successfully, an error code otherwise. + * @return ALIRO_NO_ERROR on success, error code otherwise. */ - AliroError SendReaderStatusChangedMessage( - OperationSource operationSource, ReaderStateByte readerState, - std::optional sessionContext = std::nullopt) const; + static AliroError + GenerateAdvertisingData(BleTypes::AdvertisingServiceData &outData, const BleTypes::BleAddress &address, + BleTypes::TxPowerLevel txPowerLevel, const Identifier &readerIdentifier, + BleTypes::AdvertisingServiceData::Notification notification = + BleTypes::AdvertisingServiceData::Notification::NoError, + const BleTypes::BleExpiryTimestamp &expirationTime = BleTypes::kExpiryTimeUnavailable); /** - * @brief Sets the BLE notification which will be advertised. - * - * @param notification The BLE notification. + * @brief Gets the BLE advertising version. * - * @return ALIRO_NO_ERROR if the notification was set successfully, an error code otherwise. + * @return The BLE advertising version. */ - AliroError SetBleNotification(BleTypes::AdvertisingServiceData::Notification notification) const; + static uint8_t GetBleAdvertisingVersion(); /** - * @brief Gets the BLE advertising version. + * @brief Sends the Reader Status Changed Message. * - * @return The BLE advertising version. + * @param operationSource The operation source that caused the state change. + * @param readerState The current reader state. + * @param sessionContext Optionally the session context for specific User Device. + * + * @return ALIRO_NO_ERROR if successful, an error code otherwise. */ - uint8_t GetBleAdvertisingVersion() const; + AliroError SendReaderStatusChangedMessage(OperationSource operationSource, ReaderStateByte readerState, + std::optional sessionContext = std::nullopt) const; /** * @brief Gets the BLE/UWB protocol version. - + * * @param versionCount The number of protocol versions. * * @return Pointer to the static BLE/UWB protocol version list. */ const ProtocolVersion *GetBleUwbProtocolVersions(size_t &versionCount) const; -#endif // CONFIG_DOOR_LOCK_BLE_UWB + /** + * @brief Sends a BLE message from the UWB module to the stack. + * + * This function is called by the application to send messages that require + * stack-level encryption and transmission. The stack encrypts the message payload and + * sends it over the BLE connection. + * + * @note The following message types may be sent through this function: + * - Notification with Ranging Message ID + * - UWB Ranging Service + * + * @param connectionHandle The connection handle identifying the BLE connection. + * @param data Pointer to the unencrypted message. + * @param length Length of the message in bytes. + */ + void SendBleMessage(ConnectionHandle connectionHandle, const uint8_t *data, size_t length) const; -private: - Callbacks mCallbacks{}; - AliroConfig mConfig; - bool mProvisioned{ false }; +#endif // CONFIG_NCS_ALIRO_BLE_UWB }; } // namespace Aliro diff --git a/lib/aliro/include/aliro/ble_types.h b/lib/aliro/include/aliro/ble_types.h index 3aced860..60db3ee1 100644 --- a/lib/aliro/include/aliro/ble_types.h +++ b/lib/aliro/include/aliro/ble_types.h @@ -9,7 +9,6 @@ #include "aliro/protocol_version.h" #include -#include #include #include diff --git a/lib/aliro/include/aliro/connection_handle.h b/lib/aliro/include/aliro/connection_handle.h new file mode 100644 index 00000000..7f319c53 --- /dev/null +++ b/lib/aliro/include/aliro/connection_handle.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause + */ + +#pragma once + +#include + +extern "C" { +/** + * @brief Forward declaration of bt_conn. + */ +struct bt_conn; +} + +namespace Aliro { + +/** + * @brief Unified connection handle for NFC and BLE transport layers. + * + * The ConnectionHandle class provides a type-safe way to identify connections + * across different transport protocols. It can represent either an NFC connection + * (which doesn't have a persistent connection object) or a BLE connection + * (which uses a bt_conn pointer). + * + * NFC connections are represented by a special sentinel value, while BLE + * connections store the actual bt_conn pointer. + */ +class ConnectionHandle { + using HandleType = const void *; + + /** @brief Dummy connection object used as a sentinel for NFC connections. */ + static constexpr int kNfcDummyConnection{}; + /** @brief Connection handle sentinel value for NFC connections. */ + static constexpr HandleType kNfcConnectionHandle{ &kNfcDummyConnection }; + +public: + /** + * @brief Create a connection handle for an NFC connection. + * + * @return A ConnectionHandle instance representing an NFC connection. + */ + static constexpr ConnectionHandle Nfc() { return ConnectionHandle(kNfcConnectionHandle); } + + /** + * @brief Create a connection handle for a BLE connection. + * + * @param connection Pointer to the BLE connection structure. + * @return A ConnectionHandle instance representing the BLE connection. + */ + static ConnectionHandle Ble(bt_conn *connection) { return ConnectionHandle(connection); } + + /** + * @brief Check if this connection handle represents an NFC connection. + * + * @return true if this is an NFC connection, false otherwise. + */ + constexpr bool IsNfc() const { return mHandle == kNfcConnectionHandle; } + + /** + * @brief Check if this connection handle represents a BLE connection. + * + * @return true if this is a BLE connection, false otherwise. + */ + constexpr bool IsBle() const { return mHandle != kNfcConnectionHandle; } + + /** + * @brief Get the raw connection handle. + * + * @return The underlying connection pointer. For NFC connections, this + * returns the sentinel value. For BLE connections, this returns + * the bt_conn pointer. + */ + constexpr HandleType GetRaw() const { return mHandle; } + + /** + * @brief Get the BLE connection pointer. + * + * @return Pointer to the bt_conn structure. + */ + bt_conn *GetBtConn() const + { + if (!IsBle()) { + __ASSERT(false, "Invalid connection handle"); + } + + return static_cast(const_cast(mHandle)); + } + + /** + * @brief Compare two connection handles for equality. + * + * @param other The connection handle to compare with. + * @return true if both connection handles represent the same connection, + * false otherwise. + */ + constexpr bool operator==(const ConnectionHandle &other) const { return mHandle == other.mHandle; } + +private: + /** + * @brief Private constructor to create a connection handle. + * + * @param connection The connection pointer or sentinel value. + */ + constexpr explicit ConnectionHandle(HandleType connection) : mHandle(connection) {} + + /** @brief The underlying connection handle. */ + HandleType mHandle; +}; + +} /* namespace Aliro */ diff --git a/lib/aliro/include/aliro/errors.h b/lib/aliro/include/aliro/errors.h index 2a07ae66..c8f924c2 100644 --- a/lib/aliro/include/aliro/errors.h +++ b/lib/aliro/include/aliro/errors.h @@ -16,9 +16,9 @@ enum AliroErrorCode : uint8_t { ALIRO_INVALID_ARGUMENT, ALIRO_INVALID_SIGNATURE, ALIRO_INVALID_AUTHENTICATION_TAG, - ALIRO_INVALID_PUBLIC_KEY, - ALIRO_INVALID_ACCESS_DOCUMENT, ALIRO_PUBLIC_KEY_NOT_FOUND, + ALIRO_PUBLIC_KEY_EXPIRED, + ALIRO_PUBLIC_KEY_NOT_TRUSTED, ALIRO_KEY_ALREADY_EXISTS, ALIRO_TIMEOUT, ALIRO_ERROR_NOT_IMPLEMENTED, @@ -36,6 +36,9 @@ enum AliroErrorCode : uint8_t { ALIRO_APDU_STATUS_INVALID, ALIRO_ENCRYPTION_COUNTER_OVERFLOW, ALIRO_DECRYPTION_COUNTER_OVERFLOW, + ALIRO_INVALID_DATA_FORMAT, + ALIRO_INVALID_DATA_CONTENT, + ALIRO_NO_PUBLIC_KEY_IN_RESPONSE, ALIRO_ERROR_MAX, }; diff --git a/lib/aliro/include/aliro/interface.h b/lib/aliro/include/aliro/interface.h index 0f62fd2c..f5f43a22 100644 --- a/lib/aliro/include/aliro/interface.h +++ b/lib/aliro/include/aliro/interface.h @@ -6,12 +6,138 @@ #pragma once +#include "aliro/connection_handle.h" #include "aliro/errors.h" +#include "aliro/protocol_version.h" +#include "aliro/time.h" #include "aliro/types.h" +#include + namespace Aliro::Interface { -namespace ReaderCertificate { +namespace Access { + +/** + * @brief Parameters for the Access Document request. + */ +struct AccessDocumentRequestParams { + /** + * @brief The data element identifier of the Access Document to be requested. + */ + ConstData mElementIdentifier; + /** + * @brief Indicates the intent to store the Access Document. + */ + bool mIntentToStore; +}; + +/** + * @brief Gets the parameters for the Access Document request. + * + * @param publicKey The public key of the User Device. + * @param credentialSignedTimestamp The credential signed timestamp. + * + * @return The parameters for the Access Document request if the document should be requested, std::nullopt otherwise. + */ +std::optional +GetAccessDocumentRequestParameters(const CryptoTypes::PublicKey &publicKey, + const std::optional &credentialSignedTimestamp); + +/** + * @brief Processes the Access Request from Expedited-standard and Step-up phases. + * + * @param handle The connection handle identifying the session. + * @param userPublicKey The User Device public key. + * @param kpersistentKeyId The volatile Kpersistent key derived during Expedited-standard phase. + * @param accessDocument The optional Access Document presented by the User Device during Step-up phase. + * + * @return ALIRO_NO_ERROR when the access is granted, error code otherwise. + */ +AliroError +ProcessAccessRequest(ConnectionHandle handle, const CryptoTypes::PublicKey &userPublicKey, + CryptoTypes::KeyId kpersistentKeyId, + const std::optional &accessDocument = std::nullopt); + +/** + * @brief Processes the Access Request from Expedited-fast phase. + * + * @param handle The connection handle identifying the session. + * @param kpersistentKeyId The persistent Kpersistent key ID used to authenticate the User Device during Expedited-fast + * phase. + * + * @return ALIRO_NO_ERROR when the access is granted, error code otherwise. + */ +AliroError ProcessAccessRequest(ConnectionHandle handle, CryptoTypes::KeyId kpersistentKeyId); + +/** + * @brief Get a Credential Issuer public key by its identifier. + * + * @param keyIdentifier The key identifier of the Credential Issuer public key. + * @param publicKey The public key to get. + * + * @return ALIRO_NO_ERROR on success, error code on failure. + */ +AliroError GetCredentialIssuerPublicKey(const CryptoTypes::KeyIdentifier &keyIdentifier, + CryptoTypes::PublicKey &publicKey); + +/** + * @brief Get the number of Kpersistent keys. + * + * @param count The number of Kpersistent keys. + * + * @return ALIRO_NO_ERROR when success, an error code otherwise. + */ +AliroError GetKpersistentCount(size_t &count); + +/** + * @brief Get the IDs of the Kpersistent keys. + * + * The caller must first query @ref GetKpersistentCount and allocate a buffer + * large enough to hold all key IDs. + * + * @param keyIds Output array for Kpersistent key IDs. Must point to a writable + * buffer with capacity for at least @p count entries when @p count is greater + * than zero. + * @param count Input/output number of entries in @p keyIds: + * - on input: capacity of @p keyIds (in number of KeyId entries), + * - on output: number of key IDs written to @p keyIds. + * + * @return ALIRO_NO_ERROR when success, an error code otherwise. + */ +AliroError GetKpersistentKeyIds(CryptoTypes::KeyId *keyIds, size_t &count); + +/** + * @brief Get the Access Credential public key corresponding to the Kpersistent key. + * + * @param kpersistentKeyId The ID of the Kpersistent key. + * @param publicKey The Access Credential public key. + * + * @return ALIRO_NO_ERROR when success, an error code otherwise. + */ +AliroError GetAccessCredentialPublicKey(CryptoTypes::KeyId kpersistentKeyId, CryptoTypes::PublicKey &publicKey); + +} // namespace Access + +namespace Reader { + +/** + * @brief Gets the Reader identifier. + * + * @param identifier The Reader identifier. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ +AliroError GetIdentifier(Identifier &identifier); + +/** + * @brief Gets the Reader public key. + * + * @param publicKey The Reader public key. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ +AliroError GetPublicKey(CryptoTypes::PublicKey &publicKey); /** * @brief Checks if the Reader certificate and Reader System Issuer public key are provisioned. @@ -21,7 +147,7 @@ namespace ReaderCertificate { * * @return True if the Reader certificate and Reader System Issuer public key are provisioned, false otherwise. */ -bool IsProvisioned(); +bool IsCertificateProvisioned(); /** * @brief Gets the Reader System Issuer public key. @@ -41,6 +167,466 @@ AliroError GetIssuerPublicKey(CryptoTypes::PublicKey &publicKey); */ AliroError GetCertificate(ConstData &certificate); -} // namespace ReaderCertificate +} // namespace Reader + +#if CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE > 0 + +namespace Logging { + +/** + * @brief Writes a formatted log message with a platform log level. + * + * @param platformLogLevel The platform log level. + * @param logFormat The printf-style format string. + * @param ... Arguments for the format string. + */ +void Log(uint8_t platformLogLevel, const char *logFormat, ...); + +/** + * @brief Logs binary data in hexadecimal format with a platform log level. + * + * @param platformLogLevel The platform log level. + * @param data Pointer to the data buffer. + * @param size Size of the data buffer. + * @param str Description string prefixed to the hexdump. + */ +void LogHexdump(uint8_t platformLogLevel, const void *data, size_t size, const char *str); + +} // namespace Logging + +#endif // CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE > 0 + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + +namespace Ble { + +/** + * @brief BLE interface functions for Aliro stack integration. + * + * This section declares the functions that the application must implement + * to integrate BLE transport with the Aliro stack. The application is responsible for: + * - BLE stack initialization + * - Advertising lifecycle (start/stop/update) + * - Connection management + * + * These functions must be implemented in the application scope. + */ + +/** + * @brief Get the maximum number of concurrent BLE sessions supported. + * + * @return The maximum number of concurrent BLE sessions. + */ +size_t GetMaxSessions(); + +/** + * @brief Get the BLE/UWB protocol version for the given connection. + * + * @param handle The connection handle to get the protocol version for. + * + * @return The protocol version. + */ +ProtocolVersion GetProtocolVersion(ConnectionHandle handle); + +} // namespace Ble + +namespace Uwb { + +/** + * @brief Handles BLE messages for the UWB module. + * + * This function is called by the stack to forward certain BLE messages that require + * application-level handling. The stack performs decryption and then passes the + * message with decrypted payload to this function. + * + * @note The following message types are forwarded to this function: + * - Notification with Ranging Message ID + * - UWB Ranging Service + * + * @param connectionHandle The connection handle identifying the BLE connection. + * @param data Pointer to the decrypted message. + * @param length Length of the message. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ +AliroError HandleBleMessage(ConnectionHandle connectionHandle, const uint8_t *data, size_t length); + +} // namespace Uwb + +#endif // CONFIG_NCS_ALIRO_BLE_UWB + +namespace Session { + +/** + * @brief Send data for a session over the active transport. + * + * @param handle The connection handle identifying the session. + * @param data The data to send. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ +AliroError Send(ConnectionHandle handle, Data data); + +/** + * @brief Handle a session termination event. + * + * Called by the stack after a session is destroyed to allow the application + * to perform transport teardown and any application-level cleanup. + * + * @param handle The connection handle of the terminated session. + */ +void HandleTermination(ConnectionHandle handle); + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + +/** + * @brief Starts a UWB ranging session. + * + * @param handle The connection handle identifying the session. + * @param rangingSessionId The ranging session ID. + * @param ursk The ranging session key. + * @param protocolVersion The protocol version to use for the ranging session. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ +AliroError StartRangingSession(ConnectionHandle handle, uint32_t rangingSessionId, const CryptoTypes::Ursk &ursk, + ProtocolVersion protocolVersion); + +#endif // CONFIG_NCS_ALIRO_BLE_UWB + +} // namespace Session + +namespace CredentialIssuerCertificate { + +/** + * @brief Structure representing certificate validity timestamps. + */ +struct CertificateTimestamps { + Time mValidFrom; + Time mValidUntil; +}; + +/** + * @brief Validates an X.509 certificate and extracts the public key and timestamps. + * + * @param certificate The DER-encoded certificate to validate. + * @param publicKey Output parameter for the public key extracted from the certificate. + * @param timestamps Output parameter for the certificate validity timestamps (valid from/until). + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ +AliroError Validate(const ConstData &certificate, CryptoTypes::PublicKey &publicKey, + std::optional ×tamps); + +} // namespace CredentialIssuerCertificate + +namespace AccessDocument { + +/** + * @brief Verifies the current time against a validity period. + * + * Checks whether the current time falls within the specified validity period + * defined by the validFrom and validUntil timestamps. + * + * @param validFrom The start of the validity period (inclusive). + * @param validUntil The end of the validity period (inclusive). + * + * @return True if the current time is within the validity period, false otherwise. + * std::nullopt if the verification is not possible. + */ +std::optional VerifyValidityPeriod(const Time &validFrom, const Time &validUntil); + +} // namespace AccessDocument + +namespace Os { + +/** + * @brief Queue an opaque stack event for deferred processing. + * + * Stack allocates and owns the event object. The application must enqueue the + * pointer and later pass the same pointer back to `AliroStack::ProcessEvent()` + * from appropriate execution context (for example: workqueue). + * + * @param event Opaque pointer to a stack event object. + * + * @return ALIRO_NO_ERROR on success, error code otherwise. + */ +AliroError QueueEvent(void *event); + +namespace Mutex { + +/** + * @brief Lock stack mutex. + * + * Mutex storage and backend implementation are application-owned. + * The stack uses this API to guard access to shared resources. + */ +void Lock(); + +/** + * @brief Unlock stack mutex. + */ +void Unlock(); + +} // namespace Mutex + +namespace Timer { + +/** @brief Opaque handle returned by the OS timer backend. */ +using Handle = int; + +/** @brief Invalid timer handle. */ +constexpr Handle kInvalidHandle{ -1 }; + +/** @brief Callback invoked when timer expires. */ +using Callback = void (*)(void *context); + +/** + * @brief Acquire timer slot from application-owned pool. + * + * @param callback Callback invoked on timer expiration. + * @param context User context passed to callback. + * + * @return Opaque handle to acquired timer slot, kInvalidHandle on failure. + */ +Handle Acquire(Callback callback, void *context); + +/** + * @brief Release previously acquired timer slot. + * If timer is currently running, it is stopped before release. + * + * @param handle Timer handle returned by Acquire(). + */ +void Release(Handle handle); + +/** + * @brief Start or restart timer with timeout. + * + * @param handle Timer handle returned by Acquire(). + * @param timeoutMs Timeout in milliseconds. + */ +void Start(Handle handle, uint32_t timeoutMs); + +/** + * @brief Stop timer. + * + * @param handle Timer handle returned by Acquire(). + */ +void Stop(Handle handle); + +/** + * @brief Check if timer is running. + * + * @param handle Timer handle returned by Acquire(). + * + * @return true if running, false otherwise. + */ +bool IsRunning(Handle handle); + +} // namespace Timer + +} // namespace Os + +namespace Crypto { + +/** + * @brief Generate random bytes. + * + * @param buffer Output buffer for storing the generated bytes. + * @param bufferLength Input number of bytes to generate. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError GenerateRandom(uint8_t *buffer, size_t bufferLength); + +/** + * @brief Generate ephemeral EC key pair. + * + * @param keyId Output identifier for newly created keys. + * @param ephemeralPubKey Output buffer where the public key is to be copied. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError GenerateEphemeralKeyPair(CryptoTypes::KeyId &keyId, CryptoTypes::PublicKey &ephemeralPubKey); + +/** + * @brief Import a shared key. + * + * @param key input buffer with the key. + * @param keyLength length of the key in bytes. + * @param keyId output identifier of the imported key. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError ImportSharedKey(const uint8_t *key, size_t keyLength, CryptoTypes::KeyId &keyId); + +/** + * @brief Import a symmetric key. + * + * @param key input buffer with the key. + * @param keyLength length of the key in bytes. + * @param keyId output identifier of the imported key. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError ImportSymmetricKey(const uint8_t *key, size_t keyLength, CryptoTypes::KeyId &keyId); + +/** + * @brief Destroy an key by ID. + * + * @param keyId identifier of a key to delete. + * NOTE: The function may set a ID to value 0. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError DestroyKey(CryptoTypes::KeyId &keyId); + +/** + * @brief Generate signature for a message using Reader private key. + * + * @param msg input message to sign. + * @param msgLength input size of the message. + * @param signature output buffer where the signature is to be copied. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError GenerateSignature(const uint8_t *msg, const size_t msgLength, CryptoTypes::Signature &signature); + +/** + * @brief Verify signature of a message. + * + * @param publicKey input public key to use for verification. + * @param msg input message whose signature is to be verified. + * @param msgLength input size of the message. + * @param signature input signature to verify. + * + * @return ALIRO_NO_ERROR when signature is valid, ALIRO_INVALID_SIGNATURE otherwise. + */ +AliroError VerifySignature(const CryptoTypes::PublicKey &publicKey, const uint8_t *msg, const size_t msgLength, + const CryptoTypes::Signature &signature); + +/** + * @brief Perform a raw key agreement. + * + * @param keyId input identifier of the private key to use for key agreement. + * @param peerPublicKey input public key of the peer to use for key agreement. + * @param sharedSecret output buffer where the shared secret is to be copied. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError RawKeyAgreement(CryptoTypes::KeyId keyId, const CryptoTypes::PublicKey &peerPublicKey, + CryptoTypes::SharedSecret &sharedSecret); + +/** + * @brief Derive a shared key. + * + * @param keyId input identifier of the key to use for derivation. + * @param info input additional info for the derivation. + * @param infoLength input size of the info. + * @param salt input salt for the derivation. + * @param saltLength input size of the salt. + * @param outputKeyId output identifier of the derived key. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError DeriveSharedKey(CryptoTypes::KeyId keyId, const uint8_t *info, size_t infoLength, const uint8_t *salt, + size_t saltLength, CryptoTypes::KeyId &outputKeyId); + +/** + * @brief Derive a symmetric key. + * + * @param keyId input identifier of the key to use for derivation. + * @param info input additional info for the derivation. + * @param infoLength input size of the info. + * @param salt input salt for the derivation. + * @param saltLength input size of the salt. + * @param outputKeyId output identifier of the derived key. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError DeriveSymmetricKey(CryptoTypes::KeyId keyId, const uint8_t *info, size_t infoLength, const uint8_t *salt, + size_t saltLength, CryptoTypes::KeyId &outputKeyId); + +/** + * @brief Derive a key raw. + * + * @param keyId input identifier of the key to use for derivation. + * @param info input additional info for the derivation. + * @param infoLength input size of the info. + * @param salt input salt for the derivation. + * @param saltLength input size of the salt. + * @param outputKey output buffer for the derived key. + * @param outputKeyLength input size of the output key. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError DeriveRawKey(CryptoTypes::KeyId keyId, const uint8_t *info, size_t infoLength, const uint8_t *salt, + size_t saltLength, uint8_t *outputKey, size_t outputKeyLength); + +/** + * @brief Authenticated encryption with additional data. + * + * @param keyId input identifier of the key to use for encryption. + * @param plainTxt input raw payload to encrypt. + * @param plainTxtLength input size of the payload. + * @param additionalData input additional data. + * @param additionalDataLength input size of the additional data. + * @param nonce input a nonce to use for operation. + * @param cipherText output encrypted payload. + * @param authTag output authentication tag. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError AeadEncrypt(CryptoTypes::KeyId keyId, const uint8_t *plainTxt, size_t plainTxtLength, + const uint8_t *additionalData, size_t additionalDataLength, const CryptoTypes::Nonce &nonce, + uint8_t *cipherText, CryptoTypes::AuthenticationTag &authTag); + +/** + * @brief Authenticated decryption with additional data. + * + * @param keyId input identifier of the key to use for decryption. + * @param cipherTextWithTag input encrypted and authenticated data. The buffer must contains the encrypted data + * followed by authentication tag. + * @param cipherTextWithTagLength input size of the cipherText buffer. + * @param additionalData input additional data that has been authenticated but not encrypted. + * @param additionalDataLength input size of the additional data. + * @param nonce input a nonce to use for operation with fixed size. + * @param plainText output buffer for decrypted data. + * @param plainTextLength input/output size of the plainText buffer. On success the size of the output after + * decryption. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError AeadDecrypt(CryptoTypes::KeyId keyId, const uint8_t *cipherTextWithTag, size_t cipherTextWithTagLength, + const uint8_t *additionalData, size_t additionalDataLength, const CryptoTypes::Nonce &nonce, + uint8_t *plainText, size_t &plainTextLength); + +#ifdef CONFIG_NCS_ALIRO_BLE_UWB + +/** + * @brief Encrypt data using Group Resolving Key. + * + * @param plainText input raw payload to encrypt. + * @param plainTextLength input size of the payload. + * @param cipherText output encrypted payload. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError Encrypt(const uint8_t *plainText, size_t plainTextLength, uint8_t *cipherText); + +#endif // CONFIG_NCS_ALIRO_BLE_UWB + +/** + * @brief Computes the SHA-256 hash of the data. + * + * @param data The data to compute the hash of. + * @param dataLength The length of the data. + * @param hash The computed SHA-256 hash of the data. + * + * @return ALIRO_NO_ERROR on success, error status otherwise. + */ +AliroError Sha256(const uint8_t *data, size_t dataLength, CryptoTypes::Sha256Hash &hash); + +} // namespace Crypto } // namespace Aliro::Interface diff --git a/lib/aliro/include/aliro/transport_callbacks.h b/lib/aliro/include/aliro/transport_callbacks.h deleted file mode 100644 index ffac67b4..00000000 --- a/lib/aliro/include/aliro/transport_callbacks.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#pragma once - -#include "aliro/errors.h" -#include "aliro/types.h" - -namespace Aliro { - -/** - * @brief Opaque connection handle for platform implementations. - * - * This handle represents a connection without exposing internal connection details. - * Platform implementations should treat this as an opaque pointer. - */ -using ConnectionHandle = void *; - -/** - * @brief Transport error source identifier. - */ -using TransportErrorSource = uint8_t; - -/** - * @brief Transport event callbacks template for platform implementations. - * - * This template provides a generic callback structure that can be specialized - * for different connection handle types. Platform implementations should use - * the PlatformTransportCallbacks alias. - * - * @tparam T The connection identifier type - */ -template struct TransportCallbacks { - /** - * @brief Called when data is received and can be further processed. - * - * @param connection Connection identifier - * @param data The received data - */ - void (*mOnDataReceived)(T connection, Data data){ nullptr }; - - /** - * @brief Called when transport is ready and data can be exchanged. - * - * @param connection Connection identifier - */ - void (*mOnTransportReady)(T connection){ nullptr }; - - /** - * @brief Called on transport error. - * - * @param connection Connection identifier - * @param error The error code - * @param source The error source - */ - void (*mOnError)(T connection, AliroError error, TransportErrorSource source){ nullptr }; - - /** - * @brief Called when transport medium is lost. - * - * @param connection Connection identifier - */ - void (*mOnTransportLoss)(T connection){ nullptr }; -}; - -/** - * @brief Platform transport callbacks using opaque ConnectionHandle. - * - * These callbacks allow platform implementations to communicate - * transport events back to the Aliro stack. - */ -using PlatformTransportCallbacks = TransportCallbacks; - -} // namespace Aliro diff --git a/lib/aliro/include/aliro/types.h b/lib/aliro/include/aliro/types.h index 14c1808a..43fc410a 100644 --- a/lib/aliro/include/aliro/types.h +++ b/lib/aliro/include/aliro/types.h @@ -296,25 +296,9 @@ constexpr size_t kSha256HashLength{ 32 }; using Sha256Hash = std::array; /** - * Helper structure for representing symmetric session-bounds key IDs (respective): - * - SkReader - used for Reader's messages encryption, when secure channel is established. - * - SkDevice - used for User Device messages decryption, when secure channel is established. - * - StepUpSK - used for derive StepUpSKDevice and StepUpSKReader keys to protect Step-up phase. Available only for - * Expedited-standard phase. - * - BleSK - used to protect a ultra wideband (UWB) session setup commands. Available only for BLE session. - * - URSK - as a UWB Ranging Secret Key (URSK). Available only for BLE session. - * - * NOTE: CryptogramSK and StepUpSK cannot be derived during the same Access Protocol session. - * CryptogramSK is valid only for Expedited-fast phase and StepUpSK is valid only for Step-up-phase - * that requires the Expedited-standard (not fast) phase as a prerequisite. - */ -struct SessionBoundKeys { - KeyId mSkReader{}; - KeyId mSkDevice{}; - std::optional mStepUpSk{}; - std::optional mBleSk{}; - Ursk *mUrsk{}; -}; + * @brief Type alias for key exchange shared secret. + */ +using SharedSecret = std::array; } // namespace Aliro::CryptoTypes diff --git a/lib/aliro/include/aliro/utils.h b/lib/aliro/include/aliro/utils.h index 6a36eee5..494bd181 100644 --- a/lib/aliro/include/aliro/utils.h +++ b/lib/aliro/include/aliro/utils.h @@ -154,11 +154,3 @@ template constexpr std::underlying_type_t ToUnderlying(T e) static_assert(std::is_enum::value, "ToUnderlying called to non-enum values."); return static_cast>(e); } - -/** - * @brief Checks if buffer is empty. - */ -inline bool IsBufferEmpty(const uint8_t *data, size_t length) -{ - return (data == nullptr) || (length == 0); -} diff --git a/lib/aliro/interfaces/crypto/backend_crypto_psa/crypto_impl.h b/lib/aliro/interfaces/crypto/backend_crypto_psa/crypto_impl.h deleted file mode 100644 index fc09ebdc..00000000 --- a/lib/aliro/interfaces/crypto/backend_crypto_psa/crypto_impl.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#pragma once - -#include "crypto/backend_crypto_psa/crypto_psa.h" -#include "crypto/crypto.h" - -namespace Aliro { - -class CryptoImpl final : public Crypto, public CryptoPsa { - friend class Crypto; -}; - -/* Implementation of the generic getter. */ -inline Crypto &CryptoInstance() -{ - static CryptoImpl sCrypto; - return sCrypto; -} - -} // namespace Aliro diff --git a/lib/aliro/interfaces/crypto/backend_crypto_psa/crypto_psa.h b/lib/aliro/interfaces/crypto/backend_crypto_psa/crypto_psa.h deleted file mode 100644 index 7d095c26..00000000 --- a/lib/aliro/interfaces/crypto/backend_crypto_psa/crypto_psa.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2024 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#pragma once - -#include "aliro/errors.h" -#include "aliro/types.h" - -#include - -namespace Aliro { - -/** Concrete implementation of Crypto component for specific backend. */ -class CryptoPsa { -protected: - AliroError _Init(); - AliroError _GenerateRandom(uint8_t *rngBuf, size_t rngBufLength); - AliroError _GenerateEphemeralKeyPair(CryptoTypes::KeyId &keyId); - AliroError _ExportPublicKey(CryptoTypes::KeyId keyId, CryptoTypes::PublicKey &publicKey); - AliroError _ExportKey(CryptoTypes::KeyId keyId, uint8_t *key, size_t keyLength); - AliroError _ImportPublicKey(const CryptoTypes::PublicKey &key, CryptoTypes::KeyId &keyId, bool isPersistent); - AliroError _ImportPrivateKey(const CryptoTypes::PrivateKey &key, CryptoTypes::KeyId &keyId, bool isPersistent); - AliroError _PreserveKey(CryptoTypes::KeyId &volatileKeyId, CryptoTypes::KeyId persistentKeyId); - AliroError _DestroyKey(CryptoTypes::KeyId &keyId) const; - AliroError _GenerateSignature(CryptoTypes::KeyId privateKeyId, const uint8_t *msg, const size_t msgLength, - CryptoTypes::Signature &signature); - AliroError _VerifySignature(CryptoTypes::KeyId publicKeyId, const uint8_t *msg, const size_t msgLength, - const CryptoTypes::Signature &signature); - AliroError _ComputeSharedKeyDH(CryptoTypes::KeyId privKeyId, const CryptoTypes::PublicKey &publicKey, - const CryptoTypes::TransactionIdentifier &transactionId, - CryptoTypes::KeyId &keyDhId); - AliroError _DeriveSessionKeys(CryptoTypes::KeyId kDh, const uint8_t *info, size_t infoLength, - const uint8_t *salt, size_t saltLength, - CryptoTypes::SessionBoundKeys &sessionVolatileKeys); - AliroError _DeriveSymmetricKey(CryptoTypes::KeyId inputKeyId, const uint8_t *info, size_t infoLength, - const uint8_t *salt, size_t saltLength, CryptoTypes::KeyId &outputKeyId); - AliroError _DeriveKpersistent(CryptoTypes::KeyId inputKeyId, const uint8_t *info, size_t infoLength, - const uint8_t *salt, size_t saltLength, CryptoTypes::KeyId &outputKeyId); - AliroError _EncryptPayload(CryptoTypes::KeyId keyId, const uint8_t *plainTxt, size_t plainTxtLength, - const uint8_t *additionalData, size_t additionalDataLength, - const CryptoTypes::Nonce &nonce, uint8_t *cipherText, - CryptoTypes::AuthenticationTag &authTag); - AliroError _EncryptPayload(CryptoTypes::KeyId keyId, const uint8_t *plainTxt, size_t plainTxtLength, - uint8_t *cipherText); - AliroError _DecryptPayload(CryptoTypes::KeyId keyId, const uint8_t *cipherText, size_t cipherTextLength, - const uint8_t *additionalData, size_t additionalDataLength, - const CryptoTypes::Nonce &nonce, uint8_t *plainText, size_t &plainTextLength); - AliroError _ProvisionSymmetricKey(const uint8_t *key, size_t keyLength, CryptoTypes::KeyId &keyId, - bool isPersistent = false); - AliroError _IsKeyValid(CryptoTypes::KeyId keyId) const; - AliroError _Sha256(const uint8_t *data, size_t dataLength, CryptoTypes::Sha256Hash &hash) const; -}; - -} /* namespace Aliro */ diff --git a/lib/aliro/interfaces/crypto/crypto.h b/lib/aliro/interfaces/crypto/crypto.h deleted file mode 100644 index b1f167e7..00000000 --- a/lib/aliro/interfaces/crypto/crypto.h +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (c) 2024 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#pragma once - -#include "aliro/errors.h" -#include "aliro/types.h" - -#include - -namespace Aliro { - -class CryptoImpl; - -/** - * Interface class for Crypto component. - */ -class Crypto { -public: - /** - * @brief Initialize a crypto backend. - * - * @return ALIRO_NO_ERROR on success, ALIRO_ERROR_INTERNAL otherwise. - */ - AliroError Init(); - - /** - * @brief Generate random bytes. - * - * @param rngBuf Output buffer for storing the generated bytes. - * @param rngBufLength Input number of bytes to generate. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError GenerateRandom(uint8_t *rngBuf, size_t rngBufLength); - - /** - * @brief Generate ephemeral EC key pair. - * - * @param keyId Output identifier for newly created keys. - * @param ephemeralPubKey Output buffer where the public key is to be copied. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError GenerateEphemeralKeyPair(CryptoTypes::KeyId &keyId, CryptoTypes::PublicKey &ephemeralPubKey); - - /** - * @brief Generate ephemeral EC key pair. - * - * @param keyId Output identifier for newly created keys. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError GenerateEphemeralKeyPair(CryptoTypes::KeyId &keyId); - - /** - * @brief Export EC public key. - * - * @param keyId input identifier of a private key for which the public key should be exported. - * @param ephemeralPubKey Output buffer where the public key is to be copied. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError ExportPublicKey(CryptoTypes::KeyId keyId, CryptoTypes::PublicKey &publicKey); - - /** - * @brief Export a key. - * - * @param keyId input identifier of a key to export. - * @param key output buffer where the key is to be copied. - * @param keyLength length of the key in bytes. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError ExportKey(CryptoTypes::KeyId keyId, uint8_t *key, size_t keyLength); - - /** - * Import a EC public key. - * - * @param key input buffer with public key. - * @param keyId output identifier of the imported key. - * @param isPersistent flag indicating whether the key should be persistent or temporary. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError ImportPublicKey(const CryptoTypes::PublicKey &key, CryptoTypes::KeyId &keyId, - bool isPersistent = false); - - /** - * Import EC private key. - * - * @param key input buffer with private key. - * @param keyId output identifier of the imported private key. - * @param isPersistent flag indicating whether the key should be persistent or temporary. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError ImportPrivateKey(const CryptoTypes::PrivateKey &key, CryptoTypes::KeyId &keyId, - bool isPersistent = false); - - /** - * Preserve a volatile key by moving it to persistent slot. - * - * @param volatileKeyId input identifier of the volatile key. - * @param persistentKeyId input identifier of the persistent key. - * - * @return ALIRO_NO_ERROR on success, ALIRO_KEY_ALREADY_EXISTS if the key already exists, other error status - * otherwise. - */ - AliroError PreserveKey(CryptoTypes::KeyId &volatileKeyId, CryptoTypes::KeyId persistentKeyId); - - /** - * Destroy an key by ID. - * - * @param keyId identifier of a key to delete. - * NOTE: The function may set a ID to value 0. - * - * @param keyId an ID of the key to destroy. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError DestroyKey(CryptoTypes::KeyId &keyId); - - /** - * Generate signature for a message. - * NOTE: The Reader's private key (LTK) is used for message signing. - * The key must be loaded and avialable before this method is invoked. - * - * @param keyId input identifier of the private key to use for signing. - * @param msg input message to sign. - * @param msgLength input size of the message. - * @param signature output buffer where te signature is to be copied. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError GenerateSignature(CryptoTypes::KeyId privKeyId, const uint8_t *msg, const size_t msgLength, - CryptoTypes::Signature &signature); - - /** - * Verify signature of a message. - * NOTE: The Reader's public key (LTK) is used message verify. - * The key must be loaded and avialable before this method is invoked. - * - * @param pubKeyId input identifier of the public key to use for verification. - * @param msg input message whose signature is to be verified. - * @param msgLength input size of the message. - * @param signature input signature to verify. - * - * @return ALIRO_NO_ERROR when siganture is valid, ALIRO_INVALID_SIGNATURE otherwise. - */ - AliroError VerifySignature(CryptoTypes::KeyId pubKeyId, const uint8_t *msg, const size_t msgLength, - const CryptoTypes::Signature &signature); - - /** - * Comupte shared key with ECDH procedure. - * Aliro spec. - * The function agrees common key using the ECDH procedure and derives shared key. - * - * @param privKeyId input identifier of private key id to use in procedure. - * @param publicKey input user device public key to use in procedure. - * @param transactionId input Transaction identifier. - * @param keyDhId output identifier of the agreed key. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError ComputeSharedKeyDH(CryptoTypes::KeyId privKeyId, const CryptoTypes::PublicKey &publicKey, - const CryptoTypes::TransactionIdentifier &transactionId, - CryptoTypes::KeyId &keyDhId); - - /** - * Derive session keys using Kdh. - * Aliro spec. - * This function generates 160 bytes of derived key material and extracts from it symmetric keys: - * - ExpeditedSKReader - * - ExpeditedSKDevice - * - StepUpSK (optional) - * - BleSK (optional) - * - URSK (optional) - * - CryptogramSK (optional) - * - * @param kDh input identifier of the secret key. - * @param info input information for key derivation. - * @param infoLength input size of the info buffer. - * @param salt input a salt for key derivation. - * @param saltLength input size of the salt buffer. - * @param sessionVolatileKeys output bunch of session-bound keys. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError DeriveSessionKeys(CryptoTypes::KeyId kDh, const uint8_t *info, size_t infoLength, - const uint8_t *salt, size_t saltLength, - CryptoTypes::SessionBoundKeys &sessionVolatileKeys); - - /** - * Derive 32-bytes long symmetric key. - * - * @param inputKeyId input identifier of the secret key. - * @param info input information for key derivation. - * @param infoLength input size of the info buffer. - * @param salt input a salt for key derivation. - * @param saltLength input size of the salt buffer. - * @param outputKeyId output identifier of the derived key. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError DeriveSymmetricKey(CryptoTypes::KeyId inputKeyId, const uint8_t *info, size_t infoLength, - const uint8_t *salt, size_t saltLength, CryptoTypes::KeyId &outputKeyId); - - /** - * Derive persistent key. - * - * @param inputKeyId input identifier of the secret key. - * @param info input information for key derivation. - * @param infoLength input size of the info buffer. - * @param salt input a salt for key derivation. - * @param saltLength input size of the salt buffer. - * @param outputKeyId output persistent key. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError DeriveKpersistent(CryptoTypes::KeyId inputKeyId, const uint8_t *info, size_t infoLength, - const uint8_t *salt, size_t saltLength, CryptoTypes::KeyId &outputKeyId); - - /** - * Encrypt data payload. - * - * @param keyId input identifier of the key to use for encryption. - * @param plainTxt input raw paylod to encrypt. - * @param plainTxtLength input size of the payload. - * @param additionalData input a addtional data. - * @param additionalDataLength input a size of the addtional data. - * @param nonce input a nonce to use for operation. - * @param cipherText output encrypted paylod. - * @param authTag output authentication tag. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError EncryptPayload(CryptoTypes::KeyId keyId, const uint8_t *plainTxt, size_t plainTxtLength, - const uint8_t *info, size_t infoLength, const CryptoTypes::Nonce &nonce, - uint8_t *cipherText, CryptoTypes::AuthenticationTag &authTag); - - /** - * Encrypt data payload. - * - * @param keyId input identifier of the key to use for encryption. - * @param plainTxt input raw paylod to encrypt. - * @param plainTxtLength input size of the payload. - * @param cipherText output encrypted paylod. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError EncryptPayload(CryptoTypes::KeyId keyId, const uint8_t *plainTxt, size_t plainTxtLength, - uint8_t *cipherText); - - /** - * Authenticated decryption (AEAD) data payload. - * - * @param keyId input identifier of the key to use for decryption. - * @param cipherTextWithTag input encrypted and authenticated data. The buffer must contains the encrypted data - * followed by authentication tag. - * @param cipherTextWithTagLength input size of the cipherText buffer. - * @param additionalData input a addtional data that has been authenticated but not encrypted. - * @param additionalDataLength input a size of the addtional data. - * @param nonce input a nonce to use for operation with fixed size. - * @param plainText output buffer for decrypted data. - * @param plainTextLengt input/output size of the plainText buffer. On success the size of the output after - * decryprion. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError DecryptPayload(CryptoTypes::KeyId keyId, const uint8_t *cipherTextWithTag, - size_t cipherTextWithTagLength, const uint8_t *additionalData, - size_t additionalDataLength, const CryptoTypes::Nonce &nonce, uint8_t *plainText, - size_t &plainTextLength); - - /** - * Authenticated decryption (AEAD) data payload. - * - * @param keyId input identifier of the key to use for decryption. - * @param cipherText input encrypted and authenticated data. The buffer must contains the encrypted data - * followed by authentication tag. - * @param cipherTextLength input size of the cipherText buffer. - * @param nonce input a nonce to use for operation with fixed size. - * @param plainText output buffer for decrypted data - * @param plainTextLengt input/output size of the plainText buffer. On success the size of the output after - * decryprion. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError DecryptPayload(CryptoTypes::KeyId keyId, const uint8_t *cipherText, size_t cipherTextLength, - const CryptoTypes::Nonce &nonce, uint8_t *plainText, size_t &plainTextLength) - { - return DecryptPayload(keyId, cipherText, cipherTextLength, nullptr, 0U, nonce, plainText, - plainTextLength); - } - - /** - * @brief Provisions a symmetric key for cryptographic operations. - * - * This function stores a symmetric key in a secure location for future use. - * - * @param key pointer to the symmetric key data. - * @param keyLength length of the symmetric key in bytes. - * @param keyId identifier for the key to be provisioned. This ID is used - * to reference the key in subsequent operations. - * @param isPersistent flag indicating whether the key should be persistent or temporary. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError ProvisionSymmetricKey(const uint8_t *key, size_t keyLength, CryptoTypes::KeyId &keyId, - bool isPersistent = false); - - /** - * @brief Checks if a key ID exists (key is provisioned). - * - * @param keyId The key ID to check. - * - * @return ALIRO_NO_ERROR if the key available, a error code otherwise. - */ - AliroError IsKeyValid(CryptoTypes::KeyId keyId); - - /** - * @brief Computes the SHA-256 hash of the data. - * - * @param data The data to compute the hash of. - * @param dataLength The length of the data. - * @param hash The computed SHA-256 hash of the data. - * - * @return ALIRO_NO_ERROR on success, error status otherwise. - */ - AliroError Sha256(const uint8_t *data, size_t dataLength, CryptoTypes::Sha256Hash &hash); - -protected: - Crypto() = default; - Crypto(const Crypto &) = delete; - Crypto(Crypto &&) = delete; - ~Crypto() = default; - Crypto &operator=(const Crypto &) = delete; - Crypto &operator=(Crypto &&) = delete; - -private: - bool mInitialized{ false }; - CryptoImpl *Impl(); -}; - -extern Crypto &CryptoInstance(); - -} // namespace Aliro - -#include "crypto_impl.h" diff --git a/lib/aliro/interfaces/logger/Kconfig b/lib/aliro/interfaces/logger/Kconfig deleted file mode 100644 index 86a9202d..00000000 --- a/lib/aliro/interfaces/logger/Kconfig +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright (c) 2025 Nordic Semiconductor ASA -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# - -choice NCS_ALIRO_LOG_LEVEL - prompt "Log level for Aliro module" - default NCS_ALIRO_LOG_LEVEL_DBG - -config NCS_ALIRO_LOG_LEVEL_OFF - bool "Aliro module log level set to off" - help - No logging - -config NCS_ALIRO_LOG_LEVEL_ERR - bool "Aliro module log level set to error" - help - Error logging only - -config NCS_ALIRO_LOG_LEVEL_WRN - bool "Aliro module log level set to warning" - help - Warning and error logging - -config NCS_ALIRO_LOG_LEVEL_INF - bool "Aliro module log level set to info" - help - Informational, warning and error logging - -config NCS_ALIRO_LOG_LEVEL_DBG - bool "Aliro module log level set to debug" - help - Debug, informational, warning and error logging - -endchoice - -config NCS_ALIRO_LOG_LEVEL_VALUE - int - default 0 if NCS_ALIRO_LOG_LEVEL_OFF - default 1 if NCS_ALIRO_LOG_LEVEL_ERR - default 2 if NCS_ALIRO_LOG_LEVEL_WRN - default 3 if NCS_ALIRO_LOG_LEVEL_INF - default 4 if NCS_ALIRO_LOG_LEVEL_DBG diff --git a/lib/aliro/interfaces/logger/platform_log.h b/lib/aliro/interfaces/logger/platform_log.h deleted file mode 100644 index 63e3b5a6..00000000 --- a/lib/aliro/interfaces/logger/platform_log.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#ifndef ALIRO_PLATFORM_LOG_H_ -#define ALIRO_PLATFORM_LOG_H_ - -#include -#include -#include -#include -#include - -#define _ALIRO_LOG(level, ...) \ - do { \ - if (level <= CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE) { \ - _AliroPlatformLog(level, __VA_ARGS__); \ - } \ - } while (0) - -#define _ALIRO_LOG_HEXDUMP(level, data, size, str) \ - do { \ - if (level <= CONFIG_NCS_ALIRO_LOG_LEVEL_VALUE) { \ - _AliroPlatformLogHexdump(level, data, size, str); \ - } \ - } while (0) - -#define ALIRO_LOG_ERR(...) _ALIRO_LOG(LOG_LEVEL_ERR, __VA_ARGS__) -#define ALIRO_LOG_WRN(...) _ALIRO_LOG(LOG_LEVEL_WRN, __VA_ARGS__) -#define ALIRO_LOG_INF(...) _ALIRO_LOG(LOG_LEVEL_INF, __VA_ARGS__) -#define ALIRO_LOG_DBG(...) _ALIRO_LOG(LOG_LEVEL_DBG, __VA_ARGS__) - -#define ALIRO_LOG_HEXDUMP_ERR(data, size, str) _ALIRO_LOG_HEXDUMP(LOG_LEVEL_ERR, data, size, str) -#define ALIRO_LOG_HEXDUMP_WRN(data, size, str) _ALIRO_LOG_HEXDUMP(LOG_LEVEL_WRN, data, size, str) -#define ALIRO_LOG_HEXDUMP_INF(data, size, str) _ALIRO_LOG_HEXDUMP(LOG_LEVEL_INF, data, size, str) -#define ALIRO_LOG_HEXDUMP_DBG(data, size, str) _ALIRO_LOG_HEXDUMP(LOG_LEVEL_DBG, data, size, str) - -/* - * Helpers: Debug builds print hexdump, other levels print info logs. - */ -#ifdef CONFIG_NCS_ALIRO_LOG_LEVEL_DBG -#define ALIRO_LOG_DBG_HEXDUMP_OR_INFO(data, size, fmt, ...) \ - do { \ - char _aliro_log_desc[128]; \ - (void)snprintf(_aliro_log_desc, sizeof(_aliro_log_desc), fmt, ##__VA_ARGS__); \ - ALIRO_LOG_HEXDUMP_DBG(data, size, _aliro_log_desc); \ - } while (0) -#else -#define ALIRO_LOG_DBG_HEXDUMP_OR_INFO(data, size, fmt, ...) ALIRO_LOG_INF(fmt, ##__VA_ARGS__) -#endif - -/** - * @brief Writes a log message with the specified log level. - * @note The function is intended to be implemented by the platform. - * - * @param[in] logLevel the log level. - * @param[in] logFormat a pointer to the format string. - * @param[in] ... arguments for the format specification. - */ -void _AliroPlatformLog(uint8_t platformLogLevel, const char *logFormat, ...); - -/** - * @brief Logs binary data in hexadecimal format with the specified log level. - * @note The function is intended to be implemented by the platform. - * - * @param[in] logLevel the log level. - * @param[in] data a pointer to the data to be logged. - * @param[in] size size of the data in bytes. - * @param[in] str description string to prefix the hexdump. - */ -void _AliroPlatformLogHexdump(uint8_t platformLogLevel, const void *data, size_t size, const char *str); - -#endif // ALIRO_PLATFORM_LOG_H_ diff --git a/lib/aliro/interfaces/transport/ble/ble_iface.h b/lib/aliro/interfaces/transport/ble/ble_iface.h deleted file mode 100644 index 6d49792a..00000000 --- a/lib/aliro/interfaces/transport/ble/ble_iface.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2025 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#pragma once - -#include "aliro/ble_types.h" -#include "aliro/errors.h" -#include "aliro/protocol_version.h" -#include "aliro/transport_callbacks.h" -#include "aliro/types.h" - -#include - -namespace Aliro::BleInterface { - -class BleIfc { -public: - virtual ~BleIfc() = default; - - /** - * @brief Initialize the BLE connection. - * - * @param callbacks Transport callbacks for event notification. - * Platform implementations use these to notify the stack - * of transport events. - * - * @return ALIRO_NO_ERROR on success, error code otherwise. - */ - virtual AliroError Init(const PlatformTransportCallbacks &callbacks) = 0; - - /** - * @brief Send data over the BLE connection. - * - * @param id The connection ID. - * @param data The data to send. - * - * @return ALIRO_NO_ERROR on success, error code otherwise. - */ - virtual AliroError Send(ConnectionHandle handle, Data data) const = 0; - - /** - * @brief Disconnect the BLE connection. - * - * @return ALIRO_NO_ERROR on success, error code otherwise. - */ - virtual AliroError Disconnect(ConnectionHandle handle) = 0; - - /** - * @brief Disconnect all BLE connections. - */ - virtual void DisconnectAll() = 0; - - /** - * @brief Start BLE advertising with service configuration. - * - * @param data Reference to the advertising service data. For service data types (Uuid16, Uuid32, Uuid128), - * this contains the service UUID followed by additional payload data. For complete service list types - * (Uuid16All, Uuid32All, Uuid128All), this contains only the service UUIDs without additional data. - * @param type The type of advertising data field. - * - * @return ALIRO_NO_ERROR when success, error code otherwise. - */ - virtual AliroError StartAdvertising(const ConstData &data, BleTypes::AdvertisingDataFieldType type) = 0; - - /** - * @brief Update BLE advertising data. - * - * @param data Reference to the advertising service data. For service data types (Uuid16, Uuid32, Uuid128), - * this contains the service UUID followed by additional payload data. For complete service list types - * (Uuid16All, Uuid32All, Uuid128All), this contains only the service UUIDs without additional data. - * @param type The type of advertising data field. - * - * @return ALIRO_NO_ERROR on success, error code otherwise. - */ - virtual AliroError UpdateAdvertisingData(const ConstData &data, BleTypes::AdvertisingDataFieldType type) = 0; - - /** - * @brief Stop BLE advertising. - * - * @return ALIRO_NO_ERROR on success, error code otherwise. - */ - virtual AliroError StopAdvertising() = 0; - - /** - * @brief Get the BLE address. - * - * @param address Reference to store the BLE address. - * - * @return ALIRO_NO_ERROR on success, error code otherwise. - */ - virtual AliroError GetAddress(BleTypes::BleAddress &address) const = 0; - - /** - * @brief Get the current transmit power of the BLE radio module. - * - * @param txPowerLevel Reference to store the TX power level (in dBm). - * - * @return ALIRO_NO_ERROR on success, error code otherwise. - */ - virtual AliroError GetTxPowerLevel(BleTypes::TxPowerLevel &txPowerLevel) const = 0; - - /** - * @brief Get the maximum number of BLE sessions. - * - * @return The maximum number of concurrent BLE sessions supported. - */ - virtual size_t GetMaxSessions() const = 0; - - /** - * @brief Get the BLE UWB protocol version for the given connection. - * - * @param handle The connection handle to get the protocol version for. - * - * @return The protocol version. - */ - virtual ProtocolVersion GetProtocolVersion(ConnectionHandle handle) const = 0; -}; - -} // namespace Aliro::BleInterface diff --git a/lib/aliro/interfaces/transport/nfc/driver/interface/aliro_nfc_driver.h b/lib/aliro/interfaces/transport/nfc/driver/interface/aliro_nfc_driver.h deleted file mode 100644 index 00ea7556..00000000 --- a/lib/aliro/interfaces/transport/nfc/driver/interface/aliro_nfc_driver.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2024 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#pragma once - -#include "nfc_driver_config.h" - -#include "aliro/errors.h" -#include "aliro/types.h" - -namespace Aliro { - -class NfcDriverImpl; - -/** - * @class NfcDriver - * @brief Interface class for handling NFC reader driver. - * - * NfcDriver provides an interface for NFC data exchange and control of the NFC field and detection mechanisms for a - * reader device. - */ -class NfcDriver { -public: - /** - * @struct Callbacks - * @brief Struct containing callback functions for NFC reader driver events. - * - * This struct holds pointers to functions that are called on specific NFC events. - */ - struct Callbacks { - /** Called when data is received (RX). */ - void (*mOnDataReceived)(Data, int transferError){ nullptr }; - /** Called when Tag4 is detected, anticollision is completed and ISO-DEP protocol can be activated. */ - void (*mOnTagDetected)(){ nullptr }; - /** Called on each error occurrence. */ - void (*mOnError)(AliroError){ nullptr }; - /** Called on timeout, indicating if the system is in sleep. */ - void (*mOnTimeout)(bool inSleep){ nullptr }; - }; - - /** - * @brief Initializes the NFC driver with specified callbacks. - * @param callbacks Struct containing callback functions. - * @return AliroError status of the initialization. - */ - AliroError Init(Callbacks callbacks); - - /** - * @brief Sends data via NFC to the detected tag. - * @param data Data to be sent. - * @param maximumFrameDelayTime Maximum frame delay time allowed for the transmission. - * @return AliroError status of the operation. - */ - AliroError Send(Data data, uint32_t maximumFrameDelayTime); - - /** - * @brief Enables the NFC. - * @return AliroError status of the operation. - */ - AliroError NfcOn(); - - /** - * @brief Disables the NFC. - * @return AliroError status of the operation. - */ - AliroError NfcOff(); - - /** - * @brief Restarts the NFC polling. - * @return AliroError status of the operation. - */ - AliroError RestartPolling(); - -protected: - NfcDriver() = default; - NfcDriver(const NfcDriver &) = delete; - NfcDriver(NfcDriver &&) = delete; - ~NfcDriver() = default; - NfcDriver &operator=(const NfcDriver &) = delete; - NfcDriver &operator=(NfcDriver &&) = delete; - - Callbacks mCallbacks{}; - -private: - NfcDriverImpl *Impl(); -}; - -/** - * @brief Returns the unique NfcDriver implementation object that can be used by the client code. - * @return Unique NfcDriver object reference. - */ -extern NfcDriver &NfcDriverInstance(); - -} // namespace Aliro diff --git a/lib/aliro/interfaces/transport/nfc/isodep/interface/aliro_isodep.h b/lib/aliro/interfaces/transport/nfc/isodep/interface/aliro_isodep.h deleted file mode 100644 index a2b5335b..00000000 --- a/lib/aliro/interfaces/transport/nfc/isodep/interface/aliro_isodep.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2024 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause - */ - -#pragma once - -#include "isodep_config.h" - -#include "aliro/errors.h" -#include "aliro/types.h" - -namespace Aliro { - -class IsoDepImpl; - -/** - * @class IsoDep - * @brief Interface class for handling NFC ISO-DEP layer. - * - * IsoDep provides an interface for NFC data exchange based on the ISO-DEP protocol. - * It allows for initialization, data preparation, and handling of ISO-DEP communication events. - */ -class IsoDep { -public: - /** - * @enum SelectStatus - * @brief Status codes for tag selection. - */ - enum SelectStatus : uint8_t { - /** Selection successful. */ - StatusOk, - /** Bitrate divisor unsupported. */ - UnsupportedBitrateDivisor - }; - - /** - * @struct Callbacks - * @brief Struct containing callback functions for NFC events. - * - * This struct holds pointers to functions that are called on specific NFC events. - */ - struct Callbacks { - /** Called when TX data is ready to be sent over the transport medium. */ - void (*mOnTxDataReady)(Data, uint32_t maxFrameDelayTime){ nullptr }; - /** Called when new processed data (RX) is available for reading. */ - void (*mOnRxDataAvailable)(Data){ nullptr }; - /** Called when a tag is selected. */ - void (*mOnTagSelected)(SelectStatus){ nullptr }; - /** Called on each error occurrence. */ - void (*mOnError)(AliroError){ nullptr }; - }; - - /** - * @brief Initializes the IsoDep component with specified callbacks. - * @param callbacks Struct containing callback functions. - * @return AliroError status of the initialization. - */ - AliroError Init(Callbacks callbacks); - - /** - * @brief Prepares data for transmission. Once data is ready to be sent, the mOnTxDataReady callback must be - * called, so that the underlying driver can send the data through the transport medium. - * @param data Data to be prepared. - * @return AliroError status of the operation. - */ - AliroError PrepareData(Data data); - - /** - * @brief Prepares the RATS (Request for Answer To Select) command payload. Once data is ready to be sent, - * the mOnTxDataReady callback must be called, so that the underlying driver can RATS command through the - * transport medium. - * @return AliroError status of the operation. - */ - AliroError PrepareRats(); - - /** - * @brief Handles the incoming data in the ISO-DEP layer. This function is supposed to be called with RX data - * forwarded from the transport medium. - * @param data Received data. - * @param transferError Error status of the data transfer. - * @return AliroError status of the operation. - */ - AliroError HandleReceivedData(Data data, int transferError); - - /** - * @brief Reports a timeout event. This function is supposed to be called to notify the ISO-DEP layer about the - * timeout that happened in the transport medium layer. - * @return AliroError status of the operation. - */ - AliroError ReportTimeout(); - -protected: - IsoDep() = default; - IsoDep(const IsoDep &) = delete; - IsoDep(IsoDep &&) = delete; - ~IsoDep() = default; - IsoDep &operator=(const IsoDep &) = delete; - IsoDep &operator=(IsoDep &&) = delete; - - Callbacks mCallbacks{}; - -private: - IsoDepImpl *Impl(); -}; - -/** - * @brief Returns the unique IsoDep implementation object that can be used by the client code. - * @return Unique IsoDep object reference. - */ -extern IsoDep &IsoDepInstance(); - -} // namespace Aliro diff --git a/lib/aliro/interfaces/uwb/Kconfig b/lib/aliro/interfaces/uwb/Kconfig deleted file mode 100644 index dfa0162b..00000000 --- a/lib/aliro/interfaces/uwb/Kconfig +++ /dev/null @@ -1,14 +0,0 @@ -# -# Copyright (c) 2025 Nordic Semiconductor ASA -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# - -config ALIRO_BLE_UWB - bool "Aliro Bluetooth LE (BLE) transport together with ultra wideband (UWB)" - default y if DOOR_LOCK_BLE_UWB - help - Enable the Aliro BLE transport protocol (TP). This is the transport layer - used by the Reader to communicate with the User Device. It is used to send and - receive packets over BLE. Additionally the UWB is enabled in order to manage the - ranging between the Reader and the User Device. diff --git a/snippets/matter-application-core/boards/nrf52840dk_nrf52840.overlay b/snippets/matter-application-core/boards/nrf52840dk_nrf52840.overlay index 81df07a3..ac66b4ad 100644 --- a/snippets/matter-application-core/boards/nrf52840dk_nrf52840.overlay +++ b/snippets/matter-application-core/boards/nrf52840dk_nrf52840.overlay @@ -6,7 +6,7 @@ / { aliases { - /delete-property/ access-decision-indicator; + /delete-property/ lock-sim-indicator; }; }; diff --git a/snippets/matter-application-core/boards/nrf5340dk_nrf5340_cpuapp.overlay b/snippets/matter-application-core/boards/nrf5340dk_nrf5340_cpuapp.overlay index 0e078716..7641906c 100644 --- a/snippets/matter-application-core/boards/nrf5340dk_nrf5340_cpuapp.overlay +++ b/snippets/matter-application-core/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -6,6 +6,12 @@ #include +/ { + aliases { + /delete-property/ lock-sim-indicator; + }; +}; + /* Set IPC thread priority to the highest value to not collide with other threads. */ &ipc0 { zephyr,priority = <0 PRIO_COOP>; diff --git a/snippets/matter-application-core/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/snippets/matter-application-core/boards/nrf54l15dk_nrf54l15_cpuapp.overlay index ab9ef1a6..2a907c72 100644 --- a/snippets/matter-application-core/boards/nrf54l15dk_nrf54l15_cpuapp.overlay +++ b/snippets/matter-application-core/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -8,7 +8,7 @@ aliases { /* Use watchdog wdt31 as the application watchdog */ watchdog0 = &wdt31; - /delete-property/ access-decision-indicator; + /delete-property/ lock-sim-indicator; }; }; @@ -17,11 +17,6 @@ ranges = <0x0 0x20000000 0x40000>; }; -/* TODO: re-enable HWFC once it's fixed */ -&uart20 { - /delete-property/ hw-flow-control; -}; - &wdt31 { status = "okay"; }; diff --git a/snippets/matter-application-core/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay b/snippets/matter-application-core/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay index a3ccd341..2e9bc900 100644 --- a/snippets/matter-application-core/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay +++ b/snippets/matter-application-core/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay @@ -8,7 +8,7 @@ aliases { /* Use watchdog wdt31 as the application watchdog */ watchdog0 = &wdt31; - /delete-property/ access-decision-indicator; + /delete-property/ lock-sim-indicator; }; }; diff --git a/snippets/matter-application-core/matter-application-core.conf b/snippets/matter-application-core/matter-application-core.conf index 77eb59e6..b4215383 100644 --- a/snippets/matter-application-core/matter-application-core.conf +++ b/snippets/matter-application-core/matter-application-core.conf @@ -27,9 +27,6 @@ CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n -# Shell -CONFIG_CHIP_LIB_SHELL=y -CONFIG_NCS_SAMPLE_MATTER_TEST_SHELL=y # Enable DK library CONFIG_DK_LIBRARY=y @@ -45,3 +42,6 @@ CONFIG_CHIP_MALLOC_SYS_HEAP_SIZE=16384 # Disable all logs by default to save memory, # except those explicitly enabled for Matter and Aliro related. CONFIG_LOG_DEFAULT_LEVEL=0 + +CONFIG_CHIP_NFC_ONBOARDING_PAYLOAD=n +CONFIG_CHIP_QSPI_NOR_POWER_MANAGEMENT_SUPPORT=n diff --git a/snippets/uwb_qm35_dfu/boards/sb-nrf54lm20dk_nrf54lm20a_cpuapp.conf b/snippets/uwb_qm35_dfu/boards/sb-nrf54lm20dk_nrf54lm20a_cpuapp.conf new file mode 100644 index 00000000..48898fbb --- /dev/null +++ b/snippets/uwb_qm35_dfu/boards/sb-nrf54lm20dk_nrf54lm20a_cpuapp.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +SB_CONFIG_MCUBOOT_UPDATEABLE_IMAGES=2 diff --git a/snippets/uwb_qm35_dfu/snippet.yml b/snippets/uwb_qm35_dfu/snippet.yml index 32cb0b57..4ac1c738 100644 --- a/snippets/uwb_qm35_dfu/snippet.yml +++ b/snippets/uwb_qm35_dfu/snippet.yml @@ -12,3 +12,6 @@ boards: nrf5340dk/nrf5340/cpuapp: append: SB_EXTRA_CONF_FILE: boards/sb-nrf5340dk_nrf5340_cpuapp.conf + nrf54lm20dk/nrf54lm20a/cpuapp: + append: + SB_EXTRA_CONF_FILE: boards/sb-nrf54lm20dk_nrf54lm20a_cpuapp.conf diff --git a/snippets/uwb_qm35_dfu_app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.conf b/snippets/uwb_qm35_dfu_app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.conf new file mode 100644 index 00000000..612403e6 --- /dev/null +++ b/snippets/uwb_qm35_dfu_app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_DFU_MULTI_IMAGE_MAX_IMAGE_COUNT=2 diff --git a/snippets/uwb_qm35_dfu_app/snippet.yml b/snippets/uwb_qm35_dfu_app/snippet.yml index 2ead4307..5a555a47 100644 --- a/snippets/uwb_qm35_dfu_app/snippet.yml +++ b/snippets/uwb_qm35_dfu_app/snippet.yml @@ -12,3 +12,6 @@ boards: nrf5340dk/nrf5340/cpuapp: append: EXTRA_CONF_FILE: boards/nrf5340dk_nrf5340_cpuapp.conf + nrf54lm20dk/nrf54lm20a/cpuapp: + append: + EXTRA_CONF_FILE: boards/nrf54lm20dk_nrf54lm20a_cpuapp.conf diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt new file mode 100644 index 00000000..e392071e --- /dev/null +++ b/subsys/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +add_subdirectory_ifdef(CONFIG_DOOR_LOCK_EXTERNAL_NVS external_nvs) diff --git a/subsys/Kconfig b/subsys/Kconfig new file mode 100644 index 00000000..6ffabca5 --- /dev/null +++ b/subsys/Kconfig @@ -0,0 +1,11 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "Door Lock Subsystems" + +rsource "external_nvs/Kconfig" + +endmenu diff --git a/subsys/external_nvs/CMakeLists.txt b/subsys/external_nvs/CMakeLists.txt new file mode 100644 index 00000000..d1e263ff --- /dev/null +++ b/subsys/external_nvs/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_include_directories(include) +zephyr_sources( + src/aead.cpp + src/external_nvs.cpp + src/key.cpp + src/nonce.cpp + src/storage.cpp +) + +zephyr_sources_ifdef(CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION src/counter.cpp) diff --git a/subsys/external_nvs/Kconfig b/subsys/external_nvs/Kconfig new file mode 100644 index 00000000..dfd0886a --- /dev/null +++ b/subsys/external_nvs/Kconfig @@ -0,0 +1,68 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig DOOR_LOCK_EXTERNAL_NVS + bool "External NVS Storage" + select NVS + select NVS_INIT_BAD_MEMORY_REGION + select FLASH + select FLASH_MAP + select PSA_WANT_GENERATE_RANDOM + select PSA_WANT_KEY_TYPE_CHACHA20 + select PSA_WANT_ALG_CHACHA20_POLY1305 + select HW_UNIQUE_KEY + select HW_UNIQUE_KEY_RANDOM + select HW_UNIQUE_KEY_WRITE_ON_CRYPTO_INIT + depends on !SOC_SERIES_NRF52X + help + Enable External NVS Storage. + +if DOOR_LOCK_EXTERNAL_NVS + +module = DOOR_LOCK_EXTERNAL_NVS +module-str = Door Lock External NVS Storage +source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +config DOOR_LOCK_EXTERNAL_NVS_MAX_DATA_SIZE + int "Maximum data size (bytes)" + range 1 65535 + default 1024 + help + Maximum data size in bytes for the external NVS storage objects. + +config DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + bool "Enable rollback protection" + depends on SETTINGS + select TRUSTED_STORAGE if (!PSA_SSF_CRYPTO_CLIENT && !BUILD_WITH_TFM) + select PSA_PROTECTED_STORAGE if (TRUSTED_STORAGE && !PSA_SSF_CRYPTO_CLIENT && !BUILD_WITH_TFM) + default y + help + Enable rollback protection for the external NVS storage. + The device unique ID is stored in PSA Protected Storage (internal flash). + +if DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + +config DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION_SETTINGS_PREFIX + string "Settings prefix" + default "ext" + help + Prefix for the settings keys for the rollback protection. + +config DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION_UNIQUE_ID_SIZE + int "Unique ID size (bytes)" + default 8 + help + Size of the unique ID in bytes for the rollback protection. + +config DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION_UNIQUE_ID_UID + hex "Unique ID UID" + default 0x00000005FFFFFFFF + help + PSA Protected Storage UID for the device Unique ID (internal flash). + +endif # DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + +endif # DOOR_LOCK_EXTERNAL_NVS diff --git a/subsys/external_nvs/include/external_nvs/external_nvs.h b/subsys/external_nvs/include/external_nvs/external_nvs.h new file mode 100644 index 00000000..7aa14dfe --- /dev/null +++ b/subsys/external_nvs/include/external_nvs/external_nvs.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file external_nvs.h + * @brief Public API for the External NVS subsystem. + * + * Provides encrypted non-volatile storage on an external flash device. + * Data is authenticated and encrypted using AEAD (ChaCha20-Poly1305) with + * hardware-derived keys. Optional rollback protection is available via + * monotonic counters. + */ + +#pragma once + +#include +#include + +namespace DoorLock::ExternalNvs { + +/** @brief Type used to identify entries stored in external NVS. */ +using Id = uint16_t; + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + +/** @brief Reserved ID for the Unique ID. */ +constexpr Id kUniqueIdReservedId{ 0xFFFF }; + +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + +/** + * @brief Initialize the External NVS subsystem. + * + * Sets up the underlying NVS storage partition, the nonce generator, and + * (if enabled) the rollback-protection counter. + * + * @param partitionId Flash partition identifier to use for NVS storage. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Init(uint8_t partitionId); + +/** + * @brief Erase all data from the external NVS storage. + * + * Clears the rollback-protection counters and wipes the underlying NVS + * partition. After calling this function, @ref Init must be called again + * before using the storage APIs. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Clear(); + +/** + * @brief Encrypt and write data to external NVS. + * + * The plaintext is encrypted with AEAD using a hardware-derived key and a + * fresh nonce. Metadata (nonce, and optionally the rollback counter) is + * stored alongside the ciphertext. On failure the partially written entry is + * deleted. + * + * @param id Identifier for the NVS entry. + * @param data Pointer to the plaintext data to write. Must not be @c nullptr. + * @param len Length of the plaintext data in bytes. Must not exceed + * @c CONFIG_DOOR_LOCK_EXTERNAL_NVS_MAX_DATA_SIZE. + * + * @retval 0 on success. + * @retval -EINVAL if @p data is @c nullptr or @p len exceeds the maximum. + * @retval negative errno code on other failures. + */ +int Write(Id id, const void *data, size_t len); + +/** + * @brief Read and decrypt data from external NVS. + * + * Reads the stored object, verifies its integrity and authenticity via AEAD + * decryption, and (if enabled) validates the rollback counter. On integrity + * failure the corrupted entry is deleted from storage. + * + * @param[in] id Identifier for the NVS entry. + * @param[out] data Pointer to the buffer that receives the decrypted data. + * Must not be @c nullptr. + * @param[in,out] len On input, the capacity of @p data in bytes. + * On output, the actual length of the decrypted data. + * + * @retval 0 on success. + * @retval -EINVAL if @p data is @c nullptr. + * @retval negative errno code on other failures (e.g., integrity check). + */ +int Read(Id id, void *data, size_t &len); + +/** + * @brief Delete an entry from external NVS. + * + * If rollback protection is enabled the associated counter is incremented + * before the entry is removed, preventing replay of a stale copy. + * + * @param id Identifier for the NVS entry to delete. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Delete(Id id); + +} // namespace DoorLock::ExternalNvs diff --git a/subsys/external_nvs/src/aead.cpp b/subsys/external_nvs/src/aead.cpp new file mode 100644 index 00000000..8df72507 --- /dev/null +++ b/subsys/external_nvs/src/aead.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "aead.h" + +#include + +extern "C" { +#include +} + +#include + +LOG_MODULE_DECLARE(external_nvs, CONFIG_DOOR_LOCK_EXTERNAL_NVS_LOG_LEVEL); + +namespace DoorLock::ExternalNvs::Aead { + +namespace { + +psa_key_attributes_t GetKeyAttributes() +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_algorithm(&attributes, PSA_ALG_CHACHA20_POLY1305); + psa_set_key_type(&attributes, PSA_KEY_TYPE_CHACHA20); + psa_set_key_bits(&attributes, PSA_BYTES_TO_BITS(Key::kKeySize)); + + return attributes; +} + +} // namespace + +int Encrypt(const Key::Key &key, const Nonce::Nonce &nonce, const uint8_t *aad, size_t aadLength, + const uint8_t *plainText, size_t plainTextLength, uint8_t *cipherText, size_t &cipherTextLength) +{ + const auto keyAttributes = GetKeyAttributes(); + + const auto status = + psa_driver_wrapper_aead_encrypt(&keyAttributes, key.data(), key.size(), PSA_ALG_CHACHA20_POLY1305, + nonce.data(), nonce.size(), aad, aadLength, plainText, plainTextLength, + cipherText, cipherTextLength, &cipherTextLength); + if (status != PSA_SUCCESS) { + LOG_ERR("Failed to encrypt: %d", status); + return -EIO; + } + + return 0; +} + +int Decrypt(const Key::Key &key, const Nonce::Nonce &nonce, const uint8_t *aad, size_t aadLength, + const uint8_t *cipherText, size_t cipherTextLength, uint8_t *plainText, size_t &plainTextLength) +{ + const auto keyAttributes = GetKeyAttributes(); + + const auto status = + psa_driver_wrapper_aead_decrypt(&keyAttributes, key.data(), key.size(), PSA_ALG_CHACHA20_POLY1305, + nonce.data(), nonce.size(), aad, aadLength, cipherText, + cipherTextLength, plainText, plainTextLength, &plainTextLength); + if (status != PSA_SUCCESS) { + LOG_ERR("Failed to decrypt: %d", status); + return -EIO; + } + + return 0; +} + +} // namespace DoorLock::ExternalNvs::Aead diff --git a/subsys/external_nvs/src/aead.h b/subsys/external_nvs/src/aead.h new file mode 100644 index 00000000..715e2a0f --- /dev/null +++ b/subsys/external_nvs/src/aead.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file aead.h + * @brief AEAD encryption and decryption for External NVS. + * + * Provides authenticated encryption with associated data (AEAD) using the + * ChaCha20-Poly1305 algorithm via the PSA Crypto driver interface. The + * authentication tag is appended to the ciphertext on encryption and + * verified during decryption. + */ + +#pragma once + +#include "key.h" +#include "nonce.h" + +#include + +#include +#include + +namespace DoorLock::ExternalNvs::Aead { + +/** + * @brief Get the maximum ciphertext size for a given plaintext size. + * + * @param plaintextSize The size of the plaintext in bytes. + * + * @return The maximum ciphertext size in bytes. + */ +constexpr size_t GetMaxCiphertextSize(size_t plaintextSize) +{ + return PSA_AEAD_ENCRYPT_OUTPUT_MAX_SIZE(plaintextSize); +} + +/** + * @brief Type representing a ciphertext buffer. + * + * @tparam kPlaintextSize The size of the plaintext in bytes. + */ +template using Ciphertext = std::array; + +/** + * @brief Encrypt plaintext with AEAD (ChaCha20-Poly1305). + * + * Encrypts @p plainText and appends a Poly1305 authentication tag. + * The @p aad (additional authenticated data) is authenticated but not + * encrypted. + * + * @param key Encryption key (256 bits). + * @param nonce Nonce (96 bits). Must be unique per key usage. + * @param aad Pointer to additional authenticated data (may be @c nullptr if + * @p aadLength is 0). + * @param aadLength Length of @p aad in bytes. + * @param plainText Pointer to the plaintext to encrypt. + * @param plainTextLength Length of @p plainText in bytes. + * @param[out] cipherText Buffer that receives the ciphertext and appended tag. + * @param[in,out] cipherTextLength On input, the capacity of @p cipherText in bytes. + * On output, the number of bytes written (ciphertext + tag). + * + * @retval 0 on success. + * @retval -EIO if the encryption operation fails. + */ +int Encrypt(const Key::Key &key, const Nonce::Nonce &nonce, const uint8_t *aad, size_t aadLength, + const uint8_t *plainText, size_t plainTextLength, uint8_t *cipherText, size_t &cipherTextLength); + +/** + * @brief Decrypt ciphertext with AEAD (ChaCha20-Poly1305). + * + * Verifies the Poly1305 authentication tag and decrypts @p cipherText. + * The @p aad must match the data used during encryption for the tag + * verification to succeed. + * + * @param key Decryption key (256 bits). + * @param nonce Nonce (96 bits) used during encryption. + * @param aad Pointer to additional authenticated data (may be @c nullptr if + * @p aadLength is 0). + * @param aadLength Length of @p aad in bytes. + * @param cipherText Pointer to the ciphertext with appended tag. + * @param cipherTextLength Length of @p cipherText in bytes (including tag). + * @param[out] plainText Buffer that receives the decrypted plaintext. + * @param[in,out] plainTextLength On input, the capacity of @p plainText in bytes. + * On output, the number of decrypted bytes written. + * + * @retval 0 on success. + * @retval -EIO if the decryption or tag verification fails. + */ +int Decrypt(const Key::Key &key, const Nonce::Nonce &nonce, const uint8_t *aad, size_t aadLength, + const uint8_t *cipherText, size_t cipherTextLength, uint8_t *plainText, size_t &plainTextLength); + +} // namespace DoorLock::ExternalNvs::Aead diff --git a/subsys/external_nvs/src/counter.cpp b/subsys/external_nvs/src/counter.cpp new file mode 100644 index 00000000..b41e9555 --- /dev/null +++ b/subsys/external_nvs/src/counter.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "counter.h" + +#include + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(external_nvs, CONFIG_DOOR_LOCK_EXTERNAL_NVS_LOG_LEVEL); + +namespace DoorLock::ExternalNvs::Counter { + +namespace { + +static_assert((kUniqueIdSize % 4) == 0, "Unique ID size must be a multiple of 4"); + +/** PSA Protected Storage UID for the device Unique ID (internal flash). */ +constexpr psa_storage_uid_t kPsUniqueIdUid{ CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION_UNIQUE_ID_UID }; + +constexpr auto kSettingsPrefix{ CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION_SETTINGS_PREFIX }; + +using KeyBuffer = std::array; + +struct DeleteSubtreeEntry { + const char *prefix; + int result; +}; + +UniqueId sUniqueId; +bool sInitialized{ false }; + +int GenerateUniqueId(UniqueId &uniqueId) +{ + auto status = psa_crypto_init(); + if (status != PSA_SUCCESS) { + LOG_ERR("Failed to initialize PSA Crypto: %d", status); + return -ENODEV; + } + + status = psa_generate_random(uniqueId.data(), uniqueId.size()); + if (status != PSA_SUCCESS) { + LOG_ERR("Failed to generate random unique ID: %d", status); + return -EIO; + } + + return 0; +} + +int PrepareCounterKey(KeyBuffer &keyBuffer, Id id) +{ + auto err = snprintf(keyBuffer.data(), keyBuffer.size(), "%s/%u", kSettingsPrefix, id); + if (err < 0 || static_cast(err) >= keyBuffer.size()) { + LOG_ERR("Failed to format counter key"); + return -ENOMEM; + } + + return 0; +} + +int DeleteSubtreeCallback(const char *name, size_t entrySize, settings_read_cb readCb, void *cbArg, void *param) +{ + ARG_UNUSED(entrySize); + ARG_UNUSED(readCb); + ARG_UNUSED(cbArg); + + DeleteSubtreeEntry &entry = *static_cast(param); + KeyBuffer keyBuffer; + + // name comes from Zephyr settings subsystem so it is guaranteed to fit in the buffer. + std::ignore = snprintf(keyBuffer.data(), keyBuffer.size(), "%s/%s", entry.prefix, name); + const int result = settings_delete(keyBuffer.data()); + + // Return the first error, but continue removing remaining keys anyway. + if (entry.result == 0) { + entry.result = result; + } + + return 0; +} + +int PsaStatusToErrno(psa_status_t status) +{ + switch (status) { + case PSA_SUCCESS: + return 0; + case PSA_ERROR_DOES_NOT_EXIST: + return -ENOENT; + case PSA_ERROR_INVALID_ARGUMENT: + return -EINVAL; + case PSA_ERROR_INSUFFICIENT_STORAGE: + return -ENOSPC; + default: + return -EIO; + } +} + +} // namespace + +int Init() +{ + auto err = settings_subsys_init(); + if (err != 0) { + LOG_ERR("Failed to initialize settings subsystem: %d", err); + return err; + } + + size_t readLength{ 0 }; + auto status = psa_ps_get(kPsUniqueIdUid, 0, sUniqueId.size(), sUniqueId.data(), &readLength); + if (status == PSA_ERROR_DOES_NOT_EXIST) { + LOG_INF("Unique ID not found in PSA Protected Storage, generating and storing"); + err = GenerateUniqueId(sUniqueId); + if (err != 0) { + return err; + } + status = psa_ps_set(kPsUniqueIdUid, sUniqueId.size(), sUniqueId.data(), PSA_STORAGE_FLAG_NONE); + if (status != PSA_SUCCESS) { + LOG_ERR("Failed to write Unique ID to PSA Protected Storage: %d", status); + return PsaStatusToErrno(status); + } + } else if (status != PSA_SUCCESS) { + LOG_ERR("Failed to read Unique ID from PSA Protected Storage: %d", status); + return PsaStatusToErrno(status); + } else if (readLength != sUniqueId.size()) { + LOG_ERR("Unique ID length mismatch, expected %zu, got %zu", sUniqueId.size(), readLength); + return -EIO; + } + + sInitialized = true; + + return 0; +} + +int Clear() +{ + sInitialized = false; + + auto status = psa_ps_remove(kPsUniqueIdUid); + if (status != PSA_SUCCESS && status != PSA_ERROR_DOES_NOT_EXIST) { + LOG_ERR("Failed to remove Unique ID from PSA Protected Storage: %d", status); + return PsaStatusToErrno(status); + } + + DeleteSubtreeEntry entry{ kSettingsPrefix, 0 }; + auto err = settings_load_subtree_direct(kSettingsPrefix, DeleteSubtreeCallback, &entry); + if (err != 0) { + LOG_ERR("Failed to delete subtree: %d", err); + return err; + } + + if (entry.result != 0) { + LOG_ERR("Failed to delete subtree, result: %d", entry.result); + return entry.result; + } + + return 0; +} + +int GetUniqueId(UniqueId &uniqueId) +{ + if (!sInitialized) { + LOG_ERR("Counter is not initialized"); + return -ENODEV; + } + + uniqueId = sUniqueId; + + return 0; +} + +int Read(Id id, Counter &counter) +{ + KeyBuffer keyBuffer; + + auto err = PrepareCounterKey(keyBuffer, id); + if (err != 0) { + return err; + } + + err = settings_load_one(keyBuffer.data(), &counter, sizeof(counter)); + if (err == -ENOENT || err == 0) { + LOG_INF("Counter not found"); + counter = 0; + return 0; + } else if (err < 0) { + LOG_ERR("Failed to load counter: %d", err); + return err; + } else if (err != sizeof(counter)) { + LOG_ERR("Counter length mismatch, expected %zu, got %d", sizeof(counter), err); + return -EIO; + } + + return 0; +} + +int Write(Id id, const Counter &counter) +{ + KeyBuffer keyBuffer; + + auto err = PrepareCounterKey(keyBuffer, id); + if (err != 0) { + return err; + } + + err = settings_save_one(keyBuffer.data(), &counter, sizeof(counter)); + if (err != 0) { + LOG_ERR("Failed to save counter: %d", err); + return err; + } + + return 0; +} + +} // namespace DoorLock::ExternalNvs::Counter diff --git a/subsys/external_nvs/src/counter.h b/subsys/external_nvs/src/counter.h new file mode 100644 index 00000000..e8c45359 --- /dev/null +++ b/subsys/external_nvs/src/counter.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file counter.h + * @brief Monotonic counter and unique ID management for rollback protection. + * + * Maintains per-entry monotonic counters stored in the Zephyr settings + * subsystem (internal flash) to protect External NVS data against replay + * and rollback attacks. A device-unique random identifier is persisted + * alongside the counters to bind them to a specific device instance. + * + * @note This module is only compiled when + * @c CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION is enabled. + */ + +#pragma once + +#include + +#include + +namespace DoorLock::ExternalNvs::Counter { + +/** @brief Size of the device-unique identifier in bytes. */ +constexpr size_t kUniqueIdSize{ CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION_UNIQUE_ID_SIZE }; + +/** @brief Type representing the device-unique identifier. */ +using UniqueId = std::array; + +/** @brief Type representing a monotonic counter value. */ +using Counter = uint32_t; + +/** + * @brief Initialize the counter subsystem. + * + * Initializes the Zephyr settings subsystem and attempts to load the + * persisted unique ID. If no unique ID exists, a new one is generated + * using PSA Crypto and saved to settings. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Init(); + +/** + * @brief Clear all persisted counters and regenerate the unique ID. + * + * Deletes every key under the rollback-protection settings subtree and + * generates a fresh unique ID. After this call, existing encrypted + * entries cannot be validated and should be considered invalidated. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Clear(); + +/** + * @brief Retrieve the current device-unique identifier. + * + * @param[out] uniqueId Buffer that receives the unique identifier. + * + * @retval 0 on success. + * @retval -ENODEV if the counter subsystem has not been initialized. + */ +int GetUniqueId(UniqueId &uniqueId); + +/** + * @brief Read the current counter value for a given NVS entry. + * + * Loads the counter from settings. If no counter exists for @p id the + * returned value is 0. + * + * @param id NVS entry identifier. + * @param[out] counter Buffer that receives the counter value. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Read(Id id, Counter &counter); + +/** + * @brief Persist a counter value for a given NVS entry. + * + * @param id NVS entry identifier. + * @param counter Counter value to store. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Write(Id id, const Counter &counter); + +} // namespace DoorLock::ExternalNvs::Counter diff --git a/subsys/external_nvs/src/external_nvs.cpp b/subsys/external_nvs/src/external_nvs.cpp new file mode 100644 index 00000000..67e58d1c --- /dev/null +++ b/subsys/external_nvs/src/external_nvs.cpp @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "external_nvs/external_nvs.h" + +#include "aead.h" +#include "key.h" +#include "nonce.h" +#include "storage.h" + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION +#include "counter.h" +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + +#include +#include +#include +#include + +#include +#include + +LOG_MODULE_REGISTER(external_nvs, CONFIG_DOOR_LOCK_EXTERNAL_NVS_LOG_LEVEL); + +/** Mutex protecting all external NVS operations and internal shared state. */ +static K_MUTEX_DEFINE(sExternalNvsMutex); + +namespace DoorLock::ExternalNvs { + +namespace { + +constexpr size_t kMaxDataSize{ CONFIG_DOOR_LOCK_EXTERNAL_NVS_MAX_DATA_SIZE }; + +static_assert(kMaxDataSize <= std::numeric_limits::max(), + "CONFIG_DOOR_LOCK_EXTERNAL_NVS_MAX_DATA_SIZE is too big"); + +using Ciphertext = Aead::Ciphertext; + +struct Aad { + Id mId; +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + Counter::UniqueId mUniqueId; + Counter::Counter mCounter; +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + /* Zeroize the AAD on destruction. */ + ~Aad() { mbedtls_platform_zeroize(this, sizeof(*this)); } + + /* Delete copy and move constructors and operators. */ + Aad(const Aad &) = delete; + Aad &operator=(const Aad &) = delete; + Aad(Aad &&) = delete; + Aad &operator=(Aad &&) = delete; + + const uint8_t *ToRawBytes() const { return reinterpret_cast(this); } + constexpr size_t Size() const { return sizeof(*this); } +} __packed; + +struct Metadata { + Nonce::Nonce mNonce; +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + Counter::Counter mCounter; +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION +}; + +struct Object { + Metadata mMetadata; + Ciphertext mCiphertext; +}; + +bool IsIdValid(Id id) +{ +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + return id != kUniqueIdReservedId; +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + return true; +} + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + +int InitializeUniqueId(uint8_t partitionId) +{ + Counter::UniqueId internalUniqueId; + auto ec = Counter::GetUniqueId(internalUniqueId); + if (ec != 0) { + LOG_ERR("Failed to get Unique ID: %d", ec); + return ec; + } + + Counter::UniqueId storedUniqueId; + size_t readLength{ storedUniqueId.size() }; + ec = Storage::Read(kUniqueIdReservedId, storedUniqueId.data(), readLength); + + if (ec == 0 && readLength == internalUniqueId.size() && storedUniqueId == internalUniqueId) { + return 0; + } + +#ifdef CONFIG_LOG + if (ec == -ENOENT) { + LOG_INF("Unique ID not found on storage"); + } else if (ec != 0) { + LOG_ERR("Failed to read Unique ID: %d", ec); + } else if (readLength != storedUniqueId.size()) { + LOG_ERR("Unique ID length mismatch, expected %zu, got %zu", storedUniqueId.size(), readLength); + } else if (storedUniqueId != internalUniqueId) { + LOG_ERR("Unique ID mismatch"); + } +#endif // CONFIG_LOG + + LOG_INF("Clearing the storage"); + ec = Storage::Clear(); + if (ec != 0) { + return ec; + } + + LOG_INF("Reinitializing the storage"); + ec = Storage::Init(partitionId); + if (ec != 0) { + return ec; + } + + LOG_INF("Writing new unique ID to storage"); + ec = Storage::Write(kUniqueIdReservedId, internalUniqueId.data(), internalUniqueId.size()); + if (ec != 0) { + LOG_ERR("Failed to write unique ID: %d", ec); + return ec; + } + + return 0; +} + +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + +int Write(Id id, const void *data, size_t len) +{ + int ec{ -EIO }; + Object object; + + Aad aad{}; + aad.mId = id; + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + ec = Counter::GetUniqueId(aad.mUniqueId); + if (ec != 0) { + LOG_ERR("Failed to get unique ID: %d", ec); + return ec; + } + + Counter::Counter counter; + ec = Counter::Read(id, counter); + if (ec != 0) { + LOG_ERR("Failed to read counter: %d", ec); + return ec; + } + + counter++; + + aad.mCounter = counter; + object.mMetadata.mCounter = counter; +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + ec = Nonce::Generate(object.mMetadata.mNonce); + if (ec != 0) { + return ec; + } + + Key::Key key; + ec = Key::Derive(id, key); + if (ec != 0) { + return ec; + } + + const auto &nonce{ object.mMetadata.mNonce }; + size_t cipherTextLength{ object.mCiphertext.size() }; + + ec = Aead::Encrypt(key, nonce, aad.ToRawBytes(), aad.Size(), reinterpret_cast(data), len, + object.mCiphertext.data(), cipherTextLength); + mbedtls_platform_zeroize(&key, sizeof(key)); + if (ec != 0) { + return ec; + } + + const auto writeLength{ offsetof(Object, mCiphertext) + cipherTextLength }; + ec = Storage::Write(id, &object, writeLength); + if (ec != 0) { + return ec; + } + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + ec = Counter::Write(id, object.mMetadata.mCounter); + if (ec != 0) { + LOG_ERR("Failed to write counter: %d", ec); + return ec; + } +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + return 0; +} + +int Read(Id id, void *data, size_t &len) +{ + Object object; + size_t readLength{ sizeof(object) }; + auto ec = Storage::Read(id, &object, readLength); + if (ec != 0) { + return ec; + } + + if (readLength < sizeof(object.mMetadata)) { + LOG_ERR("Read length is too short: %zu", readLength); + return -EIO; + } + + Aad aad{}; + aad.mId = id; + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + ec = Counter::GetUniqueId(aad.mUniqueId); + if (ec != 0) { + LOG_ERR("Failed to get unique ID: %d", ec); + return ec; + } + + Counter::Counter counter; + ec = Counter::Read(id, counter); + if (ec != 0) { + LOG_ERR("Failed to get current counter: %d", ec); + return ec; + } + + aad.mCounter = counter; +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + Key::Key key; + ec = Key::Derive(id, key); + if (ec != 0) { + return ec; + } + + const auto &nonce{ object.mMetadata.mNonce }; + const uint8_t *cipherText{ object.mCiphertext.data() }; + const size_t cipherTextLength{ readLength - offsetof(Object, mCiphertext) }; + + ec = Aead::Decrypt(key, nonce, aad.ToRawBytes(), aad.Size(), cipherText, cipherTextLength, + reinterpret_cast(data), len); + mbedtls_platform_zeroize(&key, sizeof(key)); + if (ec != 0) { + return ec; + } + + return 0; +} + +int InitLocked(uint8_t partitionId) +{ + auto ec = Storage::Init(partitionId); + if (ec != 0) { + return ec; + } + + ec = Nonce::Init(); + if (ec != 0) { + return ec; + } + + ec = Key::Init(); + if (ec != 0) { + return ec; + } + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + ec = Counter::Init(); + if (ec != 0) { + return ec; + } + + ec = InitializeUniqueId(partitionId); + if (ec != 0) { + return ec; + } +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + return 0; +} + +int ClearLocked() +{ +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + const auto ec = Counter::Clear(); + if (ec != 0) { + return ec; + } +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + return Storage::Clear(); +} + +int WriteLocked(Id id, const void *data, size_t len) +{ + const auto ec = Write(id, data, len); + if (ec != 0) { + LOG_ERR("Failed to write object at Id %u: %d", id, ec); + Storage::Delete(id); + } + + return ec; +} + +int ReadLocked(Id id, void *data, size_t &len) +{ + const auto ec = Read(id, data, len); + if (ec != 0) { + LOG_ERR("Failed to read object at Id %u: %d", id, ec); + + if (ec == -EIO) { + Storage::Delete(id); + } + } + + return ec; +} + +int DeleteLocked(Id id) +{ + int ec{ -EIO }; + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + Counter::Counter counter; + ec = Counter::Read(id, counter); + if (ec != 0) { + LOG_ERR("Failed to read counter: %d", ec); + return ec; + } +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + ec = Storage::Delete(id); + if (ec != 0) { + return ec; + } + +#ifdef CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + counter++; + ec = Counter::Write(id, counter); + if (ec != 0) { + LOG_ERR("Failed to write counter: %d", ec); + return ec; + } +#endif // CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION + + return 0; +} + +} // namespace + +int Init(uint8_t partitionId) +{ + k_mutex_lock(&sExternalNvsMutex, K_FOREVER); + const auto ec = InitLocked(partitionId); + k_mutex_unlock(&sExternalNvsMutex); + return ec; +} + +int Clear() +{ + k_mutex_lock(&sExternalNvsMutex, K_FOREVER); + const auto ec = ClearLocked(); + k_mutex_unlock(&sExternalNvsMutex); + return ec; +} + +int Write(Id id, const void *data, size_t len) +{ + if (!IsIdValid(id)) { + LOG_ERR("Invalid ID: %u", id); + return -EINVAL; + } + + if (data == nullptr) { + LOG_ERR("data is null"); + return -EINVAL; + } + + if (len > kMaxDataSize) { + LOG_ERR("length is too long: %zu", len); + return -EINVAL; + } + + k_mutex_lock(&sExternalNvsMutex, K_FOREVER); + const auto ec = WriteLocked(id, data, len); + k_mutex_unlock(&sExternalNvsMutex); + return ec; +} + +int Read(Id id, void *data, size_t &len) +{ + if (!IsIdValid(id)) { + LOG_ERR("Invalid ID: %u", id); + return -EINVAL; + } + + if (data == nullptr) { + LOG_ERR("data is null"); + return -EINVAL; + } + + k_mutex_lock(&sExternalNvsMutex, K_FOREVER); + const auto ec = ReadLocked(id, data, len); + k_mutex_unlock(&sExternalNvsMutex); + return ec; +} + +int Delete(Id id) +{ + if (!IsIdValid(id)) { + LOG_ERR("Invalid ID: %u", id); + return -EINVAL; + } + + k_mutex_lock(&sExternalNvsMutex, K_FOREVER); + const auto ec = DeleteLocked(id); + k_mutex_unlock(&sExternalNvsMutex); + return ec; +} + +} // namespace DoorLock::ExternalNvs diff --git a/subsys/external_nvs/src/key.cpp b/subsys/external_nvs/src/key.cpp new file mode 100644 index 00000000..0276f442 --- /dev/null +++ b/subsys/external_nvs/src/key.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "key.h" + +#include + +#include + +LOG_MODULE_DECLARE(external_nvs, CONFIG_DOOR_LOCK_EXTERNAL_NVS_LOG_LEVEL); + +namespace DoorLock::ExternalNvs::Key { + +namespace { + +#ifdef HUK_HAS_KMU +constexpr hw_unique_key_slot kKeySlot{ HUK_KEYSLOT_MEXT }; +#else // HUK_HAS_KMU +constexpr hw_unique_key_slot kKeySlot{ HUK_KEYSLOT_KDR }; +#endif // HUK_HAS_KMU + +} // namespace + +int Init() +{ +#ifndef CONFIG_HW_UNIQUE_KEY_WRITE_ON_CRYPTO_INIT + if (!hw_unique_key_are_any_written()) { + LOG_ERR("Hardware Unique Keys are not written"); + return -ENODEV; + } +#endif // CONFIG_HW_UNIQUE_KEY_WRITE_ON_CRYPTO_INIT + + return 0; +} + +int Derive(Id id, Key &key) +{ + const auto result = hw_unique_key_derive_key(kKeySlot, nullptr, 0, reinterpret_cast(&id), sizeof(id), + key.data(), key.size()); + if (result != HW_UNIQUE_KEY_SUCCESS) { + LOG_ERR("Failed to derive key: %d", result); + return -EIO; + } + + return 0; +} + +} // namespace DoorLock::ExternalNvs::Key diff --git a/subsys/external_nvs/src/key.h b/subsys/external_nvs/src/key.h new file mode 100644 index 00000000..adf36eab --- /dev/null +++ b/subsys/external_nvs/src/key.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file key.h + * @brief Key derivation for External NVS AEAD operations. + * + * Derives per-entry 256-bit encryption keys from the device's Hardware + * Unique Key (HUK) using the NVS entry identifier as context, ensuring + * each stored object is encrypted with a distinct key. + */ + +#pragma once + +#include + +#include + +namespace DoorLock::ExternalNvs::Key { + +/** @brief Size of the derived key in bytes (256 bits). */ +constexpr size_t kKeySize{ 32 }; + +/** @brief Type representing a 256-bit encryption key. */ +using Key = std::array; + +/** + * @brief Initialize the key derivation subsystem. + * + * Verifies that the Hardware Unique Keys have been provisioned on the + * device. This must succeed before @ref Generate can be used. + * + * @retval 0 on success. + * @retval -ENODEV if the Hardware Unique Keys are not written. + */ +int Init(); + +/** + * @brief Derive an encryption key for a given NVS entry. + * + * Uses the Hardware Unique Key to derive a 256-bit key that is unique + * to the specified @p id. + * + * @param id NVS entry identifier used as derivation context. + * @param[out] key Buffer that receives the derived key. + * + * @retval 0 on success. + * @retval -EIO if the key derivation operation fails. + */ +int Derive(Id id, Key &key); + +} // namespace DoorLock::ExternalNvs::Key diff --git a/subsys/external_nvs/src/nonce.cpp b/subsys/external_nvs/src/nonce.cpp new file mode 100644 index 00000000..14b3aeae --- /dev/null +++ b/subsys/external_nvs/src/nonce.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "nonce.h" + +#include + +#include +#include + +LOG_MODULE_DECLARE(external_nvs, CONFIG_DOOR_LOCK_EXTERNAL_NVS_LOG_LEVEL); + +namespace DoorLock::ExternalNvs::Nonce { + +namespace { + +struct NonceCtx { + uint64_t mLow; + uint32_t mHigh; +} __packed; + +static_assert(sizeof(NonceCtx) == kNonceSize, "NonceCtx must be 12 bytes"); + +NonceCtx sNonceCtx; +bool sInitialized{ false }; + +} // namespace + +int Init() +{ + auto status = psa_crypto_init(); + if (status != PSA_SUCCESS) { + LOG_ERR("Failed to initialize PSA Crypto: %d", status); + return -ENODEV; + } + + /* Initialize nonce to a random value. */ + status = psa_generate_random(reinterpret_cast(&sNonceCtx), sizeof(sNonceCtx)); + if (status != PSA_SUCCESS) { + LOG_ERR("Failed to generate random nonce: %d", status); + return -EIO; + } + + sInitialized = true; + + return 0; +} + +int Generate(Nonce &nonce) +{ + if (!sInitialized) { + LOG_ERR("Nonce is not initialized"); + return -ENODEV; + } + + ++sNonceCtx.mLow; + + if (sNonceCtx.mLow == 0) { + ++sNonceCtx.mHigh; + } + + std::copy_n(reinterpret_cast(&sNonceCtx), kNonceSize, nonce.data()); + + return 0; +} + +} // namespace DoorLock::ExternalNvs::Nonce diff --git a/subsys/external_nvs/src/nonce.h b/subsys/external_nvs/src/nonce.h new file mode 100644 index 00000000..260eb15b --- /dev/null +++ b/subsys/external_nvs/src/nonce.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file nonce.h + * @brief Nonce generation for External NVS AEAD operations. + * + * Provides a 96-bit (12-byte) nonce suitable for use with ChaCha20-Poly1305. + * The nonce is seeded from a random value at initialization and then + * monotonically incremented on each call to @ref Generate, guaranteeing + * uniqueness within a single boot cycle. + */ + +#pragma once + +#include +#include +#include + +namespace DoorLock::ExternalNvs::Nonce { + +/** @brief Size of the nonce in bytes (96 bits). */ +constexpr size_t kNonceSize{ 12 }; + +/** @brief Type representing a 96-bit nonce. */ +using Nonce = std::array; + +/** + * @brief Initialize the nonce generator. + * + * Initializes PSA Crypto and seeds the internal nonce state with a + * cryptographically secure random value. + * + * @retval 0 on success. + * @retval -ENODEV if PSA Crypto initialization fails. + * @retval -EIO if random number generation fails. + */ +int Init(); + +/** + * @brief Generate the next unique nonce. + * + * Increments the internal counter and copies the resulting 96-bit value + * into @p nonce. Must be called after a successful @ref Init. + * + * @param[out] nonce Buffer that receives the generated nonce. + * + * @retval 0 on success. + * @retval -ENODEV if the nonce generator has not been initialized. + */ +int Generate(Nonce &nonce); + +} // namespace DoorLock::ExternalNvs::Nonce diff --git a/subsys/external_nvs/src/storage.cpp b/subsys/external_nvs/src/storage.cpp new file mode 100644 index 00000000..4e5b82b2 --- /dev/null +++ b/subsys/external_nvs/src/storage.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "storage.h" + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(external_nvs, CONFIG_DOOR_LOCK_EXTERNAL_NVS_LOG_LEVEL); + +namespace DoorLock::ExternalNvs::Storage { + +namespace { + +nvs_fs nvs; + +} // namespace + +int Init(uint8_t partitionId) +{ + const flash_area *fa; + + auto rc = flash_area_open(partitionId, &fa); + if (rc) { + LOG_ERR("flash_area_open failed: %d", rc); + return rc; + } + LOG_DBG("flash_area_open successful, offset: 0x%lx, size: 0x%x, device: %s", fa->fa_off, fa->fa_size, + fa->fa_dev->name); + + flash_sector hw_flash_sector; + uint32_t sector_cnt{ 1 }; + + rc = flash_area_get_sectors(partitionId, §or_cnt, &hw_flash_sector); + if (rc != 0 && rc != -ENOMEM) { + LOG_ERR("flash_area_get_sectors failed: %d", rc); + return rc; + } + + sector_cnt = fa->fa_size / hw_flash_sector.fs_size; + LOG_DBG("flash_area_get_sectors successful, sector_size: 0x%x, sector_count: %u", hw_flash_sector.fs_size, + sector_cnt); + + nvs.offset = fa->fa_off; + nvs.sector_size = hw_flash_sector.fs_size; + nvs.sector_count = static_cast(sector_cnt); + nvs.flash_device = fa->fa_dev; + + rc = nvs_mount(&nvs); + if (rc) { + LOG_ERR("nvs_mount failed: %d", rc); + return rc; + } + + return 0; +} + +int Clear() +{ + auto rc = nvs_clear(&nvs); + if (rc) { + LOG_ERR("nvs_clear failed: %d", rc); + return rc; + } + + return 0; +} + +int Write(Id id, const void *data, size_t len) +{ + const auto rc = nvs_write(&nvs, id, data, len); + if (rc < 0) { + LOG_ERR("nvs_write failed: %d", rc); + return rc; + } else if (rc > 0 && static_cast(rc) != len) { + LOG_ERR("nvs_write failed to write %zu bytes: %d", len, rc); + return -EIO; + } + + return 0; +} + +int Read(Id id, void *data, size_t &len) +{ + const auto rc = nvs_read(&nvs, id, data, len); + if (rc < 0) { + LOG_ERR("nvs_read failed: %d", rc); + return rc; + } + + len = rc; + return 0; +} + +int Delete(Id id) +{ + const auto rc = nvs_delete(&nvs, id); + if (rc) { + LOG_ERR("nvs_delete failed: %d", rc); + return rc; + } + + return 0; +} + +} // namespace DoorLock::ExternalNvs::Storage diff --git a/subsys/external_nvs/src/storage.h b/subsys/external_nvs/src/storage.h new file mode 100644 index 00000000..83897252 --- /dev/null +++ b/subsys/external_nvs/src/storage.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file storage.h + * @brief Low-level NVS storage backend for External NVS. + * + * Wraps Zephyr's NVS file system API to provide basic read, write, and + * delete operations on a flash partition used by the External NVS subsystem. + */ + +#pragma once + +#include + +namespace DoorLock::ExternalNvs::Storage { + +/** + * @brief Initialize the NVS storage backend. + * + * Opens the specified flash partition, queries its sector layout, and + * mounts the NVS file system. + * + * @param partitionId Flash partition identifier to open and mount. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Init(uint8_t partitionId); + +/** + * @brief Erase all entries from the NVS partition. + * + * Clears the NVS file system. After calling this function, @ref Init must be + * called again before using the storage APIs. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Clear(); + +/** + * @brief Write raw data to the NVS partition. + * + * @param id NVS entry identifier. + * @param data Pointer to the data to store. + * @param len Length of @p data in bytes. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Write(Id id, const void *data, size_t len); + +/** + * @brief Read raw data from the NVS partition. + * + * @param[in] id NVS entry identifier. + * @param[out] data Pointer to the buffer that receives the stored data. + * @param[in,out] len On input, the capacity of @p data in bytes. + * On output, the number of bytes actually read. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Read(Id id, void *data, size_t &len); + +/** + * @brief Delete an entry from the NVS partition. + * + * @param id NVS entry identifier to delete. + * + * @retval 0 on success. + * @retval negative errno code on failure. + */ +int Delete(Id id); + +} // namespace DoorLock::ExternalNvs::Storage diff --git a/tests/subsys/external_nvs/CMakeLists.txt b/tests/subsys/external_nvs/CMakeLists.txt new file mode 100644 index 00000000..be894bd0 --- /dev/null +++ b/tests/subsys/external_nvs/CMakeLists.txt @@ -0,0 +1,15 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(NONE) + +target_sources(app PRIVATE + src/tests.cpp +) diff --git a/tests/subsys/external_nvs/Kconfig b/tests/subsys/external_nvs/Kconfig new file mode 100644 index 00000000..9e77d0e1 --- /dev/null +++ b/tests/subsys/external_nvs/Kconfig @@ -0,0 +1,10 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config ZMS + default y if SOC_FLASH_NRF_RRAM + +source "Kconfig.zephyr" diff --git a/tests/subsys/external_nvs/Kconfig.sysbuild b/tests/subsys/external_nvs/Kconfig.sysbuild new file mode 100644 index 00000000..c02a0689 --- /dev/null +++ b/tests/subsys/external_nvs/Kconfig.sysbuild @@ -0,0 +1,10 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config PARTITION_MANAGER + default n + +source "share/sysbuild/Kconfig" diff --git a/tests/subsys/external_nvs/app.overlay b/tests/subsys/external_nvs/app.overlay new file mode 100644 index 00000000..067e8439 --- /dev/null +++ b/tests/subsys/external_nvs/app.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +&mx25r64 { + status = "okay"; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + external_nvs_partition: partition@0 { + label = "external_nvs"; + reg = <0x0 DT_SIZE_K(128)>; + }; + }; +}; diff --git a/tests/subsys/external_nvs/prj.conf b/tests/subsys/external_nvs/prj.conf new file mode 100644 index 00000000..3d95f931 --- /dev/null +++ b/tests/subsys/external_nvs/prj.conf @@ -0,0 +1,24 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_ZTEST=y +CONFIG_ZTEST_STACK_SIZE=8192 + +CONFIG_CPP=y +CONFIG_STD_CPP17=y +CONFIG_REQUIRES_FULL_LIBCPP=y + +CONFIG_DOOR_LOCK_EXTERNAL_NVS=y +CONFIG_DOOR_LOCK_EXTERNAL_NVS_ROLLBACK_PROTECTION=y +CONFIG_DOOR_LOCK_EXTERNAL_NVS_LOG_LEVEL_DBG=y + +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_NRF_SECURITY=y + +CONFIG_SETTINGS=y + +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y diff --git a/tests/subsys/external_nvs/src/tests.cpp b/tests/subsys/external_nvs/src/tests.cpp new file mode 100644 index 00000000..8c55b3e2 --- /dev/null +++ b/tests/subsys/external_nvs/src/tests.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(external_nvs_tests); + +using namespace DoorLock; + +namespace { + +#if CONFIG_PARTITION_MANAGER_ENABLED +constexpr uint8_t kExternalNvsPartitionId{ FIXED_PARTITION_ID(external_nvs) }; +#else +constexpr uint8_t kExternalNvsPartitionId{ FIXED_PARTITION_ID(external_nvs_partition) }; +#endif // CONFIG_PARTITION_MANAGER_ENABLED + +using Buffer = std::array; + +void ClearStoragePartition() +{ + const flash_area *fa; + auto rc = flash_area_open(kExternalNvsPartitionId, &fa); + zassert_equal(rc, 0, "Failed to open flash area"); + + rc = flash_area_flatten(fa, 0, fa->fa_size); + zassert_equal(rc, 0, "Failed to flatten flash area"); + + flash_area_close(fa); +} + +void ClearSettings() +{ + auto rc = settings_subsys_init(); + zassert_equal(rc, 0, "Failed to initialize settings subsystem"); + + void *storage; + rc = settings_storage_get(&storage); + zassert_equal(rc, 0, "Failed to get storage"); + +#ifdef CONFIG_SETTINGS_NVS + rc = nvs_clear(static_cast(storage)); + zassert_equal(rc, 0, "Failed to clear storage"); + rc = nvs_mount(static_cast(storage)); + zassert_equal(rc, 0, "Failed to mount storage"); +#endif // CONFIG_SETTINGS_NVS +#ifdef CONFIG_SETTINGS_ZMS + rc = zms_clear(static_cast(storage)); + zassert_equal(rc, 0, "Failed to clear storage"); + rc = zms_mount(static_cast(storage)); + zassert_equal(rc, 0, "Failed to mount storage"); +#endif // CONFIG_SETTINGS_ZMS +} + +void DumpSettings() +{ + const auto cb = [](const char *key, size_t len, settings_read_cb read_cb, void *cb_arg, void *param) { + ARG_UNUSED(param); + + std::array buffer; + const auto rc = read_cb(cb_arg, buffer.data(), std::min(len, buffer.size())); + + if (rc < 0) { + LOG_ERR("Failed to read key: %s", key); + } else if (rc == 0) { + LOG_ERR("Key deleted: %s", key); + } else { + LOG_HEXDUMP_INF(buffer.data(), static_cast(rc), key); + } + + return 0; + }; + + LOG_INF("\nSettings:"); + + const auto err = settings_load_subtree_direct(nullptr, cb, nullptr); + if (err) { + LOG_ERR("Failed to load settings: %d", err); + } +} + +} // namespace + +ZTEST(external_nvs_tests, test_read_non_existent_id) +{ + constexpr ExternalNvs::Id id{ 0 }; + + Buffer buffer; + size_t readLength{ buffer.size() }; + auto rc = ExternalNvs::Read(id, buffer.data(), readLength); + zassert_equal(rc, -ENOENT, "Expected error code -ENOENT"); +} + +ZTEST(external_nvs_tests, test_write_read_max_data_size) +{ + constexpr ExternalNvs::Id id{ 1 }; + + Buffer writeBuffer; + const auto status = psa_generate_random(writeBuffer.data(), writeBuffer.size()); + zassert_equal(status, PSA_SUCCESS, "Failed to generate random data"); + + auto rc = ExternalNvs::Write(id, writeBuffer.data(), writeBuffer.size()); + zassert_equal(rc, 0, "Failed to write data"); + + Buffer readBuffer; + size_t readLength{ readBuffer.size() }; + rc = ExternalNvs::Read(id, readBuffer.data(), readLength); + zassert_equal(rc, 0, "Failed to read data"); + zassert_equal(readLength, readBuffer.size(), "Read length mismatch"); + zassert_equal(readBuffer, writeBuffer, "Data mismatch"); +} + +ZTEST(external_nvs_tests, test_write_read_hello) +{ + constexpr ExternalNvs::Id id{ 2 }; + + const char *hello = "Hello External NVM!"; + const size_t helloLength = strlen(hello) + 1; + + auto rc = ExternalNvs::Write(id, hello, helloLength); + zassert_equal(rc, 0, "Failed to write data"); + + Buffer readBuffer; + size_t readLength{ readBuffer.size() }; + rc = ExternalNvs::Read(id, readBuffer.data(), readLength); + zassert_equal(rc, 0, "Failed to read data"); + zassert_equal(readLength, helloLength, "Read length mismatch"); + zassert_mem_equal(hello, readBuffer.data(), helloLength, "Data mismatch"); +} + +void *setup_suite(void) +{ + DumpSettings(); + ClearStoragePartition(); + ClearSettings(); + + auto rc = ::DoorLock::ExternalNvs::Init(kExternalNvsPartitionId); + zassert_equal(rc, 0, "Failed to initialize External NVS"); + + return nullptr; +} + +void teardown_suite(void *) +{ + DumpSettings(); +} + +ZTEST_SUITE(external_nvs_tests, nullptr, setup_suite, nullptr, nullptr, teardown_suite); diff --git a/tests/subsys/external_nvs/testcase.yaml b/tests/subsys/external_nvs/testcase.yaml new file mode 100644 index 00000000..83915ee8 --- /dev/null +++ b/tests/subsys/external_nvs/testcase.yaml @@ -0,0 +1,8 @@ +tests: + aliro.subsys.external_nvs: + sysbuild: true + platform_allow: &platforms + - nrf5340dk/nrf5340/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + - nrf54lm20dk/nrf54lm20a/cpuapp + integration_platforms: *platforms diff --git a/west.yml b/west.yml index 48eb4a3d..b1832556 100644 --- a/west.yml +++ b/west.yml @@ -21,5 +21,26 @@ manifest: revision: QM35ALIRO_ENG-0.6.1 groups: - nrfconnect-sdk-qorvo + - name: matter + remote: ncs + repo-path: sdk-connectedhomeip + path: modules/lib/matter + # v3.2-door-lock-app + revision: ffd5c633b01ab15698d2631fa5b6e8fb8a3bc0d0 + west-commands: scripts/west/west-commands.yml + submodules: + - name: nlio + path: third_party/nlio/repo + - name: nlassert + path: third_party/nlassert/repo + - name: pigweed + path: third_party/pigweed/repo + - name: jsoncpp + path: third_party/jsoncpp/repo + userdata: + ncs: + upstream-url: https://github.com/project-chip/connectedhomeip + upstream-sha: 181b0cb14ff007ec912f2ba6627e05dfb066c008 + compare-by-default: false group-filter: - -nrfconnect-sdk-qorvo