diff --git a/docs/guides/nrfconnect_examples_configuration.md b/docs/guides/nrfconnect_examples_configuration.md index 1e74d2f4e097ea..d2a135ebdf6d9a 100644 --- a/docs/guides/nrfconnect_examples_configuration.md +++ b/docs/guides/nrfconnect_examples_configuration.md @@ -25,7 +25,7 @@ Regardless of the option, you will need to rebuild your application. This will require you to provide the build target name of the kit you are using. You can find the build target names in the Requirements section of the example you are building or on the -[Board support](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/app_dev/board_support/index.html) +[Board support](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/app_dev/board_support/index.html) page in the nRF Connect SDK documentation. ### Temporary changes to configuration @@ -97,7 +97,7 @@ them values of proper type. Few examples: `CONFIG_SAMPLE_STRING_OPTION="some_text"` For more detailed information, read about -[setting Kconfig values](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/kconfig/setting.html#setting-configuration-values) +[setting Kconfig values](https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/build/kconfig/setting.html) in the nRF Connect SDK documentation. Because Kconfig configuration files are used in the building process, make sure @@ -150,7 +150,7 @@ from your perspective: $ west build -b build-target -- -DOVERLAY_CONFIG=overlay_file_name Read the -[Kconfig](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/kconfig/index.html#kconfig) +[Kconfig](https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/build/kconfig/index.html) guide in the nRF Connect SDK's Zephyr documentation if you are interested in getting more advanced and detailed information about the configuration structure. @@ -214,7 +214,7 @@ performance in case it has been polluted with unwanted entries. #### Logging You can enable logging for both the stack and Zephyr’s -[Logging](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/reference/logging/index.html#logging-api) +[Logging](https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/services/logging/index.html) API by setting the `CONFIG_LOG` option. Zephyr allows you to configure log levels of different software modules diff --git a/docs/guides/nrfconnect_examples_software_update.md b/docs/guides/nrfconnect_examples_software_update.md index 9fc1f6e6e5bd3e..d295bae47667a3 100644 --- a/docs/guides/nrfconnect_examples_software_update.md +++ b/docs/guides/nrfconnect_examples_software_update.md @@ -6,7 +6,7 @@ protocols: - Matter-compliant OTA update protocol that uses the Matter operational network for querying and downloading a new firmware image. -- [Simple Management Protocol](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/services/device_mgmt/smp_protocol.html) +- [Simple Management Protocol](https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/services/device_mgmt/smp_protocol.html) over Bluetooth LE. In this case, the DFU can be done either using a smartphone application or a PC command line tool. Note that this protocol is not part of the Matter specification. @@ -125,11 +125,9 @@ rebooted to apply the update. To upgrade your device firmware over Bluetooth LE using a smartphone, complete the following steps: -1. Install _one_ of the following applications on your smartphone: - - - [nRF Connect for Mobile](https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-mobile) - - [nRF Toolbox](https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Toolbox) - +1. Install the + [nRF Connect Device Manager](https://www.nordicsemi.com/Products/Development-tools/nrf-connect-device-manager) + application on your smartphone. 2. Push the appropriate button on the device to enable the software update functionality (if it is not enabled by default) and start the Bluetooth LE advertising of the SMP service. See the user interface section in the example @@ -138,8 +136,10 @@ the following steps: advertising. See the user interface section in the example documentation to check the button number. 4. Follow the instructions about downloading the new image to a device on the - [FOTA upgrades](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/working_with_nrf/nrf52/developing.html#fota-updates) - page in the nRF Connect SDK documentation. + FOTA updates page for either + [nRF52 Series devices](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/app_dev/device_guides/nrf52/fota_update.html) + or the + [nRF5340 DK](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/app_dev/device_guides/nrf53/fota_update_nrf5340.html).
@@ -171,7 +171,7 @@ Complete the following steps to perform DFU using mcumgr: > (for example, `MatterLock`). 1. Install the tool by following the - [mcumgr command line tool installation instructions](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/device_mgmt/index.html#command-line-tool). + [mcumgr command line tool installation instructions](https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/services/device_mgmt/mcumgr.html#command-line_tool). 2. Push the appropriate button on the device to enable the software update functionality (if it is not enabled by default) and start the Bluetooth LE advertising of SMP service. See the user interface section in the example @@ -342,6 +342,6 @@ Swapping operation can take some time, and after it completes, the new firmware is booted. Visit the -[mcumgr image management](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/device_mgmt/indexhtml#image-management) +[mcumgr image management](https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/services/device_mgmt/mcumgr.html#image_management) section to get familiar with all image management commands supported by the tool. diff --git a/docs/guides/nrfconnect_factory_data_configuration.md b/docs/guides/nrfconnect_factory_data_configuration.md index 27595e2888d616..886acc9b3049d3 100644 --- a/docs/guides/nrfconnect_factory_data_configuration.md +++ b/docs/guides/nrfconnect_factory_data_configuration.md @@ -24,7 +24,7 @@ data secure by applying hardware write protection. > **Note:** Due to hardware limitations, in the nRF Connect platform, protection > against writing can be applied only to the internal memory partition. The -> [Fprotect](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/others/fprotect.html) +> [Fprotect](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/libraries/security/bootloader/fprotect.html) > is the hardware flash protection driver, and we used it to ensure write > protection of the factory data partition in internal flash memory. @@ -643,7 +643,7 @@ $ west build -b nrf52840dk_nrf52840 -- \ The factory data partition is an area in the device's persistent storage where a factory data set is stored. This area is configured using the -[Partition Manager](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/scripts/partition_manager/partition_manager.html), +[Partition Manager](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/scripts/partition_manager/partition_manager.html), within which all partitions are declared in the `pm_static.yml` file. To prepare an example that supports factory data, add a partition called @@ -845,7 +845,7 @@ snippet: > **Note:** To get more information about how to use the interactive Kconfig > interfaces, read the -> [Kconfig documentation](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/build/kconfig/menuconfig.html). +> [Kconfig documentation](https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/build/kconfig/menuconfig.html). ### Default Kconfig values and developing aspects diff --git a/docs/guides/nrfconnect_platform_overview.md b/docs/guides/nrfconnect_platform_overview.md index ed04f302d72e71..8c570a67899b14 100644 --- a/docs/guides/nrfconnect_platform_overview.md +++ b/docs/guides/nrfconnect_platform_overview.md @@ -18,7 +18,7 @@ communication purposes: ## nRF Connect SDK Nordic Semiconductor's -[nRF Connect SDK](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/index.html) +[nRF Connect SDK](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/index.html) allows to build a range of applications, including cellular IoT (LTE-M and NB-IoT), Bluetooth Low Energy, Thread, Zigbee, and Bluetooth mesh. The SDK contains samples, libraries and a full set of drivers for Nordic Semiconductor's @@ -57,8 +57,8 @@ Bluetooth LE concurrently with other stacks on the same radio chip. Read more in the nRF Connect SDK documentation: -- [SoftDevice Controller](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/softdevice_controller/README.html) -- [Multiprotocol Service Layer](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/mpsl/README.html) +- [SoftDevice Controller](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/softdevice_controller/README.html) +- [Multiprotocol Service Layer](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/mpsl/README.html)
@@ -72,9 +72,9 @@ network layer functionalities provided by the Zephyr. Read more in the nRF Connect SDK documentation: -- [Multiprotocol Service Layer](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/mpsl/README.html) -- [nRF 802.15.4 Radio Driver](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/nrf_802154/README.html) -- [OpenThread integration](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/protocols/thread/overview/ot_integration.html) +- [Multiprotocol Service Layer](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/mpsl/README.html) +- [nRF 802.15.4 Radio Driver](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/nrf_802154/README.html) +- [OpenThread integration](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/protocols/thread/overview/ot_integration.html)
@@ -105,7 +105,7 @@ agnostic interfaces and no additional platform-related actions are needed to perform communication through the Matter stack. For more information, see the -[Matter integration in the nRF Connect SDK](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/protocols/matter/overview/integration.html) +[Matter integration in the nRF Connect SDK](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/protocols/matter/overview/integration.html) page in the SDK documentation.
@@ -126,5 +126,5 @@ library file is imported during the compilation process. For more information about the build system in Zephyr and the nRF Connect SDK, see the -[Build and configuration system](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/app_dev/build_and_config_system/index.html) +[Build and configuration system](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/app_dev/config_and_build/config_and_build_system.html) page in the SDK documentation. diff --git a/docs/guides/openthread_rcp_nrf_dongle.md b/docs/guides/openthread_rcp_nrf_dongle.md index 57bb716a3ff0d0..e1a9f90a0509b6 100644 --- a/docs/guides/openthread_rcp_nrf_dongle.md +++ b/docs/guides/openthread_rcp_nrf_dongle.md @@ -13,14 +13,14 @@ Once programmed, the dongle can be used for ## Requirements You need to set up the -[nRF Connect SDK](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/index.html) +[nRF Connect SDK](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/index.html) to build and program the OpenThread Radio Co-Processor to the nRF52840 Dongle. Read the -[nRF Connect SDK Getting started](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/getting_started.html) +[nRF Connect SDK installation](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/installation.html) documentation for more information. ## Building and programming the RCP firmware onto an nRF52840 Dongle After you set up the nRF Connect SDK, follow the steps in the -[Configuring a radio co-processor](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_thread_tools.html#configuring-a-radio-co-processor) +[Configuring a radio co-processor](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/protocols/thread/tools.html#configuring_a_radio_co-processor) section for the nRF52840 Dongle (USB transport). diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 8debb1a4930f33..06a034512fc199 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -2554,7 +2554,7 @@ cluster TimeSynchronization = 56 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/all-clusters-app/linux/ValveControlDelegate.cpp b/examples/all-clusters-app/linux/ValveControlDelegate.cpp index 29c744257fbabe..f161ee65eae504 100644 --- a/examples/all-clusters-app/linux/ValveControlDelegate.cpp +++ b/examples/all-clusters-app/linux/ValveControlDelegate.cpp @@ -35,7 +35,7 @@ DataModel::Nullable ValveControlDelegate::HandleOpenValve(DataMod sLastOpenDuration = 0; ChipLogProgress(NotSpecified, "Valve opening from level: %d to %d", currentLevel, sLevel); - // In this demo application, the trasition is considered instant, + // In this demo application, the transition is considered instant, // so current level is set to the requested level and current state is set to kOpen. currentLevel = sLevel; Attributes::CurrentState::Set(kValveEndpoint, ValveConfigurationAndControl::ValveStateEnum::kOpen); diff --git a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter index 7c209b80465f18..77ad5a9fc5d0a5 100644 --- a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter +++ b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter @@ -2286,7 +2286,7 @@ cluster EthernetNetworkDiagnostics = 55 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/bridge-app/bridge-common/bridge-app.matter b/examples/bridge-app/bridge-common/bridge-app.matter index f1cdf949a70524..d64624281236cc 100644 --- a/examples/bridge-app/bridge-common/bridge-app.matter +++ b/examples/bridge-app/bridge-common/bridge-app.matter @@ -1921,7 +1921,7 @@ cluster EthernetNetworkDiagnostics = 55 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/chef/devices/rootnode_genericswitch_2dfff6e516.matter b/examples/chef/devices/rootnode_genericswitch_2dfff6e516.matter index 9c8d9477b2c0ef..98e363345d855c 100644 --- a/examples/chef/devices/rootnode_genericswitch_2dfff6e516.matter +++ b/examples/chef/devices/rootnode_genericswitch_2dfff6e516.matter @@ -1251,7 +1251,7 @@ cluster GeneralDiagnostics = 51 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/chef/devices/rootnode_genericswitch_9866e35d0b.matter b/examples/chef/devices/rootnode_genericswitch_9866e35d0b.matter index a083afc80331bc..57040749ebc510 100644 --- a/examples/chef/devices/rootnode_genericswitch_9866e35d0b.matter +++ b/examples/chef/devices/rootnode_genericswitch_9866e35d0b.matter @@ -1251,7 +1251,7 @@ cluster GeneralDiagnostics = 51 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/contact-sensor-app/linux/README.md b/examples/contact-sensor-app/linux/README.md index f99162f4fdee10..e71ea89feb7eab 100644 --- a/examples/contact-sensor-app/linux/README.md +++ b/examples/contact-sensor-app/linux/README.md @@ -69,11 +69,6 @@ details. ## Running the Complete Example on Raspberry Pi 4 -> If you want to test Echo protocol, please enable Echo handler -> -> gn gen out/debug --args='chip_app_use_echo=true' -> ninja -C out/debug - - Prerequisites 1. A Raspberry Pi 4 board @@ -112,7 +107,7 @@ details. $ cd ~/connectedhomeip/examples/contact-sensor-app/linux $ sudo out/debug/chip-contact-sensor-app --ble-device [bluetooth device number] # In this example, the device we want to use is hci1 - $ sudo out/debug/chip-contact-sensor-app --ble-device 1 + $ sudo out/debug/contact-sensor-app --ble-device 1 - Test the device using ChipDeviceController on your laptop / workstation etc. diff --git a/examples/fabric-admin/commands/pairing/PairingCommand.h b/examples/fabric-admin/commands/pairing/PairingCommand.h index 293c369b25227e..73e717b93757c2 100644 --- a/examples/fabric-admin/commands/pairing/PairingCommand.h +++ b/examples/fabric-admin/commands/pairing/PairingCommand.h @@ -250,7 +250,7 @@ class PairingCommand : public CHIPCommand, const PairingNetworkType mNetworkType; const chip::Dnssd::DiscoveryFilterType mFilterType; Command::AddressWithInterface mRemoteAddr; - NodeId mNodeId; + NodeId mNodeId = chip::kUndefinedNodeId; chip::Optional mTimeout; chip::Optional mDiscoverOnce; chip::Optional mUseOnlyOnNetworkDiscovery; @@ -272,18 +272,18 @@ class PairingCommand : public CHIPCommand, TypedComplexArgument> mComplex_DSTOffsets; - uint16_t mRemotePort; - uint16_t mDiscriminator; - uint32_t mSetupPINCode; - uint16_t mIndex; + uint16_t mRemotePort = 0; + uint16_t mDiscriminator = 0; + uint32_t mSetupPINCode = 0; + uint16_t mIndex = 0; chip::ByteSpan mOperationalDataset; chip::ByteSpan mSSID; chip::ByteSpan mPassword; - char * mOnboardingPayload; - uint64_t mDiscoveryFilterCode; - char * mDiscoveryFilterInstanceName; + char * mOnboardingPayload = nullptr; + uint64_t mDiscoveryFilterCode = 0; + char * mDiscoveryFilterInstanceName = nullptr; - bool mDeviceIsICD; + bool mDeviceIsICD = false; uint8_t mRandomGeneratedICDSymmetricKey[chip::Crypto::kAES_CCM128_Key_Length]; // For unpair diff --git a/examples/fabric-admin/scripts/run_fabric_sync.sh b/examples/fabric-admin/scripts/run_fabric_sync.sh index 957fe5f3cf16e9..49182f7ce92825 100755 --- a/examples/fabric-admin/scripts/run_fabric_sync.sh +++ b/examples/fabric-admin/scripts/run_fabric_sync.sh @@ -14,7 +14,9 @@ DEFAULT_BRIDGE_CHOICES=( "./fabric-bridge-app" "out/debug/standalone/fabric-bridge-app" "out/linux-x64-fabric-bridge-rpc/fabric-bridge-app" + "out/linux-x64-fabric-bridge-rpc-no-ble/fabric-bridge-app" "out/darwin-arm64-fabric-bridge-rpc/fabric-bridge-app" + "out/darwin-arm64-fabric-bridge-rpc-no-ble/fabric-bridge-app" ) FABRIC_ADMIN_LOG="/tmp/fabric_admin.log" FABRIC_BRIDGE_APP_LOG="/tmp/fabric_bridge_app.log" diff --git a/examples/fabric-bridge-app/linux/README.md b/examples/fabric-bridge-app/linux/README.md index 96e8a2924a65bc..27a01d9ffec60a 100644 --- a/examples/fabric-bridge-app/linux/README.md +++ b/examples/fabric-bridge-app/linux/README.md @@ -92,7 +92,7 @@ defined: ``` source scripts/activate.sh - ./scripts/build/build_examples.py --target linux-x64-fabric-bridge-rpc build + ./scripts/build/build_examples.py --target linux-x64-fabric-bridge-rpc-no-ble build ``` ### For Raspberry Pi 4 example: diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.matter b/examples/light-switch-app/light-switch-common/light-switch-app.matter index a18f0c9724cda2..46dd0d70bf43da 100644 --- a/examples/light-switch-app/light-switch-common/light-switch-app.matter +++ b/examples/light-switch-app/light-switch-common/light-switch-app.matter @@ -1897,7 +1897,7 @@ cluster TimeSynchronization = 56 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/light-switch-app/qpg/zap/switch.matter b/examples/light-switch-app/qpg/zap/switch.matter index 23033c78bffda8..dd41fdbcad9541 100644 --- a/examples/light-switch-app/qpg/zap/switch.matter +++ b/examples/light-switch-app/qpg/zap/switch.matter @@ -1953,7 +1953,7 @@ cluster ThreadNetworkDiagnostics = 53 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/lighting-app-data-mode-no-unique-id/lighting-common/lighting-app.matter b/examples/lighting-app-data-mode-no-unique-id/lighting-common/lighting-app.matter index c19c7d7bf8e67a..8b88e7d961fe5f 100644 --- a/examples/lighting-app-data-mode-no-unique-id/lighting-common/lighting-app.matter +++ b/examples/lighting-app-data-mode-no-unique-id/lighting-common/lighting-app.matter @@ -1803,7 +1803,7 @@ cluster EthernetNetworkDiagnostics = 55 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/lighting-app/lighting-common/lighting-app.matter b/examples/lighting-app/lighting-common/lighting-app.matter index 16e722d746b3d1..1fe4d7929d37fa 100644 --- a/examples/lighting-app/lighting-common/lighting-app.matter +++ b/examples/lighting-app/lighting-common/lighting-app.matter @@ -1803,7 +1803,7 @@ cluster EthernetNetworkDiagnostics = 55 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/placeholder/linux/apps/app1/config.matter b/examples/placeholder/linux/apps/app1/config.matter index bf5e6d6b75af16..de9603d2627962 100644 --- a/examples/placeholder/linux/apps/app1/config.matter +++ b/examples/placeholder/linux/apps/app1/config.matter @@ -2711,7 +2711,7 @@ cluster BridgedDeviceBasicInformation = 57 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; @@ -2767,7 +2767,7 @@ cluster Switch = 59 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/examples/placeholder/linux/apps/app2/config.matter b/examples/placeholder/linux/apps/app2/config.matter index 26651684750454..2b6528d55ad018 100644 --- a/examples/placeholder/linux/apps/app2/config.matter +++ b/examples/placeholder/linux/apps/app2/config.matter @@ -2668,7 +2668,7 @@ cluster BridgedDeviceBasicInformation = 57 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; @@ -2724,7 +2724,7 @@ cluster Switch = 59 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index b5195f4ab05eda..2e79c6f8f9cfa9 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -180,6 +180,7 @@ 'src/lib/support/jsontlv/JsonToTlv.h': {'string'}, 'src/lib/support/jsontlv/TlvToJson.h': {'string'}, 'src/lib/support/jsontlv/TextFormat.h': {'string'}, + 'src/lib/support/TemporaryFileStream.h': {'ostream', 'streambuf', 'string'}, 'src/app/icd/client/DefaultICDClientStorage.cpp': {'vector'}, 'src/app/icd/client/DefaultICDClientStorage.h': {'vector'}, 'src/app/icd/client/DefaultICDStorageKey.h': {'vector'}, diff --git a/src/app/tests/suites/certification/Test_TC_CADMIN_1_9.yaml b/src/app/tests/suites/certification/Test_TC_CADMIN_1_9.yaml deleted file mode 100644 index 5ee85f98a48a72..00000000000000 --- a/src/app/tests/suites/certification/Test_TC_CADMIN_1_9.yaml +++ /dev/null @@ -1,603 +0,0 @@ -# Copyright (c) 2023 Project CHIP Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -name: - 24.1.9. [TC-CADMIN-1.9] Device exit commissioning mode after 20 failed - commission attempts [ECM] [DUT - Commissionee] - -PICS: - - CADMIN.S - -config: - nodeId: 0x12344321 - timeout: 700 - nodeIdForDuplicateCommissioning: - type: node_id - defaultValue: 0x11 - nodeId2: - type: node_id - defaultValue: 0xCAFE - nodeId3: - type: node_id - defaultValue: 0xC00FEE - endpoint: 0 - waitAfterCommissioning: - type: int16u - defaultValue: 5000 - discriminator: - type: int16u - defaultValue: 3840 - correctPayload: - type: char_string - defaultValue: "MT:-24J0AFN00KA0648G00" - incorrectSetupCodePayload: - type: char_string - defaultValue: "MT:-24J0AFN00I.0648G00" - PakeVerifier: - type: octet_string - defaultValue: "hex:b96170aae803346884724fe9a3b287c30330c2a660375d17bb205a8cf1aecb350457f8ab79ee253ab6a8e46bb09e543ae422736de501e3db37d441fe344920d09548e4c18240630c4ff4913c53513839b7c07fcc0627a1b8573a149fcd1fa466cf" - PIXIT.CADMIN.CwDuration: - type: int16u - defaultValue: 900 - -tests: - - label: "Precondition: Reset Devices to factory defaults" - PICS: PICS_SDK_CI_ONLY - cluster: "SystemCommands" - command: "FactoryReset" - - - label: "Precondition: Reset Devices to factory defaults" - verification: | - Reset Devices to factory defaults - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP - arguments: - values: - - name: "message" - value: "Factory Reset the DUT and enter 'y' after success" - - name: "expectedValue" - value: "y" - - - label: "Step 1a: TH_CR1 starts a commissioning process with DUT_CE" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId - - name: "payload" - value: correctPayload - - - label: "Step 1b: TH_CR1 commissioned with DUT_CE" - cluster: "DelayCommands" - command: "WaitForCommissionee" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId - - - label: - "Step 2: TH_CR1 opens a commissioning window on DUT_CE using a - commissioning timeout of PIXIT.CADMIN.CwDuration seconds using ECM" - cluster: "Administrator Commissioning" - command: "OpenCommissioningWindow" - timedInteractionTimeoutMs: 10000 - PICS: CADMIN.S.C00.Rsp && PICS_SDK_CI_ONLY - arguments: - values: - - name: "CommissioningTimeout" - value: PIXIT.CADMIN.CwDuration - - name: "PAKEPasscodeVerifier" - value: PakeVerifier - - name: "Discriminator" - value: discriminator - - name: "Iterations" - value: 1000 - - name: "Salt" - value: "SPAKE2P Key Salt" - - - label: "Waiting after opening commissioning window" - PICS: CADMIN.S.C00.Rsp && PICS_SDK_CI_ONLY - cluster: "DelayCommands" - command: "WaitForMs" - arguments: - values: - - name: "ms" - value: waitAfterCommissioning - - #Issue https://github.com/project-chip/connectedhomeip/issues/26127 - - label: - "Step 2: TH_CR1 opens a commissioning window on DUT_CE using a - commissioning timeout of PIXIT.CADMIN.CwDuration seconds using ECM" - verification: | - On TH_CR1 send the below command - - ./chip-tool pairing open-commissioning-window 1 1 PIXIT.CADMIN.CwDuration 1000 3841 - - Verify the Open commisioning window on the DUT_CE(all-cluster-app) Log: - - [1660904553.796857][3537:3537] CHIP:DMG: Received command for Endpoint=0 Cluster=0x0000_003C Command=0x0000_0000 - [1660904553.796951][3537:3537] CHIP:ZCL: Received command to open commissioning window - [1660904553.797255][3537:3537] CHIP:IN: SecureSession[0xaaab142ef7f0]: Allocated Type:1 LSID:34523 - - Verify the Manual pairing code on the TH_CR1(chip-tool) Log: - - [1635864513.699433][3850:3855] CHIP:DMG: ICR moving to [CommandSen] - [1635864513.699489][3850:3855] CHIP:CTL: Manual pairing code: [36177160937] - [1635864513.699566][3850:3855] CHIP:CTL: SetupQRCode: [MT:00000CQM00YZN476420] - [1635864513.699636][3850:3855] CHIP:EM: Sending Standalone Ack for MessageCounter:2599714227 on exchange 60688i - [1635864513.699685][3850:3855] CHIP:IN: Prepared plaintext message 0xffff8a7cd960 to 0x0000000000000000 of type - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP && CADMIN.S.C00.Rsp - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" - - - label: - "Step 3: DNS-SD records shows DUT_CE advertising. Verify that the - DNS-SD advertisement shows CM=2" - PICS: CADMIN.S.C00.Rsp - cluster: "DiscoveryCommands" - command: "FindCommissionable" - response: - values: - - name: "commissioningMode" - value: 2 - - - label: - "Step 4.1: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.2: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.3: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.4: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.5: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.6: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.7: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.8: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.9: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.10: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.11: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.12: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.13: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.14: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.15: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.16: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.17: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.18: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.19: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - - label: - "Step 4.20: TH_CR2 starts a commissioning process with DUT_CE using - Invalid setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: incorrectSetupCodePayload - - name: discoverOnce - value: true - response: - error: FAILURE - - # This step must match the verification step above where we checked `payload` - - label: - "Step 5: TH_CR2 starts a commissioning process with DUT_CE using valid - setup code" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S && PICS_SDK_CI_ONLY - arguments: - values: - - name: "nodeId" - value: nodeId2 - - name: "payload" - value: correctPayload - response: - error: FAILURE - - #Issue https://github.com/project-chip/connectedhomeip/issues/26127 - - label: "Step 5: TH_CR2 starts a commissioning process with DUT_CE" - verification: | - On TH_CR2 send the below command (with correct passcode) - - ./chip-tool pairing code 0xCAFE 36177160937 --commissioner-name beta - - Verify the following error on 21st attempt using correct passcode in TH_CR2(chip-tool) - - [1665484807.015876][5399:5399] CHIP:DL: renamed tmp file to file (/tmp/chip_counters.ini) - [1665484807.016042][5399:5399] CHIP:DL: NVS set: chip-counters/total-operational-hours = 0 (0x0) - [1665484807.016108][5399:5399] CHIP:DL: Inet Layer shutdown - [1665484807.016163][5399:5399] CHIP:DL: BLE Layer shutdown - [1665484807.016215][5399:5399] CHIP:DL: System Layer shutdown - [1665484807.016460][5399:5399] CHIP:TOO: Run command failure: ../../commands/pairing/PairingCommand.cpp:164: CHIP Error 0x00000003: Incorrect state - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP && CADMIN.S.C00.Rsp - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" - - - label: "Step 6: TH_CR3 starts a commissioning process with DUT_CE" - identity: "gamma" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: CADMIN.S && PICS_SDK_CI_ONLY - arguments: - values: - - name: "nodeId" - value: nodeId3 - - name: "payload" - value: correctPayload - response: - error: FAILURE - - #Issue https://github.com/project-chip/connectedhomeip/issues/26127 - - label: "Step 6: TH_CR2 starts a commissioning process with DUT_CE" - verification: | - On TH_CR3 send the below command (with correct passcode) - - ./chip-tool pairing code 0xC00FEE 36177160938 (With correct passcode) --commissioner-name gamma - - Verify the following error on correct passcode in TH_CR3(chip-tool) - [1665484807.015876][5399:5399] CHIP:DL: renamed tmp file to file (/tmp/chip_counters.ini) - [1665484807.016042][5399:5399] CHIP:DL: NVS set: chip-counters/total-operational-hours = 0 (0x0) - [1665484807.016108][5399:5399] CHIP:DL: Inet Layer shutdown - [1665484807.016163][5399:5399] CHIP:DL: BLE Layer shutdown - [1665484807.016215][5399:5399] CHIP:DL: System Layer shutdown - [1665484807.016460][5399:5399] CHIP:TOO: Run command failure: ../../commands/pairing/PairingCommand.cpp:164: CHIP Error 0x00000003: Incorrect state - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP && CADMIN.S.C00.Rsp - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" diff --git a/src/app/zap-templates/zcl/data-model/chip/global-attributes.xml b/src/app/zap-templates/zcl/data-model/chip/global-attributes.xml index 4b473a8ea26037..bad94e46df67bf 100644 --- a/src/app/zap-templates/zcl/data-model/chip/global-attributes.xml +++ b/src/app/zap-templates/zcl/data-model/chip/global-attributes.xml @@ -16,9 +16,7 @@ limitations under the License. --> - ClusterRevision ClusterRevision - FeatureMap FeatureMap AttributeList EventList diff --git a/src/app/zap-templates/zcl/data-model/chip/switch-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/switch-cluster.xml index 72630933a26479..176163fae7fce6 100644 --- a/src/app/zap-templates/zcl/data-model/chip/switch-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/switch-cluster.xml @@ -28,7 +28,7 @@ Two types of switch devices are supported: latching switch (e.g. rocker switch) Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. - + diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 7c43d50c082389..346867226e4261 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -403,6 +403,9 @@ void DeviceController::Shutdown() // assume that all sessions for our fabric belong to us here. mSystemState->CASESessionMgr()->ReleaseSessionsForFabric(mFabricIndex); + // Shut down any bdx transfers we're acting as the server for. + mSystemState->BDXTransferServer()->AbortTransfersForFabric(mFabricIndex); + // TODO: The CASE session manager does not shut down existing CASE // sessions. It just shuts down any ongoing CASE session establishment // we're in the middle of as initiator. Maybe it should shut down diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index ea7ab01b39d2dd..412550214b68ec 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -2594,7 +2594,7 @@ cluster BridgedDeviceBasicInformation = 57 { Two types of switch devices are supported: latching switch (e.g. rocker switch) and momentary switch (e.g. push button), distinguished with their feature flags. Interactions with the switch device are exposed as attributes (for the latching switch) and as events (for both types of switches). An interested party MAY subscribe to these attributes/events and thus be informed of the interactions, and can perform actions based on this, for example by sending commands to perform an action such as controlling a light or a window shade. */ cluster Switch = 59 { - revision 1; + revision 2; bitmap Feature : bitmap32 { kLatchingSwitch = 0x1; diff --git a/src/darwin/Framework/CHIP/MTRDefines_Internal.h b/src/darwin/Framework/CHIP/MTRDefines_Internal.h index 7894f31835a142..e22f00dd4e6a0b 100644 --- a/src/darwin/Framework/CHIP/MTRDefines_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDefines_Internal.h @@ -111,3 +111,42 @@ typedef struct {} variable_hidden_by_mtr_hide; \ return outValue; \ } + +#ifndef MTR_OPTIONAL_ATTRIBUTE +#if __has_feature(objc_arc) +#define MTR_OPTIONAL_ATTRIBUTE(ATTRIBUTE, VALUE, DICTIONARY) \ + { \ + id valueToAdd = VALUE; \ + if (valueToAdd != nil) { \ + CFDictionarySetValue((CFMutableDictionaryRef) DICTIONARY, (CFStringRef) (__bridge const void *) ATTRIBUTE, (const void *) valueToAdd); \ + } \ + } +#else +#define MTR_OPTIONAL_ATTRIBUTE(ATTRIBUTE, VALUE, DICTIONARY) \ + { \ + id valueToAdd = VALUE; \ + if (valueToAdd != nil) { \ + CFDictionarySetValue((CFMutableDictionaryRef) DICTIONARY, (CFStringRef) (const void *) ATTRIBUTE, (const void *) valueToAdd); \ + } \ + } +#endif +#endif + +#ifndef MTR_REMOVE_ATTRIBUTE +#define MTR_REMOVE_ATTRIBUTE(ATTRIBUTE, DICTIONARY) \ + if (ATTRIBUTE != nil && DICTIONARY) { \ + CFDictionaryRemoveValue((CFMutableDictionaryRef) DICTIONARY, (CFStringRef) ATTRIBUTE); \ + } +#endif + +#ifndef MTR_REQUIRED_ATTRIBUTE +#define MTR_REQUIRED_ATTRIBUTE(ATTRIBUTE, VALUE, DICTIONARY) \ + { \ + id valueToAdd = VALUE; \ + if (valueToAdd != nil) { \ + CFDictionarySetValue((CFMutableDictionaryRef) DICTIONARY, (CFStringRef) ATTRIBUTE, (const void *) valueToAdd); \ + } else { \ + MTR_LOG_ERROR("Warning, missing %@ to add to %s", ATTRIBUTE, #DICTIONARY); \ + } \ + } +#endif diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 01b6b2ebbd24e8..a668e02bbfa1ff 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -648,8 +648,9 @@ - (void)_addDelegate:(id)delegate queue:(dispatch_queue_t)que - (void)_delegateAdded { - // Nothing to do; this is a hook for subclasses. If that ever changes for - // some reason, subclasses need to start calling this hook on their super. + os_unfair_lock_assert_owner(&self->_lock); + + // Nothing to do for now. At the moment this is a hook for subclasses. } - (void)removeDelegate:(id)delegate @@ -1459,6 +1460,17 @@ - (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue } } +- (NSArray *> *)getAllAttributesReport +{ +#define MTRDeviceErrorStr "MTRDevice getAllAttributesReport must be handled by subclasses that support it" + MTR_LOG_ERROR(MTRDeviceErrorStr); +#ifdef DEBUG + NSAssert(NO, @MTRDeviceErrorStr); +#endif // DEBUG +#undef MTRDeviceErrorStr + return nil; +} + #ifdef DEBUG - (NSUInteger)unitTestAttributeCount { @@ -1732,6 +1744,16 @@ - (NSNumber * _Nullable)_networkFeatures return result; } +- (void)controllerSuspended +{ + // Nothing to do for now. +} + +- (void)controllerResumed +{ + // Nothing to do for now. +} + @end /* BEGIN DRAGONS: Note methods here cannot be renamed, and are used by private callers, do not rename, remove or modify behavior here */ diff --git a/src/darwin/Framework/CHIP/MTRDeviceController+XPC.mm b/src/darwin/Framework/CHIP/MTRDeviceController+XPC.mm index 87ce5560381cca..1cd02e24648867 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController+XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController+XPC.mm @@ -34,7 +34,13 @@ static NSSet * GetXPCAllowedClasses() { static NSSet * const sXPCAllowedClasses = [NSSet setWithArray:@[ - [NSString class], [NSNumber class], [NSData class], [NSArray class], [NSDictionary class], [NSError class] + [NSString class], + [NSNumber class], + [NSData class], + [NSArray class], + [NSDictionary class], + [NSError class], + [NSDate class], ]]; return sXPCAllowedClasses; } diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index f6dc54fbc1dd19..021ada1852cf9c 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -31,6 +31,7 @@ #import "MTRDeviceControllerLocalTestStorage.h" #import "MTRDeviceControllerStartupParams.h" #import "MTRDeviceControllerStartupParams_Internal.h" +#import "MTRDeviceController_Concrete.h" #import "MTRDeviceController_XPC.h" #import "MTRDevice_Concrete.h" #import "MTRDevice_Internal.h" @@ -82,6 +83,8 @@ #import +// TODO: These strings and their consumers in this file should probably go away, +// since none of them really apply to all controllers. static NSString * const kErrorCommissionerInit = @"Init failure while initializing a commissioner"; static NSString * const kErrorIPKInit = @"Init failure while initializing IPK"; static NSString * const kErrorSigningKeypairInit = @"Init failure while creating signing keypair bridge"; @@ -117,7 +120,6 @@ @implementation MTRDeviceController { MTROperationalCredentialsDelegate * _operationalCredentialsDelegate; MTRDeviceAttestationDelegateBridge * _deviceAttestationDelegateBridge; MTRDeviceControllerFactory * _factory; - NSMapTable * _nodeIDToDeviceMap; os_unfair_lock _underlyingDeviceMapLock; MTRCommissionableBrowser * _commissionableBrowser; MTRAttestationTrustStoreBridge * _attestationTrustStoreBridge; @@ -139,6 +141,8 @@ @implementation MTRDeviceController { os_unfair_lock _assertionLock; } +@synthesize uniqueIdentifier = _uniqueIdentifier; + - (os_unfair_lock_t)deviceMapLock { return &_underlyingDeviceMapLock; @@ -156,8 +160,14 @@ - (instancetype)initForSubclasses:(BOOL)startSuspended _shutdownPending = NO; _assertionLock = OS_UNFAIR_LOCK_INIT; + // All synchronous suspend/resume activity has to be protected by + // @synchronized(self), so that parts of suspend/resume can't + // interleave with each other. Using @synchronized here because + // MTRDevice may call isSuspended. _suspended = startSuspended; + _nodeIDToDeviceMap = [NSMapTable strongToWeakObjectsMapTable]; + return self; } @@ -176,7 +186,7 @@ - (nullable MTRDeviceController *)initWithParameters:(MTRDeviceControllerAbstrac auto * controllerParameters = static_cast(parameters); // MTRDeviceControllerFactory will auto-start in per-controller-storage mode if necessary - return [MTRDeviceControllerFactory.sharedInstance initializeController:self withParameters:controllerParameters error:error]; + return [MTRDeviceControllerFactory.sharedInstance initializeController:[MTRDeviceController_Concrete alloc] withParameters:controllerParameters error:error]; } - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory @@ -330,7 +340,7 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p uuid %@>", NSStringFromClass(self.class), self, _uniqueIdentifier]; + return [NSString stringWithFormat:@"<%@: %p uuid %@>", NSStringFromClass(self.class), self, self.uniqueIdentifier]; } - (BOOL)isRunning @@ -342,28 +352,53 @@ - (BOOL)isRunning - (BOOL)isSuspended { - return _suspended; + @synchronized(self) { + return _suspended; + } } - (void)suspend { - _suspended = YES; + MTR_LOG("%@ suspending", self); + + @synchronized(self) { + _suspended = YES; - // TODO: In the concrete class (which is unused so far!), iterate our - // MTRDevices, tell them to tear down subscriptions. Possibly close all - // CASE sessions for our identity. Possibly try to see whether we can - // change our fabric entry to not advertise and restart advertising. + NSArray * devicesToSuspend; + { + std::lock_guard lock(*self.deviceMapLock); + devicesToSuspend = [self.nodeIDToDeviceMap objectEnumerator].allObjects; + } + + for (MTRDevice * device in devicesToSuspend) { + [device controllerSuspended]; + } - // TODO: What should happen with active commissioning sessions? Presumably - // close them? + // TODO: In the concrete class, consider what should happen with: + // + // * Active commissioning sessions (presumably close them?) + // * CASE sessions in general. + // * Possibly try to see whether we can change our fabric entry to not advertise and restart advertising. + } } - (void)resume { - _suspended = NO; + MTR_LOG("%@ resuming", self); + + @synchronized(self) { + _suspended = NO; - // TODO: In the concrete class (which is unused so far!), iterate our - // MTRDevices, tell them to restart subscriptions. + NSArray * devicesToResume; + { + std::lock_guard lock(*self.deviceMapLock); + devicesToResume = [self.nodeIDToDeviceMap objectEnumerator].allObjects; + } + + for (MTRDevice * device in devicesToResume) { + [device controllerResumed]; + } + } } - (BOOL)matchesPendingShutdownControllerWithOperationalCertificate:(nullable MTRCertificateDERBytes)operationalCertificate andRootCertificate:(nullable MTRCertificateDERBytes)rootCertificate @@ -449,7 +484,7 @@ - (void)cleanupAfterStartup // devices before we start invalidating. MTR_LOG("%s: %@", __PRETTY_FUNCTION__, self); os_unfair_lock_lock(self.deviceMapLock); - NSEnumerator * devices = [_nodeIDToDeviceMap objectEnumerator]; + auto * devices = [self.nodeIDToDeviceMap objectEnumerator].allObjects; [_nodeIDToDeviceMap removeAllObjects]; os_unfair_lock_unlock(self.deviceMapLock); @@ -1718,6 +1753,9 @@ + (void)forceLocalhostAdvertisingOnly @end +// TODO: This should not be in the superclass: either move to +// MTRDeviceController_Concrete.mm, or move into a separate .h/.mm pair of +// files. @implementation MTRDevicePairingDelegateShim - (instancetype)initWithDelegate:(id)delegate { @@ -1770,6 +1808,9 @@ - (void)onPairingDeleted:(NSError * _Nullable)error * Shim to allow us to treat an MTRNOCChainIssuer as an * MTROperationalCertificateIssuer. */ +// TODO: This should not be in the superclass: either move to +// MTRDeviceController_Concrete.mm, or move into a separate .h/.mm pair of +// files. @interface MTROperationalCertificateChainIssuerShim : NSObject @property (nonatomic, readonly) id nocChainIssuer; @property (nonatomic, readonly) BOOL shouldSkipAttestationCertificateValidation; diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm index e0488f974b13b8..9cd3b62340d20a 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm @@ -28,6 +28,7 @@ #import "MTRDeviceController.h" #import "MTRDeviceControllerStartupParams.h" #import "MTRDeviceControllerStartupParams_Internal.h" +#import "MTRDeviceController_Concrete.h" #import "MTRDeviceController_Internal.h" #import "MTRDiagnosticLogsDownloader.h" #import "MTRError_Internal.h" @@ -669,7 +670,7 @@ - (MTRDeviceController * _Nullable)createControllerOnExistingFabric:(MTRDeviceCo return existingController; } - return [self _startDeviceController:[MTRDeviceController alloc] + return [self _startDeviceController:[MTRDeviceController_Concrete alloc] startupParams:startupParams fabricChecker:^MTRDeviceControllerStartupParamsInternal *( FabricTable * fabricTable, MTRDeviceController * controller, CHIP_ERROR & fabricError) { @@ -741,7 +742,7 @@ - (MTRDeviceController * _Nullable)createControllerOnNewFabric:(MTRDeviceControl return nil; } - return [self _startDeviceController:[MTRDeviceController alloc] + return [self _startDeviceController:[MTRDeviceController_Concrete alloc] startupParams:startupParams fabricChecker:^MTRDeviceControllerStartupParamsInternal *( FabricTable * fabricTable, MTRDeviceController * controller, CHIP_ERROR & fabricError) { @@ -960,6 +961,10 @@ - (void)controllerShuttingDown:(MTRDeviceController *)controller _otaProviderDelegateBridge->ControllerShuttingDown(controller); } + if (_diagnosticLogsDownloader != nil) { + [_diagnosticLogsDownloader abortDownloadsForController:controller]; + } + [controller shutDownCppController]; self->_controllerBeingShutDown = nil; diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h b/src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h index 68d725f796a0dd..171c91c889a493 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h @@ -150,6 +150,11 @@ MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)) intermediateCertificate:(MTRCertificateDERBytes _Nullable)intermediateCertificate rootCertificate:(MTRCertificateDERBytes)rootCertificate; +/** + * The root certificate we were initialized with. + */ +@property (nonatomic, copy, readonly) MTRCertificateDERBytes rootCertificate MTR_NEWLY_AVAILABLE; + @end MTR_NEWLY_AVAILABLE diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm index fa2791a908cda7..6a64b6ee84ac8d 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm @@ -338,6 +338,9 @@ + (nullable NSData *)publicKeyFromCertificate:(MTRCertificateDERBytes)certificat @end @implementation MTRDeviceControllerExternalCertificateParameters + +@dynamic rootCertificate; + - (instancetype)initWithStorageDelegate:(id)storageDelegate storageDelegateQueue:(dispatch_queue_t)storageDelegateQueue uniqueIdentifier:(NSUUID *)uniqueIdentifier diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h index c9587e910d3b1a..34c5ca8efad1da 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h @@ -58,11 +58,6 @@ typedef void (^MTRDeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NS */ @property (readonly, nonatomic, getter=isRunning) BOOL running; -/** - * The ID assigned to this controller at creation time. - */ -@property (readonly, nonatomic) NSUUID * uniqueIdentifier MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)); - /** * Return the Node ID assigned to the controller. Will return nil if the * controller is not running (and hence does not know its node id). diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm index 6e7d056270d1e0..1edcb4928943a1 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm @@ -26,7 +26,6 @@ #import "MTRCommissionableBrowserResult_Internal.h" #import "MTRCommissioningParameters.h" #import "MTRConversion.h" -#import "MTRDeviceController.h" #import "MTRDeviceControllerDelegateBridge.h" #import "MTRDeviceControllerFactory_Internal.h" #import "MTRDeviceControllerLocalTestStorage.h" @@ -50,6 +49,7 @@ #import "MTRSetupPayload.h" #import "MTRTimeUtils.h" #import "MTRUnfairLock.h" +#import "MTRUtilities.h" #import "NSDataSpanConversion.h" #import "NSStringSpanConversion.h" #import @@ -80,8 +80,11 @@ #include #include +#include #include +#import + typedef void (^SyncWorkQueueBlock)(void); typedef id (^SyncWorkQueueBlockWithReturnValue)(void); typedef BOOL (^SyncWorkQueueBlockWithBoolReturnValue)(void); @@ -98,7 +101,6 @@ @interface MTRDeviceController_Concrete () @property (nonatomic, readonly) MTRDeviceControllerDelegateBridge * deviceControllerDelegateBridge; @property (nonatomic, readonly) MTROperationalCredentialsDelegate * operationalCredentialsDelegate; @property (nonatomic, readonly) MTRDeviceAttestationDelegateBridge * deviceAttestationDelegateBridge; -@property (nonatomic, readwrite) NSUUID * uniqueIdentifier; @property (nonatomic, readonly) dispatch_queue_t chipWorkQueue; @property (nonatomic, readonly, nullable) MTRDeviceControllerFactory * factory; @property (nonatomic, readonly, nullable) id otaProviderDelegate; @@ -114,19 +116,26 @@ @interface MTRDeviceController_Concrete () @end @implementation MTRDeviceController_Concrete { - // queue used to serialize all work performed by the MTRDeviceController std::atomic _storedFabricIndex; std::atomic> _storedCompressedFabricID; MTRP256KeypairBridge _signingKeypairBridge; MTRP256KeypairBridge _operationalKeypairBridge; + + // Counters to track assertion status and access controlled by the _assertionLock + // TODO: Figure out whether they should live here or in the base class (or + // go away completely!), which depends on how the shutdown codepaths get set up. + NSUInteger _keepRunningAssertionCounter; + BOOL _shutdownPending; + os_unfair_lock _assertionLock; } -// MTRDeviceController ivar internal access -@synthesize uniqueIdentifier = _uniqueIdentifier; +// TODO: Figure out whether the work queue storage lives here or in the superclass +// Right now we seem to have both? @synthesize chipWorkQueue = _chipWorkQueue; @synthesize controllerDataStore = _controllerDataStore; +// TODO: For these remaining ivars, figure out whether they should live here or +// on the superclass. Should not be both. @synthesize factory = _factory; -@synthesize deviceMapLock = _deviceMapLock; @synthesize otaProviderDelegate = _otaProviderDelegate; @synthesize otaProviderDelegateQueue = _otaProviderDelegateQueue; @synthesize commissionableBrowser = _commissionableBrowser; @@ -165,7 +174,7 @@ - (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParamete MTR_LOG_DEBUG("%s: got standard parameters, getting standard device controller from factory", __PRETTY_FUNCTION__); auto * controllerParameters = static_cast(parameters); - // or, if necessary, MTRDeviceControllerFactory will auto-start in per-controller-storage mode if necessary + // Start us up normally. MTRDeviceControllerFactory will auto-start in per-controller-storage mode if necessary. MTRDeviceControllerFactory * factory = MTRDeviceControllerFactory.sharedInstance; id controller = [factory initializeController:self withParameters:controllerParameters @@ -195,7 +204,13 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory if (self = [super initForSubclasses:startSuspended]) { // Make sure our storage is all set up to work as early as possible, // before we start doing anything else with the controller. - _uniqueIdentifier = uniqueIdentifier; + self.uniqueIdentifier = uniqueIdentifier; + + // Setup assertion variables + _keepRunningAssertionCounter = 0; + _shutdownPending = NO; + _assertionLock = OS_UNFAIR_LOCK_INIT; + if (storageDelegate != nil) { if (storageDelegateQueue == nil) { MTR_LOG_ERROR("storageDelegate provided without storageDelegateQueue"); @@ -269,7 +284,6 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory _otaProviderDelegateQueue = otaProviderDelegateQueue; _chipWorkQueue = queue; _factory = factory; - self.nodeIDToDeviceMap = [NSMapTable strongToWeakObjectsMapTable]; _serverEndpoints = [[NSMutableArray alloc] init]; _commissionableBrowser = nil; @@ -310,6 +324,10 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory _concurrentSubscriptionPool = [[MTRAsyncWorkQueue alloc] initWithContext:self width:concurrentSubscriptionPoolSize]; _storedFabricIndex = chip::kUndefinedFabricIndex; + _storedCompressedFabricID = std::nullopt; + self.nodeID = nil; + self.fabricID = nil; + self.rootPublicKey = nil; _storageBehaviorConfiguration = storageBehaviorConfiguration; } @@ -318,7 +336,7 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p uuid %@>", NSStringFromClass(self.class), self, _uniqueIdentifier]; + return [NSString stringWithFormat:@"<%@: %p uuid %@>", NSStringFromClass(self.class), self, self.uniqueIdentifier]; } - (BOOL)isRunning @@ -326,8 +344,68 @@ - (BOOL)isRunning return _cppCommissioner != nullptr; } +- (BOOL)matchesPendingShutdownControllerWithOperationalCertificate:(nullable MTRCertificateDERBytes)operationalCertificate andRootCertificate:(nullable MTRCertificateDERBytes)rootCertificate +{ + if (!operationalCertificate || !rootCertificate) { + return FALSE; + } + NSNumber * nodeID = [MTRDeviceControllerParameters nodeIDFromNOC:operationalCertificate]; + NSNumber * fabricID = [MTRDeviceControllerParameters fabricIDFromNOC:operationalCertificate]; + NSData * publicKey = [MTRDeviceControllerParameters publicKeyFromCertificate:rootCertificate]; + + std::lock_guard lock(_assertionLock); + + // If any of the local above are nil, the return will be false since MTREqualObjects handles them correctly + return _keepRunningAssertionCounter > 0 && _shutdownPending && MTREqualObjects(nodeID, self.nodeID) && MTREqualObjects(fabricID, self.fabricID) && MTREqualObjects(publicKey, self.rootPublicKey); +} + +- (void)addRunAssertion +{ + std::lock_guard lock(_assertionLock); + + // Only take an assertion if running + if ([self isRunning]) { + ++_keepRunningAssertionCounter; + MTR_LOG("%@ Adding keep running assertion, total %lu", self, static_cast(_keepRunningAssertionCounter)); + } +} + +- (void)removeRunAssertion; +{ + std::lock_guard lock(_assertionLock); + + if (_keepRunningAssertionCounter > 0) { + --_keepRunningAssertionCounter; + MTR_LOG("%@ Removing keep running assertion, total %lu", self, static_cast(_keepRunningAssertionCounter)); + + if ([self isRunning] && _keepRunningAssertionCounter == 0 && _shutdownPending) { + MTR_LOG("%@ All assertions removed and shutdown is pending, shutting down", self); + [self finalShutdown]; + } + } +} + +- (void)clearPendingShutdown +{ + std::lock_guard lock(_assertionLock); + _shutdownPending = NO; +} + - (void)shutdown { + std::lock_guard lock(_assertionLock); + + if (_keepRunningAssertionCounter > 0) { + MTR_LOG("%@ Pending shutdown since %lu assertions are present", self, static_cast(_keepRunningAssertionCounter)); + _shutdownPending = YES; + return; + } + [self finalShutdown]; +} + +- (void)finalShutdown +{ + os_unfair_lock_assert_owner(&_assertionLock); MTR_LOG("%@ shutdown called", self); if (_cppCommissioner == nullptr) { // Already shut down. @@ -348,7 +426,7 @@ - (void)cleanupAfterStartup // devices before we start invalidating. MTR_LOG("%s: %@", __PRETTY_FUNCTION__, self); os_unfair_lock_lock(self.deviceMapLock); - NSEnumerator * devices = [self.nodeIDToDeviceMap objectEnumerator]; + auto * devices = [self.nodeIDToDeviceMap objectEnumerator].allObjects; [self.nodeIDToDeviceMap removeAllObjects]; os_unfair_lock_unlock(self.deviceMapLock); @@ -383,11 +461,17 @@ - (void)shutDownCppController // shutdown completes, in case it wants to write to storage as it // shuts down. _storedFabricIndex = chip::kUndefinedFabricIndex; + _storedCompressedFabricID = std::nullopt; + self.nodeID = nil; + self.fabricID = nil; + self.rootPublicKey = nil; + delete commissionerToShutDown; if (_operationalCredentialsDelegate != nil) { _operationalCredentialsDelegate->SetDeviceCommissioner(nullptr); } } + _shutdownPending = NO; } - (void)deinitFromFactory @@ -622,6 +706,15 @@ - (BOOL)startup:(MTRDeviceControllerStartupParamsInternal *)startupParams } self->_storedFabricIndex = fabricIdx; + self->_storedCompressedFabricID = _cppCommissioner->GetCompressedFabricId(); + + chip::Crypto::P256PublicKey rootPublicKey; + if (_cppCommissioner->GetRootPublicKey(rootPublicKey) == CHIP_NO_ERROR) { + self.rootPublicKey = [NSData dataWithBytes:rootPublicKey.Bytes() length:rootPublicKey.Length()]; + self.nodeID = @(_cppCommissioner->GetNodeId()); + self.fabricID = @(_cppCommissioner->GetFabricId()); + } + commissionerInitialized = YES; MTR_LOG("%@ startup succeeded for nodeID 0x%016llX", self, self->_cppCommissioner->GetNodeId()); @@ -647,7 +740,7 @@ - (BOOL)startup:(MTRDeviceControllerStartupParamsInternal *)startupParams if (_controllerDataStore) { // If the storage delegate supports the bulk read API, then a dictionary of nodeID => cluster data dictionary would be passed to the handler. Otherwise this would be a no-op, and stored attributes for MTRDevice objects will be loaded lazily in -deviceForNodeID:. [_controllerDataStore fetchAttributeDataForAllDevices:^(NSDictionary *> * _Nonnull clusterDataByNode) { - MTR_LOG("%@ Loaded attribute values for %lu nodes from storage for controller uuid %@", self, static_cast(clusterDataByNode.count), self->_uniqueIdentifier); + MTR_LOG("%@ Loaded attribute values for %lu nodes from storage for controller uuid %@", self, static_cast(clusterDataByNode.count), self.uniqueIdentifier); std::lock_guard lock(*self.deviceMapLock); NSMutableArray * deviceList = [NSMutableArray array]; @@ -669,7 +762,7 @@ - (BOOL)startup:(MTRDeviceControllerStartupParamsInternal *)startupParams }); }]; } - MTR_LOG("%s: startup: %@", __PRETTY_FUNCTION__, self); + MTR_LOG("%@ startup: %@", NSStringFromClass(self.class), self); return YES; } @@ -1193,7 +1286,7 @@ - (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint [self->_serverEndpoints addObject:endpoint]; [endpoint registerMatterEndpoint]; MTR_LOG("%@ Added server endpoint %u to controller %@", self, static_cast(endpoint.endpointID.unsignedLongLongValue), - self->_uniqueIdentifier); + self.uniqueIdentifier); } errorHandler:^(NSError * error) { MTR_LOG_ERROR("%@ Unexpected failure dispatching to Matter queue on running controller in addServerEndpoint, adding endpoint %u", self, @@ -1220,8 +1313,7 @@ - (void)removeServerEndpointInternal:(MTRServerEndpoint *)endpoint queue:(dispat // tearing it down. [self asyncDispatchToMatterQueue:^() { [self removeServerEndpointOnMatterQueue:endpoint]; - MTR_LOG("%@ Removed server endpoint %u from controller %@", self, static_cast(endpoint.endpointID.unsignedLongLongValue), - self->_uniqueIdentifier); + MTR_LOG("%@ Removed server endpoint %u from controller %@", self, static_cast(endpoint.endpointID.unsignedLongLongValue), self.uniqueIdentifier); if (queue != nil && completion != nil) { dispatch_async(queue, completion); } @@ -1279,6 +1371,9 @@ - (BOOL)checkForStartError:(CHIP_ERROR)errorCode logMsg:(NSString *)logMsg return YES; } +// TODO: Figure out whether this should live here or in superclass; we shouldn't +// have two copies of this thing. Probably after removing code from the +// superclass that should not be there. + (BOOL)checkForError:(CHIP_ERROR)errorCode logMsg:(NSString *)logMsg error:(NSError * __autoreleasing *)error { if (CHIP_NO_ERROR == errorCode) { @@ -1314,6 +1409,15 @@ - (BOOL)checkIsRunning:(NSError * __autoreleasing *)error - (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion { + // TODO: Figure out whether the synchronization here makes sense. What + // happens if this call happens mid-suspend or mid-resume? + if (self.suspended) { + MTR_LOG_ERROR("%@ suspended: can't get session for node %016llX-%016llx (%llu)", self, self.compressedFabricID.unsignedLongLongValue, nodeID, nodeID); + // TODO: Can we do a better error here? + completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE], nil); + return; + } + // Get the corresponding MTRDevice object to determine if the case/subscription pool is to be used MTRDevice * device = [self deviceForNodeID:@(nodeID)]; @@ -1338,6 +1442,15 @@ - (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConn - (void)directlyGetSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion { + // TODO: Figure out whether the synchronization here makes sense. What + // happens if this call happens mid-suspend or mid-resume? + if (self.suspended) { + MTR_LOG_ERROR("%@ suspended: can't get session for node %016llX-%016llx (%llu)", self, self.compressedFabricID.unsignedLongLongValue, nodeID, nodeID); + // TODO: Can we do a better error here? + completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE], nil); + return; + } + [self asyncGetCommissionerOnMatterQueue:^(chip::Controller::DeviceCommissioner * commissioner) { auto connectionBridge = new MTRDeviceConnectionBridge(completion); @@ -1472,20 +1585,8 @@ - (BOOL)syncRunOnWorkQueueWithBoolReturnValue:(SyncWorkQueueBlockWithBoolReturnV - (nullable NSNumber *)compressedFabricID { - assertChipStackLockedByCurrentThread(); - - if (!_cppCommissioner) { - return nil; - } - - return @(_cppCommissioner->GetCompressedFabricId()); -} - -- (NSNumber * _Nullable)syncGetCompressedFabricID -{ - return [self syncRunOnWorkQueueWithReturnValue:^NSNumber * { - return [self compressedFabricID]; - } error:nil]; + auto storedValue = _storedCompressedFabricID.load(); + return storedValue.has_value() ? @(storedValue.value()) : nil; } - (CHIP_ERROR)isRunningOnFabric:(chip::FabricTable *)fabricTable diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h index 5d5acf8063d4c2..993298234f33ef 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h @@ -66,9 +66,11 @@ NS_ASSUME_NONNULL_BEGIN @interface MTRDeviceController () -@property (nonatomic, readwrite, nullable) NSMapTable * nodeIDToDeviceMap; +@property (nonatomic, readonly) NSMapTable * nodeIDToDeviceMap; @property (readonly, assign) os_unfair_lock_t deviceMapLock; +@property (readwrite, nonatomic) NSUUID * uniqueIdentifier; + // queue used to serialize all work performed by the MTRDeviceController // (moved here so subclasses can initialize differently) @property (readwrite, retain) dispatch_queue_t chipWorkQueue; diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.h b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.h index c891c10d726791..87218b1f391a66 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.h @@ -27,7 +27,7 @@ MTR_TESTABLE - (id)initWithUniqueIdentifier:(NSUUID *)UUID machServiceName:(NSString *)machServiceName options:(NSXPCConnectionOptions)options #endif - @property(atomic, retain, readwrite)NSXPCConnection * xpcConnection; + @property(nullable, atomic, retain, readwrite)NSXPCConnection * xpcConnection; @end diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm index 1bf0888ab3b2b4..5b6dcf93f586f8 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm @@ -19,6 +19,7 @@ #import "MTRDefines_Internal.h" #import "MTRDeviceController_Internal.h" #import "MTRDevice_XPC.h" +#import "MTRDevice_XPC_Internal.h" #import "MTRLogging_Internal.h" #import "MTRXPCClientProtocol.h" #import "MTRXPCServerProtocol.h" @@ -33,7 +34,9 @@ @interface MTRDeviceController_XPC () -@property (nonatomic, retain, readwrite) NSUUID * uniqueIdentifier; +@property (nonnull, atomic, readwrite, retain) MTRXPCDeviceControllerParameters * xpcParameters; +@property (atomic, readwrite, assign) NSTimeInterval xpcRetryTimeInterval; +@property (atomic, readwrite, assign) BOOL xpcConnectedOrConnecting; @end @@ -41,40 +44,62 @@ @interface MTRDeviceController_XPC () @implementation MTRDeviceController_XPC -@synthesize uniqueIdentifier = _uniqueIdentifier; ++ (NSMutableSet *)_allowedClasses +{ + static NSArray * sBaseAllowedClasses = @[ + [NSString class], + [NSNumber class], + [NSData class], + [NSArray class], + [NSDictionary class], + [NSError class], + [NSDate class], + ]; + + return [NSMutableSet setWithArray:sBaseAllowedClasses]; +} - (NSXPCInterface *)_interfaceForServerProtocol { NSXPCInterface * interface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCServerProtocol)]; - NSSet * allowedClasses = [NSSet setWithArray:@[ - [NSString class], [NSNumber class], [NSData class], [NSArray class], [NSDictionary class], [NSError class], [MTRCommandPath class], [MTRAttributePath class] + NSMutableSet * allowedClasses = [MTRDeviceController_XPC _allowedClasses]; + [allowedClasses addObjectsFromArray:@[ + [MTRCommandPath class], + [MTRAttributePath class] ]]; [interface setClasses:allowedClasses forSelector:@selector(deviceController:nodeID:invokeCommandWithEndpointID:clusterID:commandID:commandFields:expectedValues:expectedValueInterval:timedInvokeTimeout:serverSideProcessingTimeout:completion:) argumentIndex:0 ofReply:YES]; + return interface; } - (NSXPCInterface *)_interfaceForClientProtocol { NSXPCInterface * interface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCClientProtocol)]; - NSSet * allowedClasses = [NSSet setWithArray:@[ - [NSString class], [NSNumber class], [NSData class], [NSArray class], [NSDictionary class], [NSError class], [MTRAttributePath class] + NSMutableSet * allowedClasses = [MTRDeviceController_XPC _allowedClasses]; + [allowedClasses addObjectsFromArray:@[ + [MTRAttributePath class] ]]; + [interface setClasses:allowedClasses forSelector:@selector(device:receivedAttributeReport:) argumentIndex:1 ofReply:NO]; - allowedClasses = [NSSet setWithArray:@[ - [NSString class], [NSNumber class], [NSData class], [NSArray class], [NSDictionary class], [NSError class], [MTREventPath class] + + allowedClasses = [MTRDeviceController_XPC _allowedClasses]; + [allowedClasses addObjectsFromArray:@[ + [MTREventPath class] ]]; + [interface setClasses:allowedClasses forSelector:@selector(device:receivedEventReport:) argumentIndex:1 ofReply:NO]; + return interface; } @@ -83,6 +108,107 @@ - (NSXPCInterface *)_interfaceForClientProtocol return [self.uniqueIdentifier UUIDString]; } +- (void)_startXPCConnectionRetry +{ + if (!self.xpcConnectedOrConnecting) { + MTR_LOG("%@: XPC Connection retry - Starting retry for XPC Connection", self); + self.xpcRetryTimeInterval = 0.5; + mtr_weakify(self); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (self.xpcRetryTimeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + mtr_strongify(self); + [self _xpcConnectionRetry]; + }); + } else { + MTR_LOG("%@: XPC Connection retry - Not starting retry for XPC Connection, already trying", self); + } +} + +- (void)_xpcConnectionRetry +{ + MTR_LOG("%@: XPC Connection retry - timer hit", self); + if (!self.xpcConnectedOrConnecting) { + if (![self _setupXPCConnection]) { +#if 0 // FIXME: Not sure why this retry is not working, but I will fix this later + MTR_LOG("%@: XPC Connection retry - Scheduling another retry", self); + self.xpcRetryTimeInterval = self.xpcRetryTimeInterval >= 1 ? self.xpcRetryTimeInterval * 2 : 1; + self.xpcRetryTimeInterval = MIN(60.0, self.xpcRetryTimeInterval); + mtr_weakify(self); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.xpcRetryTimeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + mtr_strongify(self); + [self _xpcConnectionRetry]; + }); +#else + MTR_LOG("%@: XPC Connection failed retry - bailing", self); +#endif + } else { + MTR_LOG("%@: XPC Connection retry - connection attempt successful", self); + } + } else { + MTR_LOG("%@: XPC Connection retry - Mid retry, or connected, stopping retry timer", self); + } +} + +- (BOOL)_setupXPCConnection +{ + self.xpcConnection = self.xpcParameters.xpcConnectionBlock(); + + MTR_LOG("%@ Set up XPC Connection: %@", self, self.xpcConnection); + if (self.xpcConnection) { + mtr_weakify(self); + self.xpcConnection.remoteObjectInterface = [self _interfaceForServerProtocol]; + + self.xpcConnection.exportedInterface = [self _interfaceForClientProtocol]; + self.xpcConnection.exportedObject = self; + + self.xpcConnection.interruptionHandler = ^{ + mtr_strongify(self); + MTR_LOG_ERROR("XPC Connection for device controller interrupted: %@", self.xpcParameters.uniqueIdentifier); + self.xpcConnectedOrConnecting = NO; + self.xpcConnection = nil; + [self _startXPCConnectionRetry]; + }; + + self.xpcConnection.invalidationHandler = ^{ + mtr_strongify(self); + MTR_LOG_ERROR("XPC Connection for device controller invalidated: %@", self.xpcParameters.uniqueIdentifier); + self.xpcConnectedOrConnecting = NO; + self.xpcConnection = nil; + [self _startXPCConnectionRetry]; + }; + + MTR_LOG("%@ Activating new XPC connection", self); + [self.xpcConnection activate]; + + [[self.xpcConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) { + MTR_LOG_ERROR("Checkin error: %@", error); + }] deviceController:self.uniqueIdentifier checkInWithContext:[NSDictionary dictionary]]; + + // FIXME: Trying to kick all the MTRDevices attached to this controller to re-establish connections + // This state needs to be stored properly and re-established at connnection time + + MTR_LOG("%@ Starting existing NodeID Registration", self); + for (NSNumber * nodeID in [self.nodeIDToDeviceMap keyEnumerator]) { + MTR_LOG("%@ => Registering nodeID: %@", self, nodeID); + mtr_weakify(self); + + [[self.xpcConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) { + mtr_strongify(self); + MTR_LOG_ERROR("%@ Registration error for device nodeID: %@ : %@", self, nodeID, error); + }] deviceController:self.uniqueIdentifier registerNodeID:nodeID]; + } + + MTR_LOG("%@ Done existing NodeID Registration", self); + self.xpcConnectedOrConnecting = YES; + } else { + MTR_LOG_ERROR("%@ Failed to set up XPC Connection", self); + self.xpcConnectedOrConnecting = NO; + } + + return (self.xpcConnectedOrConnecting); +} + - (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParameters *)parameters error:(NSError * __autoreleasing *)error { @@ -110,30 +236,11 @@ - (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParamete return nil; } - self.xpcConnection = connectionBlock(); self.uniqueIdentifier = UUID; + self.xpcParameters = xpcParameters; self.chipWorkQueue = dispatch_queue_create("MTRDeviceController_XPC_queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); - self.nodeIDToDeviceMap = [NSMapTable strongToWeakObjectsMapTable]; - - MTR_LOG("Set up XPC Connection: %@", self.xpcConnection); - if (self.xpcConnection) { - self.xpcConnection.remoteObjectInterface = [self _interfaceForServerProtocol]; - self.xpcConnection.exportedInterface = [self _interfaceForClientProtocol]; - self.xpcConnection.exportedObject = self; - - self.xpcConnection.interruptionHandler = ^{ - MTR_LOG_ERROR("XPC Connection for device controller interrupted: %@", UUID); - }; - - self.xpcConnection.invalidationHandler = ^{ - MTR_LOG_ERROR("XPC Connection for device controller invalidated: %@", UUID); - }; - - MTR_LOG("Activating new XPC connection"); - [self.xpcConnection activate]; - } else { - MTR_LOG_ERROR("Failed to set up XPC Connection"); + if (![self _setupXPCConnection]) { return nil; } } @@ -159,7 +266,7 @@ - (id)initWithUniqueIdentifier:(NSUUID *)UUID machServiceName:(NSString *)machSe self.xpcConnection.exportedObject = self; MTR_LOG("%s: resuming new XPC connection"); - [self.xpcConnection resume]; + [self.xpcConnection activate]; } else { MTR_LOG_ERROR("Failed to set up XPC Connection"); return nil; @@ -177,14 +284,15 @@ - (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(N os_unfair_lock_assert_owner(self.deviceMapLock); MTRDevice * deviceToReturn = [[MTRDevice_XPC alloc] initWithNodeID:nodeID controller:self]; - // If we're not running, don't add the device to our map. That would - // create a cycle that nothing would break. Just return the device, - // which will be in exactly the state it would be in if it were created - // while we were running and then we got shut down. - if ([self isRunning]) { - [self.nodeIDToDeviceMap setObject:deviceToReturn forKey:nodeID]; - } + [self.nodeIDToDeviceMap setObject:deviceToReturn forKey:nodeID]; MTR_LOG("%s: returning XPC device for node id %@", __PRETTY_FUNCTION__, nodeID); + + mtr_weakify(self); + [[self.xpcConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) { + mtr_strongify(self); + MTR_LOG_ERROR("%@ Registration error for device nodeID: %@ : %@", self, nodeID, error); + }] deviceController:self.uniqueIdentifier registerNodeID:nodeID]; + return deviceToReturn; } @@ -255,6 +363,14 @@ - (oneway void)deviceConfigurationChanged:(NSNumber *)nodeID [device deviceConfigurationChanged:nodeID]; } +- (oneway void)device:(NSNumber *)nodeID internalStateUpdated:(NSDictionary *)dictionary +{ + MTRDevice_XPC * device = (MTRDevice_XPC *) [self deviceForNodeID:nodeID]; + MTR_LOG("Received internalStateUpdated: %@ found device: %@", nodeID, device); + + [device device:nodeID internalStateUpdated:dictionary]; +} + #pragma mark - MTRDeviceController Protocol Client // Not Supported via XPC diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm index 70824dd9bcd7d1..6e3ca4bf32b299 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm +++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm @@ -254,8 +254,6 @@ @interface MTRDevice_Concrete () @property (nonatomic) ReadClient * currentReadClient; @property (nonatomic) SubscriptionCallback * currentSubscriptionCallback; // valid when and only when currentReadClient is valid -@property (nonatomic, weak) id privateInternalStateDelegate; - @end // Declaring selector so compiler won't complain about testing and calling it in _handleReportEnd @@ -468,31 +466,28 @@ - (NSString *)description - (NSDictionary *)_internalProperties { - id vidOrUnknown, pidOrUnknown; - - { - std::lock_guard lock(_descriptionLock); + NSMutableDictionary * properties = [NSMutableDictionary dictionary]; + std::lock_guard lock(_descriptionLock); - vidOrUnknown = _vid ?: @"Unknown"; - pidOrUnknown = _pid ?: @"Unknown"; - // id networkFeatures = _allNetworkFeatures; - // id internalDeviceState = _internalDeviceStateForDescription; - // id lastSubscriptionAttemptWait = _lastSubscriptionAttemptWaitForDescription; - // id mostRecentReportTime = _mostRecentReportTimeForDescription; - // id lastSubscriptionFailureTime = _lastSubscriptionFailureTimeForDescription; - } + MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyKeyVendorID, _vid, properties); + MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyKeyProductID, _pid, properties); + MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyNetworkFeatures, _allNetworkFeatures, properties); + MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyDeviceState, [NSNumber numberWithUnsignedInteger:_internalDeviceStateForDescription], properties); + MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyLastSubscriptionAttemptWait, [NSNumber numberWithUnsignedInt:_lastSubscriptionAttemptWaitForDescription], properties); + MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyMostRecentReportTime, _mostRecentReportTimeForDescription, properties); + MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyLastSubscriptionFailureTime, _lastSubscriptionFailureTimeForDescription, properties); - return @{ - kMTRDeviceInternalPropertyKeyVendorID : vidOrUnknown, - kMTRDeviceInternalPropertyKeyProductID : pidOrUnknown, - }; + return properties; } -- (void)_notifyPrivateInternalPropertiesDelegateOfChanges +- (void)_notifyDelegateOfPrivateInternalPropertiesChanges { - if ([self.privateInternalStateDelegate respondsToSelector:@selector(device:internalStateUpdated:)]) { - [self.privateInternalStateDelegate device:self.nodeID internalStateUpdated:[self _internalProperties]]; - } + os_unfair_lock_assert_owner(&self->_lock); + [self _callDelegatesWithBlock:^(id delegate) { + if ([delegate respondsToSelector:@selector(device:internalStateUpdated:)]) { + [delegate performSelector:@selector(device:internalStateUpdated:) withObject:self withObject:[self _internalProperties]]; + } + }]; } #pragma mark - Time Synchronization @@ -724,14 +719,23 @@ - (BOOL)_subscriptionsAllowed { os_unfair_lock_assert_owner(&self->_lock); - // We should not allow a subscription for device controllers over XPC. - return ![_deviceController isKindOfClass:MTRDeviceControllerOverXPC.class]; + // We should not allow a subscription for suspended controllers or device controllers over XPC. + return _deviceController.isSuspended == NO && ![_deviceController isKindOfClass:MTRDeviceControllerOverXPC.class]; } - (void)_delegateAdded { os_unfair_lock_assert_owner(&self->_lock); + [super _delegateAdded]; + + [self _ensureSubscriptionForExistingDelegates:@"delegate is set"]; +} + +- (void)_ensureSubscriptionForExistingDelegates:(NSString *)reason +{ + os_unfair_lock_assert_owner(&self->_lock); + __block BOOL shouldSetUpSubscription = [self _subscriptionsAllowed]; // For unit testing only. If this ever changes to not being for unit testing purposes, @@ -754,12 +758,14 @@ - (void)_delegateAdded MTR_LOG(" => %@ - device is a thread device, scheduling in pool", self); [self _scheduleSubscriptionPoolWork:^{ std::lock_guard lock(self->_lock); - [self _setupSubscriptionWithReason:@"delegate is set and scheduled subscription is happening"]; + [self _setupSubscriptionWithReason:[NSString stringWithFormat:@"%@ and scheduled subscription is happening", reason]]; } inNanoseconds:0 description:@"MTRDevice setDelegate first subscription"]; } else { - [self _setupSubscriptionWithReason:@"delegate is set and subscription is needed"]; + [self _setupSubscriptionWithReason:[NSString stringWithFormat:@"%@ and subscription is needed", reason]]; } } + + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; } - (void)invalidate @@ -983,7 +989,7 @@ - (void)_changeInternalState:(MTRInternalDeviceState)state }]; /* END DRAGONS */ - [self _notifyPrivateInternalPropertiesDelegateOfChanges]; + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; } } @@ -1185,7 +1191,7 @@ - (void)_handleResubscriptionNeededWithDelay:(NSNumber *)resubscriptionDelayMs std::lock_guard lock(_descriptionLock); _lastSubscriptionFailureTimeForDescription = _lastSubscriptionFailureTime; } - [self _notifyPrivateInternalPropertiesDelegateOfChanges]; + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; deviceUsesThread = [self _deviceUsesThread]; // If a previous resubscription failed, remove the item from the subscription pool. @@ -1236,13 +1242,18 @@ - (void)_setLastSubscriptionAttemptWait:(uint32_t)lastSubscriptionAttemptWait _lastSubscriptionAttemptWaitForDescription = lastSubscriptionAttemptWait; } - [self _notifyPrivateInternalPropertiesDelegateOfChanges]; + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; } - (void)_doHandleSubscriptionReset:(NSNumber * _Nullable)retryDelay { os_unfair_lock_assert_owner(&_lock); + if (_deviceController.isSuspended) { + MTR_LOG("%@ ignoring expected subscription reset on controller suspend", self); + return; + } + // If we are here, then either we failed to establish initial CASE, or we // failed to send the initial SubscribeRequest message, or our ReadClient // has given up completely. Those all count as "we have tried and failed to @@ -1252,7 +1263,7 @@ - (void)_doHandleSubscriptionReset:(NSNumber * _Nullable)retryDelay std::lock_guard lock(_descriptionLock); _lastSubscriptionFailureTimeForDescription = _lastSubscriptionFailureTime; } - [self _notifyPrivateInternalPropertiesDelegateOfChanges]; + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; // if there is no delegate then also do not retry if (![self _delegateExists]) { @@ -1310,6 +1321,7 @@ - (void)_doHandleSubscriptionReset:(NSNumber * _Nullable)retryDelay // For non-Thread-enabled devices, just call the resubscription block after the specified time dispatch_after(dispatch_time(DISPATCH_TIME_NOW, resubscriptionDelayNs), self.queue, resubscriptionBlock); } + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; } - (void)_reattemptSubscriptionNowIfNeededWithReason:(NSString *)reason @@ -1507,7 +1519,8 @@ - (void)unitTestSetMostRecentReportTimes:(NSMutableArray *)mostRecentR std::lock_guard lock(_descriptionLock); _mostRecentReportTimeForDescription = [mostRecentReportTimes lastObject]; } - [self _notifyPrivateInternalPropertiesDelegateOfChanges]; + std::lock_guard lock(_lock); + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; } #endif @@ -1566,7 +1579,6 @@ - (void)_scheduleClusterDataPersistence std::lock_guard lock(_descriptionLock); _mostRecentReportTimeForDescription = [_mostRecentReportTimes lastObject]; } - [self _notifyPrivateInternalPropertiesDelegateOfChanges]; // Calculate running average and update multiplier - need at least 2 items to calculate intervals if (_mostRecentReportTimes.count > 2) { @@ -1613,6 +1625,8 @@ - (void)_scheduleClusterDataPersistence } } + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; + // Do not schedule persistence if device is reporting excessively if ([self _deviceIsReportingExcessively]) { return; @@ -1637,7 +1651,6 @@ - (void)_resetStorageBehaviorState std::lock_guard lock(_descriptionLock); _mostRecentReportTimeForDescription = nil; } - [self _notifyPrivateInternalPropertiesDelegateOfChanges]; _deviceReportingExcessivelyStartTime = nil; _reportToPersistenceDelayCurrentMultiplier = 1; @@ -1646,6 +1659,8 @@ - (void)_resetStorageBehaviorState if ([self _dataStoreExists]) { [self _persistClusterData]; } + + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; } - (void)setStorageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration @@ -1699,6 +1714,8 @@ - (void)_handleReportEnd } }]; #endif + + [self _notifyDelegateOfPrivateInternalPropertiesChanges]; } - (BOOL)_interestedPaths:(NSArray * _Nullable)interestedPaths includesAttributePath:(MTRAttributePath *)attributePath @@ -3092,6 +3109,12 @@ - (void)_performScheduledExpirationCheck - (NSDictionary *)_attributeValueDictionaryForAttributePath:(MTRAttributePath *)attributePath { std::lock_guard lock(_lock); + return [self _lockedAttributeValueDictionaryForAttributePath:attributePath]; +} + +- (NSDictionary *)_lockedAttributeValueDictionaryForAttributePath:(MTRAttributePath *)attributePath +{ + os_unfair_lock_assert_owner(&self->_lock); // First check expected value cache NSArray * expectedValue = _expectedValueCache[attributePath]; @@ -3478,6 +3501,31 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray *> *)getAllAttributesReport +{ + std::lock_guard lock(_lock); + + NSMutableArray * attributeReport = [NSMutableArray array]; + for (MTRClusterPath * clusterPath in [self _knownClusters]) { + MTRDeviceClusterData * clusterData = [self _clusterDataForPath:clusterPath]; + + for (NSNumber * attributeID in clusterData.attributes) { + auto * attributePath = [MTRAttributePath attributePathWithEndpointID:clusterPath.endpoint + clusterID:clusterPath.cluster + attributeID:attributeID]; + + // Construct response-value dictionary with the data-value dictionary returned by + // _lockedAttributeValueDictionaryForAttributePath, to takes into consideration expected values as well. + [attributeReport addObject:@{ + MTRAttributePathKey : attributePath, + MTRDataKey : [self _lockedAttributeValueDictionaryForAttributePath:attributePath] + }]; + } + } + + return attributeReport; +} + #ifdef DEBUG - (NSUInteger)unitTestAttributeCount { @@ -3975,6 +4023,34 @@ - (NSNumber * _Nullable)_networkFeatures return result; } +- (void)controllerSuspended +{ + [super controllerSuspended]; + + std::lock_guard lock(self->_lock); + [self _resetSubscriptionWithReasonString:@"Controller suspended"]; + + // Ensure that any pre-existing resubscribe attempts we control don't try to + // do anything. + _reattemptingSubscription = NO; +} + +- (void)controllerResumed +{ + [super controllerResumed]; + + std::lock_guard lock(self->_lock); + + if (![self _delegateExists]) { + MTR_LOG("%@ ignoring controller resume: no delegates", self); + return; + } + + // Use _ensureSubscriptionForExistingDelegates so that the subscriptions + // will go through the pool as needed, not necessarily happen immediately. + [self _ensureSubscriptionForExistingDelegates:@"Controller resumed"]; +} + @end /* BEGIN DRAGONS: Note methods here cannot be renamed, and are used by private callers, do not rename, remove or modify behavior here */ diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index c3b6b60fa6314e..7a91b926560f30 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -196,12 +196,22 @@ MTR_DIRECT_MEMBERS - (BOOL)_delegateExists; +// Must be called by subclasses or MTRDevice implementation only. +- (void)_delegateAdded; + #ifdef DEBUG // Only used for unit test purposes - normal delegate should not expect or handle being called back synchronously // Returns YES if a delegate is called - (void)_callFirstDelegateSynchronouslyWithBlock:(void (^)(id delegate))block; #endif +// Used to generate attribute report that contains all known attributes, taking into consideration expected values +- (NSArray *> *)getAllAttributesReport; + +// Hooks for controller suspend/resume. +- (void)controllerSuspended; +- (void)controllerResumed; + @end #pragma mark - MTRDevice internal state monitoring @@ -225,6 +235,10 @@ static NSString * const sLastInitialSubscribeLatencyKey = @"lastInitialSubscribe // Concrete to XPC internal state property dictionary keys static NSString * const kMTRDeviceInternalPropertyKeyVendorID = @"MTRDeviceInternalStateKeyVendorID"; static NSString * const kMTRDeviceInternalPropertyKeyProductID = @"MTRDeviceInternalStateKeyProductID"; -// TODO: more internal properties +static NSString * const kMTRDeviceInternalPropertyNetworkFeatures = @"MTRDeviceInternalPropertyNetworkFeatures"; +static NSString * const kMTRDeviceInternalPropertyDeviceState = @"MTRDeviceInternalPropertyDeviceState"; +static NSString * const kMTRDeviceInternalPropertyLastSubscriptionAttemptWait = @"kMTRDeviceInternalPropertyLastSubscriptionAttemptWait"; +static NSString * const kMTRDeviceInternalPropertyMostRecentReportTime = @"MTRDeviceInternalPropertyMostRecentReportTime"; +static NSString * const kMTRDeviceInternalPropertyLastSubscriptionFailureTime = @"MTRDeviceInternalPropertyLastSubscriptionFailureTime"; NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm index 7267eb3a2b4f7e..5279933104a11c 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm @@ -103,32 +103,35 @@ - (void)dealloc - (NSString *)description { - // TODO: Figure out whether, and if so how, to log: VID, PID, WiFi, Thread, - // internalDeviceState (do we even have such a thing here?), last - // subscription attempt wait (does that apply to us?) queued work (do we - // have any?), last report, last subscription failure (does that apply to us?). - return [NSString - stringWithFormat:@"<%@: %p, node: %016llX-%016llX (%llu), VID: %@, PID: %@, controller: %@>", NSStringFromClass(self.class), self, _deviceController.compressedFabricID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, [self _vid], [self _pid], _deviceController.uniqueIdentifier]; -} + NSString * wifi; + NSString * thread; + NSNumber * networkFeatures = [self._internalState objectForKey:kMTRDeviceInternalPropertyNetworkFeatures]; -- (nullable NSNumber *)_internalStateNumberOrNilForKey:(NSString *)key -{ - if ([_internalState[key] isKindOfClass:NSNumber.class]) { - NSNumber * number = _internalState[key]; - return number; + if (networkFeatures == nil) { + wifi = @"NO"; + thread = @"NO"; } else { - return nil; + wifi = YES_NO(networkFeatures.unsignedLongLongValue & MTRNetworkCommissioningFeatureWiFiNetworkInterface); + thread = YES_NO(networkFeatures.unsignedLongLongValue & MTRNetworkCommissioningFeatureThreadNetworkInterface); } -} -- (nullable NSNumber *)_vid -{ - return [self _internalStateNumberOrNilForKey:kMTRDeviceInternalPropertyKeyVendorID]; -} + // TODO: Add these to the description + // MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyDeviceState, _internalDeviceStateForDescription, properties); + // MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyLastSubscriptionAttemptWait, _lastSubscriptionAttemptWaitForDescription, properties); + // MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyMostRecentReportTime, _mostRecentReportTimeForDescription, properties); + // MTR_OPTIONAL_ATTRIBUTE(kMTRDeviceInternalPropertyLastSubscriptionFailureTime, _lastSubscriptionFailureTimeForDescription, properties); -- (nullable NSNumber *)_pid -{ - return [self _internalStateNumberOrNilForKey:kMTRDeviceInternalPropertyKeyProductID]; + return [NSString + stringWithFormat:@"<%@: %p, node: %016llX-%016llX (%llu), VID: %@, PID: %@, WiFi: %@, Thread: %@, controller: %@>", + NSStringFromClass(self.class), self, + _deviceController.compressedFabricID.unsignedLongLongValue, + _nodeID.unsignedLongLongValue, + _nodeID.unsignedLongLongValue, + [self._internalState objectForKey:kMTRDeviceInternalPropertyKeyVendorID], + [self._internalState objectForKey:kMTRDeviceInternalPropertyKeyProductID], + wifi, + thread, + _deviceController.uniqueIdentifier]; } #pragma mark - Client Callbacks (MTRDeviceDelegate) @@ -187,6 +190,12 @@ - (oneway void)deviceConfigurationChanged:(NSNumber *)nodeID }]; } +- (oneway void)device:(NSNumber *)nodeID internalStateUpdated:(NSDictionary *)dictionary +{ + [self _setInternalState:dictionary]; + MTR_LOG("%@ internal state updated", self); +} + #pragma mark - Remote Commands MTR_DEVICE_SIMPLE_REMOTE_XPC_GETTER(state, MTRDeviceState, MTRDeviceStateUnknown, getStateWithReply) diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.h b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.h index f10fea520fb4fb..da7a780c1237f0 100644 --- a/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.h +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.h @@ -31,12 +31,17 @@ NS_ASSUME_NONNULL_BEGIN @interface MTRDiagnosticLogsDownloader : NSObject - (chip::bdx::BDXTransferServerDelegate *)getBridge; +// Must be called on Matter queue - (void)downloadLogFromNodeWithID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller type:(MTRDiagnosticLogType)type timeout:(NSTimeInterval)timeout queue:(dispatch_queue_t)queue completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion; + +// Must be called on Matter queue +- (void)abortDownloadsForController:(MTRDeviceController *)controller; + @end NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm index ac388ebdb72074..69dfb86d1da40f 100644 --- a/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm @@ -85,6 +85,9 @@ - (MTRDownload * _Nullable)add:(MTRDiagnosticLogType)type queue:(dispatch_queue_t)queue completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion done:(void (^)(MTRDownload * finishedDownload))done; + +- (void)abortDownloadsForController:(MTRDeviceController *)controller; + @end @interface MTRDiagnosticLogsDownloader () @@ -351,6 +354,21 @@ - (MTRDownload * _Nullable)add:(MTRDiagnosticLogType)type return download; } +- (void)abortDownloadsForController:(MTRDeviceController *)controller +{ + assertChipStackLockedByCurrentThread(); + + auto fabricIndex = @(controller.fabricIndex); + for (MTRDownload * download in [_downloads copy]) { + if (![download.fabricIndex isEqual:fabricIndex]) { + continue; + } + + [download failure:[MTRError errorForCHIPErrorCode:CHIP_ERROR_CANCELLED]]; + [self remove:download]; + } +} + - (void)remove:(MTRDownload *)download { assertChipStackLockedByCurrentThread(); @@ -444,6 +462,13 @@ - (void)downloadLogFromNodeWithID:(NSNumber *)nodeID } } +- (void)abortDownloadsForController:(MTRDeviceController *)controller; +{ + assertChipStackLockedByCurrentThread(); + + [_downloads abortDownloadsForController:controller]; +} + - (void)handleBDXTransferSessionBeginForFileDesignator:(NSString *)fileDesignator fabricIndex:(NSNumber *)fabricIndex nodeID:(NSNumber *)nodeID diff --git a/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCClientProtocol.h b/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCClientProtocol.h index b26b6d88849193..cfa59db8a73303 100644 --- a/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCClientProtocol.h +++ b/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCClientProtocol.h @@ -27,9 +27,6 @@ MTR_NEWLY_AVAILABLE - (oneway void)deviceBecameActive:(NSNumber *)nodeID; - (oneway void)deviceCachePrimed:(NSNumber *)nodeID; - (oneway void)deviceConfigurationChanged:(NSNumber *)nodeID; - -@optional -// temporarily optional to avoid lockstep needs - (oneway void)device:(NSNumber *)nodeID internalStateUpdated:(NSDictionary *)dictionary; @end diff --git a/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h b/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h index 1ebdea5d3757ec..52a5d2228fa524 100644 --- a/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h +++ b/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h @@ -70,9 +70,6 @@ MTR_NEWLY_AVAILABLE //- (oneway void)deviceController:(NSUUID *)controller removeServerEndpoint:(MTRServerEndpoint *)endpoint; - (oneway void)deviceController:(NSUUID *)controller shutdownDeviceController:(NSUUID *)controller; - -@optional -// register / unregister temporarily optional to avoid lockstep needs - (oneway void)deviceController:(NSUUID *)controller registerNodeID:(NSNumber *)nodeID; - (oneway void)deviceController:(NSUUID *)controller unregisterNodeID:(NSNumber *)nodeID; diff --git a/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m b/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m index 185f4981cff405..15ef7a839de97a 100644 --- a/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m +++ b/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m @@ -17,9 +17,9 @@ #import +#import "MTRTestCase+ServerAppRunner.h" #import "MTRTestCase.h" #import "MTRTestKeys.h" -#import "MTRTestServerAppRunner.h" #import "MTRTestStorage.h" // Fixture 1: chip-all-clusters-app --KVS "$(mktemp -t chip-test-kvs)" --interface-id -1 @@ -31,8 +31,6 @@ static const uint16_t kDiscoverDeviceTimeoutInSeconds = 10; static const uint16_t kExpectedDiscoveredDevicesCount = 3; -static bool sHelperAppsStarted = false; - // Singleton controller we use. static MTRDeviceController * sController = nil; @@ -180,6 +178,17 @@ + (void)setUp XCTAssertNotNil(controller); sController = controller; + + // Start the helper apps our tests use. + for (NSString * payload in @[ + @"MT:Y.K90SO527JA0648G00", + @"MT:-24J0AFN00I40648G00", + ]) { + BOOL started = [self startAppWithName:@"all-clusters" + arguments:@[] + payload:payload]; + XCTAssertTrue(started); + } } + (void)tearDown @@ -197,21 +206,6 @@ + (void)tearDown - (void)setUp { [super setUp]; - - if (!sHelperAppsStarted) { - for (NSString * payload in @[ - @"MT:Y.K90SO527JA0648G00", - @"MT:-24J0AFN00I40648G00", - ]) { - __auto_type * appRunner = [[MTRTestServerAppRunner alloc] initCrossTestWithAppName:@"all-clusters" - arguments:@[] - payload:payload - testcase:self]; - XCTAssertNotNil(appRunner); - } - sHelperAppsStarted = true; - } - [self setContinueAfterFailure:NO]; } diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index da059fc505de8c..615ff63996a27e 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -4022,6 +4022,7 @@ - (void)test037_MTRDeviceMultipleDelegatesGetReports // For unit test no real data is needed, but timestamp is required }; } + - (void)test038_MTRDeviceMultipleDelegatesInterestedPaths { dispatch_queue_t queue = dispatch_get_main_queue(); @@ -4274,6 +4275,78 @@ - (void)test038_MTRDeviceMultipleDelegatesInterestedPaths XCTAssertEqual(eventsReceived4, 36); } +- (void)test039_GetAllAttributesReport +{ + dispatch_queue_t queue = dispatch_get_main_queue(); + + // First start with clean slate by removing the MTRDevice and clearing the persisted cache + __auto_type * device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:sController]; + [sController removeDevice:device]; + [sController.controllerDataStore clearAllStoredClusterData]; + NSDictionary * storedClusterDataAfterClear = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)]; + XCTAssertEqual(storedClusterDataAfterClear.count, 0); + + // Now recreate device and get subscription primed + device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:sController]; + XCTestExpectation * gotReportEnd = [self expectationWithDescription:@"Report end for delegate"]; + + __auto_type * delegate = [[MTRDeviceTestDelegateWithSubscriptionSetupOverride alloc] init]; + delegate.skipSetupSubscription = YES; + __weak __auto_type weakdelegate = delegate; + __block NSUInteger attributesReceived = 0; + delegate.onAttributeDataReceived = ^(NSArray *> * data) { + attributesReceived += data.count; + }; + delegate.onReportEnd = ^{ + [gotReportEnd fulfill]; + __strong __auto_type strongDelegate = weakdelegate; + strongDelegate.onReportEnd = nil; + }; + + [device addDelegate:delegate queue:queue]; + + // Now inject attributes and check that each delegate gets the right set of attributes + NSMutableArray * attributeReport = [NSMutableArray array]; + // Construct 36 attributes with endpoints 1~4, clusters 11 ~ 33, and attributes 111~333 + for (int i = 1; i <= 4; i++) { + for (int j = 1; j <= 3; j++) { + for (int k = 1; k <= 3; k++) { + int endpointID = i; + int clusterID = i * 10 + j; + int attributeID = i * 100 + j * 10 + k; + int value = attributeID + 10000; + [attributeReport addObject:[self _testAttributeResponseValueWithEndpointID:@(endpointID) clusterID:@(clusterID) attributeID:@(attributeID) value:value]]; + } + } + } + [device unitTestInjectAttributeReport:attributeReport fromSubscription:YES]; + + [self waitForExpectations:@[ gotReportEnd ] timeout:60]; + + NSArray * allAttributesReport = [device getAllAttributesReport]; + + XCTAssertEqual(allAttributesReport.count, attributeReport.count); + + for (NSDictionary * newResponseValueDict in allAttributesReport) { + MTRAttributePath * newPath = newResponseValueDict[MTRAttributePathKey]; + NSDictionary * newDataValueDict = newResponseValueDict[MTRDataKey]; + NSNumber * newValue = newDataValueDict[MTRValueKey]; + XCTAssertNotNil(newValue); + + for (NSDictionary * originalResponseValueDict in attributeReport) { + MTRAttributePath * originalPath = originalResponseValueDict[MTRAttributePathKey]; + // Find same attribute path and compare value + if ([newPath isEqual:originalPath]) { + NSDictionary * originalDataValueDict = originalResponseValueDict[MTRDataKey]; + NSNumber * originalValue = originalDataValueDict[MTRValueKey]; + XCTAssertNotNil(originalValue); + XCTAssertEqualObjects(newValue, originalValue); + continue; + } + } + } +} + @end @interface MTRDeviceEncoderTests : XCTestCase diff --git a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m index 3c8cad690ad83b..f2deba2f1ddda6 100644 --- a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m +++ b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m @@ -20,10 +20,10 @@ #import "MTRDeviceTestDelegate.h" #import "MTRErrorTestUtils.h" +#import "MTRTestCase+ServerAppRunner.h" #import "MTRTestCase.h" #import "MTRTestKeys.h" #import "MTRTestResetCommissioneeHelper.h" -#import "MTRTestServerAppRunner.h" #import "MTRTestStorage.h" // system dependencies @@ -80,7 +80,7 @@ - (NSString *)createImageFromRawImage:(NSString *)rawImage withVersion:(NSNumber - (MTRDevice *)commissionDeviceWithPayload:(NSString *)payloadString nodeID:(NSNumber *)nodeID; @end -@interface MTROTARequestorAppRunner : MTRTestServerAppRunner +@interface MTROTARequestorAppRunner : NSObject @property (nonatomic, copy) NSString * downloadFilePath; - (instancetype)initWithPayload:(NSString *)payload testcase:(MTROTAProviderTests *)testcase; @@ -99,14 +99,15 @@ - (MTRDevice *)commissionWithNodeID:(NSNumber *)nodeID - (instancetype)initWithPayload:(NSString *)payload testcase:(MTROTAProviderTests *)testcase { - __auto_type * downloadFilePath = [NSString stringWithFormat:@"/tmp/chip-ota-requestor-downloaded-image%u", [MTRTestServerAppRunner nextUniqueIndex]]; + __auto_type * downloadFilePath = [NSString stringWithFormat:@"/tmp/chip-ota-requestor-downloaded-image%u", [MTROTAProviderTests nextUniqueIndex]]; __auto_type * extraArguments = @[ @"--otaDownloadPath", downloadFilePath, @"--autoApplyImage", ]; - if (!(self = [super initWithAppName:@"ota-requestor" arguments:extraArguments payload:payload testcase:testcase])) { + BOOL started = [testcase startAppWithName:@"ota-requestor" arguments:extraArguments payload:payload]; + if (!started) { return nil; } diff --git a/src/darwin/Framework/CHIPTests/MTRPairingTests.m b/src/darwin/Framework/CHIPTests/MTRPairingTests.m index 38f7667590bca7..cd1802dff3dd47 100644 --- a/src/darwin/Framework/CHIPTests/MTRPairingTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPairingTests.m @@ -19,9 +19,9 @@ #import #import "MTRErrorTestUtils.h" +#import "MTRTestCase+ServerAppRunner.h" #import "MTRTestCase.h" #import "MTRTestKeys.h" -#import "MTRTestServerAppRunner.h" #import "MTRTestStorage.h" static const uint16_t kPairingTimeoutInSeconds = 10; @@ -186,14 +186,13 @@ - (void)tearDown - (void)startServerApp { // For manual testing, CASE retry code paths can be tested by adding --faults chip_CASEServerBusy_f1 (or similar) - __auto_type * appRunner = [[MTRTestServerAppRunner alloc] initWithAppName:@"all-clusters" - arguments:@[ - @"--dac_provider", - [self absolutePathFor:@"credentials/development/commissioner_dut/struct_cd_origin_pid_vid_correct/test_case_vector.json"], - ] - payload:kOnboardingPayload - testcase:self]; - XCTAssertNotNil(appRunner); + BOOL started = [self startAppWithName:@"all-clusters" + arguments:@[ + @"--dac_provider", + [self absolutePathFor:@"credentials/development/commissioner_dut/struct_cd_origin_pid_vid_correct/test_case_vector.json"], + ] + payload:kOnboardingPayload]; + XCTAssertTrue(started); } // attestationDelegate and failSafeExtension can both be nil diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m index db8bd300df94e1..ccce34792f9db8 100644 --- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m @@ -24,12 +24,12 @@ #import "MTRDevice_Internal.h" #import "MTRErrorTestUtils.h" #import "MTRFabricInfoChecker.h" +#import "MTRTestCase+ServerAppRunner.h" #import "MTRTestCase.h" #import "MTRTestDeclarations.h" #import "MTRTestKeys.h" #import "MTRTestPerControllerStorage.h" #import "MTRTestResetCommissioneeHelper.h" -#import "MTRTestServerAppRunner.h" static const uint16_t kPairingTimeoutInSeconds = 10; static const uint16_t kTimeoutInSeconds = 3; @@ -262,11 +262,8 @@ - (nullable MTRDeviceController *)startControllerWithRootKeys:(MTRTestKeys *)roo nodeID:(NSNumber *)nodeID storage:(MTRTestPerControllerStorage *)storage caseAuthenticatedTags:(NSSet * _Nullable)caseAuthenticatedTags + paramsModifier:(void (^_Nullable)(MTRDeviceControllerExternalCertificateParameters *))paramsModifier error:(NSError * __autoreleasing *)error - certificateIssuer: - (MTRPerControllerStorageTestsCertificateIssuer * __autoreleasing *)certificateIssuer - concurrentSubscriptionPoolSize:(NSUInteger)concurrentSubscriptionPoolSize - storageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration * _Nullable)storageBehaviorConfiguration { XCTAssertTrue(error != NULL); @@ -295,33 +292,60 @@ - (nullable MTRDeviceController *)startControllerWithRootKeys:(MTRTestKeys *)roo intermediateCertificate:nil rootCertificate:root]; XCTAssertNotNil(params); - // TODO: This is only used by testControllerServer. If that moves - // elsewhere, take this back out again. - params.shouldAdvertiseOperational = YES; - - __auto_type * ourCertificateIssuer = [[MTRPerControllerStorageTestsCertificateIssuer alloc] initWithRootCertificate:root - intermediateCertificate:nil - signingKey:rootKeys - fabricID:fabricID]; - XCTAssertNotNil(ourCertificateIssuer); - - if (certificateIssuer) { - *certificateIssuer = ourCertificateIssuer; - } - - [params setOperationalCertificateIssuer:ourCertificateIssuer queue:dispatch_get_main_queue()]; - if (concurrentSubscriptionPoolSize > 0) { - params.concurrentSubscriptionEstablishmentsAllowedOnThread = concurrentSubscriptionPoolSize; - } - - if (storageBehaviorConfiguration) { - params.storageBehaviorConfiguration = storageBehaviorConfiguration; + if (paramsModifier) { + paramsModifier(params); } return [[MTRDeviceController alloc] initWithParameters:params error:error]; } +- (nullable MTRDeviceController *)startControllerWithRootKeys:(MTRTestKeys *)rootKeys + operationalKeys:(MTRTestKeys *)operationalKeys + fabricID:(NSNumber *)fabricID + nodeID:(NSNumber *)nodeID + storage:(MTRTestPerControllerStorage *)storage + caseAuthenticatedTags:(NSSet * _Nullable)caseAuthenticatedTags + error:(NSError * __autoreleasing *)error + certificateIssuer: + (MTRPerControllerStorageTestsCertificateIssuer * __autoreleasing *)certificateIssuer + concurrentSubscriptionPoolSize:(NSUInteger)concurrentSubscriptionPoolSize + storageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration * _Nullable)storageBehaviorConfiguration +{ + return [self startControllerWithRootKeys:rootKeys + operationalKeys:operationalKeys + fabricID:fabricID + nodeID:nodeID + storage:storage + caseAuthenticatedTags:caseAuthenticatedTags + paramsModifier:^(MTRDeviceControllerExternalCertificateParameters * params) { + // TODO: This is only used by testControllerServer. If that moves + // elsewhere, take this back out again. + params.shouldAdvertiseOperational = YES; + + __auto_type * ourCertificateIssuer = [[MTRPerControllerStorageTestsCertificateIssuer alloc] initWithRootCertificate:params.rootCertificate + intermediateCertificate:nil + signingKey:rootKeys + fabricID:fabricID]; + XCTAssertNotNil(ourCertificateIssuer); + + if (certificateIssuer) { + *certificateIssuer = ourCertificateIssuer; + } + + [params setOperationalCertificateIssuer:ourCertificateIssuer queue:dispatch_get_main_queue()]; + + if (concurrentSubscriptionPoolSize > 0) { + params.concurrentSubscriptionEstablishmentsAllowedOnThread = concurrentSubscriptionPoolSize; + } + + if (storageBehaviorConfiguration) { + params.storageBehaviorConfiguration = storageBehaviorConfiguration; + } + } + error:error]; +} + - (nullable MTRDeviceController *)startControllerWithRootKeys:(MTRTestKeys *)rootKeys operationalKeys:(MTRTestKeys *)operationalKeys fabricID:(NSNumber *)fabricID @@ -462,6 +486,7 @@ - (void)test001_BasicControllerStartup XCTAssertNil(error); XCTAssertNotNil(controller); XCTAssertTrue([controller isRunning]); + XCTAssertFalse(controller.suspended); XCTAssertEqualObjects(controller.controllerNodeID, nodeID); @@ -1611,6 +1636,128 @@ - (void)test011_TestDataStoreMTRDeviceWithStorageBehaviorOptimizationDisabled [self doDataStoreMTRDeviceTestWithStorageDelegate:[[MTRTestPerControllerStorage alloc] initWithControllerID:[NSUUID UUID]] disableStorageBehaviorOptimization:YES]; } +// TODO: Factor out startControllerWithRootKeys into a test helper, move these +// suspension tests to a different file. +- (void)test012_startSuspended +{ + NSError * error; + __auto_type * storageDelegate = [[MTRTestPerControllerStorage alloc] initWithControllerID:[NSUUID UUID]]; + __auto_type * controller = [self startControllerWithRootKeys:[[MTRTestKeys alloc] init] + operationalKeys:[[MTRTestKeys alloc] init] + fabricID:@555 + nodeID:@888 + storage:storageDelegate + caseAuthenticatedTags:nil + paramsModifier:^(MTRDeviceControllerExternalCertificateParameters * params) { + params.startSuspended = YES; + } + error:&error]; + + XCTAssertNil(error); + XCTAssertNotNil(controller); + XCTAssertTrue(controller.running); + XCTAssertTrue(controller.suspended); + [controller shutdown]; +} + +- (void)test013_suspendDevices +{ + NSNumber * deviceID = @(17); + __auto_type * device = [self getMTRDevice:deviceID]; + __auto_type * controller = device.deviceController; + + XCTAssertFalse(controller.suspended); + + __auto_type queue = dispatch_get_main_queue(); + __auto_type * delegate = [[MTRDeviceTestDelegate alloc] init]; + + XCTestExpectation * initialSubscriptionExpectation = [self expectationWithDescription:@"Subscription has been set up"]; + XCTestExpectation * initialReachableExpectation = [self expectationWithDescription:@"Device initially became reachable"]; + XCTestExpectation * initialUnreachableExpectation = [self expectationWithDescription:@"Device initially became unreachable"]; + initialUnreachableExpectation.inverted = YES; + + delegate.onReachable = ^{ + [initialReachableExpectation fulfill]; + }; + + delegate.onNotReachable = ^{ + // We do not expect to land here. + [initialUnreachableExpectation fulfill]; + }; + + delegate.onReportEnd = ^{ + [initialSubscriptionExpectation fulfill]; + }; + + [device setDelegate:delegate queue:queue]; + [self waitForExpectations:@[ initialReachableExpectation, initialSubscriptionExpectation ] timeout:60]; + // Separately wait for the unreachable bit, so we don't end up waiting 60 + // seconds for it. + [self waitForExpectations:@[ initialUnreachableExpectation ] timeout:0]; + + // Test that sending a command works. Clear the delegate's onReportEnd + // first, so reports from the command don't trigger it. + delegate.onReportEnd = nil; + XCTestExpectation * toggle1Expectation = [self expectationWithDescription:@"toggle 1"]; + __auto_type * cluster = [[MTRClusterOnOff alloc] initWithDevice:device endpointID:@(1) queue:queue]; + [cluster toggleWithExpectedValues:nil expectedValueInterval:nil completion:^(NSError * _Nullable error) { + XCTAssertNil(error); + [toggle1Expectation fulfill]; + }]; + + [self waitForExpectations:@[ toggle1Expectation ] timeout:kTimeoutInSeconds]; + + XCTestExpectation * becameUnreachableExpectation = [self expectationWithDescription:@"Device became unreachable"]; + delegate.onNotReachable = ^{ + [becameUnreachableExpectation fulfill]; + }; + + [controller suspend]; + XCTAssertTrue(controller.suspended); + + // Test that sending a command no longer works. + XCTestExpectation * toggle2Expectation = [self expectationWithDescription:@"toggle 2"]; + [cluster toggleWithExpectedValues:nil expectedValueInterval:nil completion:^(NSError * _Nullable error) { + XCTAssertNotNil(error); + [toggle2Expectation fulfill]; + }]; + + [self waitForExpectations:@[ becameUnreachableExpectation, toggle2Expectation ] timeout:kTimeoutInSeconds]; + + XCTestExpectation * newSubscriptionExpectation = [self expectationWithDescription:@"Subscription has been set up again"]; + XCTestExpectation * newReachableExpectation = [self expectationWithDescription:@"Device became reachable again"]; + delegate.onReachable = ^{ + [newReachableExpectation fulfill]; + }; + + delegate.onReportEnd = ^{ + [newSubscriptionExpectation fulfill]; + }; + + [controller resume]; + XCTAssertFalse(controller.suspended); + + [self waitForExpectations:@[ newSubscriptionExpectation, newReachableExpectation ] timeout:kTimeoutInSeconds]; + + // Test that sending a command works again. Clear the delegate's onReportEnd + // first, so reports from the command don't trigger it. + delegate.onReportEnd = nil; + XCTestExpectation * toggle3Expectation = [self expectationWithDescription:@"toggle 3"]; + [cluster toggleWithExpectedValues:nil expectedValueInterval:nil completion:^(NSError * _Nullable error) { + XCTAssertNil(error); + [toggle3Expectation fulfill]; + }]; + + [self waitForExpectations:@[ toggle3Expectation ] timeout:kTimeoutInSeconds]; + + [controller removeDevice:device]; + // Reset our commissionee. + __auto_type * baseDevice = [MTRBaseDevice deviceWithNodeID:deviceID controller:controller]; + ResetCommissionee(baseDevice, queue, self, kTimeoutInSeconds); + + [controller shutdown]; +} + // TODO: This might want to go in a separate test file, with some shared setup // across multiple tests, maybe. Would need to factor out // startControllerWithRootKeys into a test helper. @@ -2437,11 +2584,10 @@ - (void)testSubscriptionPool // Start our helper apps. __auto_type * sortedKeys = [[deviceOnboardingPayloads allKeys] sortedArrayUsingSelector:@selector(compare:)]; for (NSNumber * deviceID in sortedKeys) { - __auto_type * appRunner = [[MTRTestServerAppRunner alloc] initWithAppName:@"all-clusters" - arguments:@[] - payload:deviceOnboardingPayloads[deviceID] - testcase:self]; - XCTAssertNotNil(appRunner); + BOOL started = [self startAppWithName:@"all-clusters" + arguments:@[] + payload:deviceOnboardingPayloads[deviceID]]; + XCTAssertTrue(started); } [self doTestSubscriptionPoolWithSize:1 deviceOnboardingPayloads:deviceOnboardingPayloads]; diff --git a/src/darwin/Framework/CHIPTests/MTRSwiftPairingTests.swift b/src/darwin/Framework/CHIPTests/MTRSwiftPairingTests.swift index 31ecb93236429c..907b9a2761673c 100644 --- a/src/darwin/Framework/CHIPTests/MTRSwiftPairingTests.swift +++ b/src/darwin/Framework/CHIPTests/MTRSwiftPairingTests.swift @@ -3,7 +3,7 @@ import XCTest // This more or less parallels the "no delegate" case in MTRPairingTests, but uses the "normal" // all-clusters-app, since it does not do any of the "interesting" VID/PID notification so far. If -// it ever starts needing to do that, we should figure out a way to use MTRTestServerAppRunner from +// it ever starts needing to do that, we should figure out a way to use MTRTestCase+ServerAppRunner from // here. struct PairingConstants { diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase+ServerAppRunner.h similarity index 64% rename from src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h rename to src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase+ServerAppRunner.h index b54b7dbd78cfce..8eb9384f67e01e 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase+ServerAppRunner.h @@ -16,21 +16,14 @@ #import -@class MTRTestCase; +#import "MTRTestCase.h" NS_ASSUME_NONNULL_BEGIN -/** - * A representation of a server application instance. - * - * Server applications are assumed to be compiled into out/debug/${APPNAME}-app, - * with the binary being out/debug/${APPNAME}-app/chip-${APPNAME}-app. - */ -@interface MTRTestServerAppRunner : NSObject +@interface MTRTestCase (ServerAppRunner) /** - * Initialize the app runner with the given app name, arguments, setup payload, and testcase - * instance. + * Start a server app with the given app name, arguments, and setup payload. * * The payload will be used to determine the discriminator and passcode * arguments the app should use, in addition to the provided arguments. @@ -43,13 +36,13 @@ NS_ASSUME_NONNULL_BEGIN * subtracting 1111 from the discriminator and adding 5542 (so as not to collide * with any existing Matter things running on 5540/5541). */ -- (instancetype)initWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase; +- (BOOL)startAppWithName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload; /** - * Same thing, but initialize as a "cross test" helper, which is not killed at - * the end of the current test (but is killed at the end of the current suite). + * Same thing, but the server will be killed at the end of the current suite, + * and is not bound to a particular test in the suite. */ -- (instancetype)initCrossTestWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase; ++ (BOOL)startAppWithName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload; /** * Get the unique index that will be used for the next initialization. This diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase+ServerAppRunner.m similarity index 64% rename from src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m rename to src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase+ServerAppRunner.m index cc64d62e71d516..82d9cf114238a3 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase+ServerAppRunner.m @@ -16,8 +16,7 @@ #import -#import "MTRTestCase.h" -#import "MTRTestServerAppRunner.h" +#import "MTRTestCase+ServerAppRunner.h" static unsigned sAppRunnerIndex = 1; @@ -29,24 +28,12 @@ static const uint16_t kBasePort = 5542 - kMinDiscriminator; #endif // HAVE_NSTASK -@implementation MTRTestServerAppRunner { - unsigned _uniqueIndex; -#if HAVE_NSTASK - NSTask * _appTask; -#endif -} +@implementation MTRTestCase (ServerAppRunner) -- (instancetype)initInternalWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase isCrossTest:(BOOL)isCrossTest +#if HAVE_NSTASK ++ (NSTask *)doStartAppWithName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload { -#if !HAVE_NSTASK - XCTFail("Unable to start server app when we do not have NSTask"); - return nil; -#else // HAVE_NSTASK - if (!(self = [super init])) { - return nil; - } - - _uniqueIndex = sAppRunnerIndex++; + __auto_type uniqueIndex = sAppRunnerIndex++; NSError * error; __auto_type * parsedPayload = [MTRSetupPayload setupPayloadWithOnboardingPayload:payload error:&error]; @@ -61,7 +48,7 @@ - (instancetype)initInternalWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase +- (BOOL)startAppWithName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload { - return [self initInternalWithAppName:name arguments:arguments payload:payload testcase:testcase isCrossTest:NO]; +#if !HAVE_NSTASK + XCTFail("Unable to start server app when we do not have NSTask"); + return NO; +#else + [self launchTask:[self.class doStartAppWithName:name arguments:arguments payload:payload]]; + return YES; +#endif // HAVE_NSTASK } -- (instancetype)initCrossTestWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase ++ (BOOL)startAppWithName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload { - return [self initInternalWithAppName:name arguments:arguments payload:payload testcase:testcase isCrossTest:YES]; +#if !HAVE_NSTASK + XCTFail("Unable to start server app when we do not have NSTask"); + return NO; +#else + [self launchTask:[self doStartAppWithName:name arguments:arguments payload:payload]]; + return YES; +#endif // HAVE_NSTASK } + (unsigned)nextUniqueIndex diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h index e3668bb90e1fca..3b404f6f05b1f4 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h @@ -37,6 +37,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSTask *)createTaskForPath:(NSString *)path; +/** + * Same thing, but not tied to a specific testcase instance. + */ ++ (NSTask *)createTaskForPath:(NSString *)path; + /** * Run a task to completion and make sure it succeeds. */ @@ -52,7 +57,7 @@ NS_ASSUME_NONNULL_BEGIN * Launch a cross-test task. The task will be automatically terminated when the testsuite * tearDown happens. */ -- (void)launchCrossTestTask:(NSTask *)task; ++ (void)launchTask:(NSTask *)task; #endif // HAVE_NSTASK /** @@ -60,6 +65,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSString *)absolutePathFor:(NSString *)matterRootRelativePath; +/** + * Same thing, but not tied to a specific testcase instance. + */ ++ (NSString *)absolutePathFor:(NSString *)matterRootRelativePath; + @end NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm index fc615cc927d06b..a418393a0fca79 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm @@ -86,6 +86,11 @@ - (void)tearDown #if HAVE_NSTASK - (NSTask *)createTaskForPath:(NSString *)path +{ + return [self.class createTaskForPath:path]; +} + ++ (NSTask *)createTaskForPath:(NSString *)path { NSTask * task = [[NSTask alloc] init]; [task setLaunchPath:[self absolutePathFor:path]]; @@ -102,7 +107,7 @@ - (void)runTask:(NSTask *)task XCTAssertEqual([task terminationStatus], 0); } -- (void)doLaunchTask:(NSTask *)task ++ (void)doLaunchTask:(NSTask *)task { NSError * launchError; [task launchAndReturnError:&launchError]; @@ -111,12 +116,12 @@ - (void)doLaunchTask:(NSTask *)task - (void)launchTask:(NSTask *)task { - [self doLaunchTask:task]; + [self.class doLaunchTask:task]; [_runningTasks addObject:task]; } -- (void)launchCrossTestTask:(NSTask *)task ++ (void)launchTask:(NSTask *)task { [self doLaunchTask:task]; @@ -125,6 +130,11 @@ - (void)launchCrossTestTask:(NSTask *)task #endif // HAVE_NSTASK - (NSString *)absolutePathFor:(NSString *)matterRootRelativePath +{ + return [self.class absolutePathFor:matterRootRelativePath]; +} + ++ (NSString *)absolutePathFor:(NSString *)matterRootRelativePath { // Start with the absolute path to our file, then remove the suffix that // comes after the path to the Matter SDK root. diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 69de347df8341c..afc1df19338b46 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -232,7 +232,7 @@ 51EF279F2A2A3EB100E33F75 /* MTRBackwardsCompatShims.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EF279E2A2A3EB100E33F75 /* MTRBackwardsCompatShims.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51F522682AE70734000C4050 /* MTRDeviceTypeMetadata.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51F522672AE70734000C4050 /* MTRDeviceTypeMetadata.mm */; }; 51F5226A2AE70761000C4050 /* MTRDeviceTypeMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F522692AE70761000C4050 /* MTRDeviceTypeMetadata.h */; }; - 51F9F9D52BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m */; }; + 51F9F9D52BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.m */; }; 51FE72352ACDB40000437032 /* MTRCommandPayloads_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 51FE72342ACDB40000437032 /* MTRCommandPayloads_Internal.h */; }; 51FE723F2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 51FE723E2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h */; }; 5A60370827EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h in Headers */ = {isa = PBXBuildFile; fileRef = 5A60370727EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h */; }; @@ -670,8 +670,8 @@ 51F522662AE7071E000C4050 /* MTRDeviceTypeMetadata-src.zapt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "MTRDeviceTypeMetadata-src.zapt"; sourceTree = ""; }; 51F522672AE70734000C4050 /* MTRDeviceTypeMetadata.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceTypeMetadata.mm; sourceTree = ""; }; 51F522692AE70761000C4050 /* MTRDeviceTypeMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceTypeMetadata.h; sourceTree = ""; }; - 51F9F9D32BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestServerAppRunner.h; sourceTree = ""; }; - 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestServerAppRunner.m; sourceTree = ""; }; + 51F9F9D32BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTRTestCase+ServerAppRunner.h"; sourceTree = ""; }; + 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTRTestCase+ServerAppRunner.m"; sourceTree = ""; }; 51FE72342ACDB40000437032 /* MTRCommandPayloads_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCommandPayloads_Internal.h; sourceTree = ""; }; 51FE723E2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCommandPayloadExtensions_Internal.h; sourceTree = ""; }; 5A60370727EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MTRClusterStateCacheContainer+XPC.h"; sourceTree = ""; }; @@ -1177,8 +1177,8 @@ 5131BF642BE2E1B000D5D6BC /* MTRTestCase.mm */, D437613F285BDC0D0051FEA2 /* MTRTestKeys.h */, 51C8E3F72825CDB600D47D00 /* MTRTestKeys.m */, - 51F9F9D32BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.h */, - 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m */, + 51F9F9D32BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.h */, + 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.m */, D4376140285BDC0D0051FEA2 /* MTRTestStorage.h */, 51D10D2D2808E2CA00E8CA3D /* MTRTestStorage.m */, D437613E285BDC0D0051FEA2 /* MTRErrorTestUtils.h */, @@ -2111,7 +2111,7 @@ 510CECA8297F72970064E0B3 /* MTROperationalCertificateIssuerTests.m in Sources */, 5A7947DE27BEC3F500434CF2 /* MTRXPCListenerSampleTests.m in Sources */, 3DFCB3292966684500332B35 /* MTRCertificateInfoTests.m in Sources */, - 51F9F9D52BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m in Sources */, + 51F9F9D52BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.m in Sources */, 517BF3F3282B62CB00A8B7DB /* MTRCertificateTests.m in Sources */, 5142E39829D377F000A206F0 /* MTROTAProviderTests.m in Sources */, 51E0FC102ACBBF230001E197 /* MTRSwiftDeviceTests.swift in Sources */, diff --git a/src/lib/support/BUILD.gn b/src/lib/support/BUILD.gn index 80b1b6d99dc328..3fd3d7e16754ca 100644 --- a/src/lib/support/BUILD.gn +++ b/src/lib/support/BUILD.gn @@ -206,6 +206,7 @@ static_library("support") { "Defer.h", "FibonacciUtils.cpp", "FibonacciUtils.h", + "FileDescriptor.h", "FixedBufferAllocator.cpp", "FixedBufferAllocator.h", "Fold.h", @@ -239,6 +240,7 @@ static_library("support") { "StringBuilder.cpp", "StringBuilder.h", "StringSplitter.h", + "TemporaryFileStream.h", "ThreadOperationalDataset.cpp", "ThreadOperationalDataset.h", "TimeUtils.cpp", diff --git a/src/lib/support/FileDescriptor.h b/src/lib/support/FileDescriptor.h new file mode 100644 index 00000000000000..8f5a00d6b315ad --- /dev/null +++ b/src/lib/support/FileDescriptor.h @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace chip { + +/// Unix file descriptor wrapper with RAII semantics. +class FileDescriptor +{ +public: + FileDescriptor() = default; + explicit FileDescriptor(int fd) : mFd(fd) {} + ~FileDescriptor() { Close(); } + + /// Disallow copy and assignment. + FileDescriptor(const FileDescriptor &) = delete; + FileDescriptor & operator=(const FileDescriptor &) = delete; + + FileDescriptor(FileDescriptor && other) noexcept : mFd(other.Release()) {} + FileDescriptor & operator=(FileDescriptor && other) noexcept + { + Close(); + mFd = other.Release(); + return *this; + } + + int Get() const { return mFd; } + + int Release() { return std::exchange(mFd, -1); } + + int Close() + { + if (mFd != -1) + { + return close(std::exchange(mFd, -1)); + } + return 0; + } + +private: + int mFd = -1; +}; + +} // namespace chip diff --git a/src/lib/support/TemporaryFileStream.h b/src/lib/support/TemporaryFileStream.h new file mode 100644 index 00000000000000..a96a23917b23ce --- /dev/null +++ b/src/lib/support/TemporaryFileStream.h @@ -0,0 +1,107 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class FileDescriptorStreamBuf : public std::streambuf +{ +public: + FileDescriptorStreamBuf() = default; + explicit FileDescriptorStreamBuf(int fd) : mFd(fd) {} + + FileDescriptorStreamBuf(FileDescriptorStreamBuf &) = delete; + FileDescriptorStreamBuf & operator=(FileDescriptorStreamBuf &) = delete; + + FileDescriptorStreamBuf(FileDescriptorStreamBuf && other) = default; + FileDescriptorStreamBuf & operator=(FileDescriptorStreamBuf && other) = default; + +protected: + int overflow(int c) override + { + if (c != EOF) + { + char z = c; + if (write(mFd, &z, 1) != 1) + { + return EOF; + } + } + return c; + } + + std::streamsize xsputn(const char * s, std::streamsize n) override { return write(mFd, s, static_cast(n)); } + +private: + int mFd = -1; +}; + +/// File stream for a temporary file compatible with std::ostream. +class TemporaryFileStream : public std::ostream +{ +public: + TemporaryFileStream() : std::ostream(&mBuf) {} + explicit TemporaryFileStream(std::string nameTemplate) : std::ostream(&mBuf) { Open(std::move(nameTemplate)); }; + + /// Disallow copy and assignment. + TemporaryFileStream(const TemporaryFileStream &) = delete; + TemporaryFileStream & operator=(const TemporaryFileStream &) = delete; + + /// Open a temporary file with a given name template. + /// + /// In order to check if the file was opened successfully, use IsOpen(). + void Open(std::string nameTemplate) + { + mFileName = std::move(nameTemplate); + mFd = FileDescriptor(mkstemp(mFileName.data())); + mBuf = FileDescriptorStreamBuf(mFd.Get()); + } + + /// Check if the file was opened successfully. + /// + /// In case of failure, the error can be retrieved using errno. + bool IsOpen() const { return mFd.Get() != -1; }; + + /// Synchronize the file's contents with the underlying storage device. + /// + /// In case of failure, the error can be retrieved using errno. + bool DataSync() { return fdatasync(mFd.Get()) == 0; } + + /// Get the name of created temporary file. + const std::string & GetFileName() const { return mFileName; } + +private: + FileDescriptor mFd; + FileDescriptorStreamBuf mBuf; + std::string mFileName; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/ESP32/ConfigurationManagerImpl.cpp b/src/platform/ESP32/ConfigurationManagerImpl.cpp index e03ec70ce8192b..31c7cb714f96c0 100644 --- a/src/platform/ESP32/ConfigurationManagerImpl.cpp +++ b/src/platform/ESP32/ConfigurationManagerImpl.cpp @@ -45,14 +45,26 @@ namespace DeviceLayer { using namespace ::chip::DeviceLayer::Internal; -// TODO: Define a Singleton instance of CHIP Group Key Store here (#1266) - ConfigurationManagerImpl & ConfigurationManagerImpl::GetDefaultInstance() { static ConfigurationManagerImpl sInstance; return sInstance; } +uint32_t ConfigurationManagerImpl::mTotalOperationalHours = 0; + +void ConfigurationManagerImpl::TotalOperationalHoursTimerCallback(TimerHandle_t timer) +{ + mTotalOperationalHours++; + + CHIP_ERROR err = ConfigurationMgrImpl().StoreTotalOperationalHours(mTotalOperationalHours); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to store total operational hours: %" CHIP_ERROR_FORMAT, err.Format()); + } +} + CHIP_ERROR ConfigurationManagerImpl::Init() { CHIP_ERROR err; @@ -161,18 +173,34 @@ CHIP_ERROR ConfigurationManagerImpl::Init() SuccessOrExit(err); } - if (!ESP32Config::ConfigValueExists(ESP32Config::kCounterKey_TotalOperationalHours)) + if (CHIP_NO_ERROR != GetTotalOperationalHours(mTotalOperationalHours)) { - err = StoreTotalOperationalHours(0); + err = StoreTotalOperationalHours(mTotalOperationalHours); SuccessOrExit(err); } + { + // Start a timer which reloads every one hour and bumps the total operational hours + TickType_t reloadPeriod = (1000 * 60 * 60) / portTICK_PERIOD_MS; + TimerHandle_t timerHandle = xTimerCreate("tOpHrs", reloadPeriod, pdPASS, nullptr, TotalOperationalHoursTimerCallback); + if (timerHandle == nullptr) + { + err = CHIP_ERROR_NO_MEMORY; + ExitNow(ChipLogError(DeviceLayer, "total operational hours Timer creation failed")); + } + + BaseType_t timerStartStatus = xTimerStart(timerHandle, 0); + if (timerStartStatus == pdFAIL) + { + err = CHIP_ERROR_INTERNAL; + ExitNow(ChipLogError(DeviceLayer, "total operational hours Timer start failed")); + } + } + // Initialize the generic implementation base class. err = Internal::GenericConfigurationManagerImpl::Init(); SuccessOrExit(err); - // TODO: Initialize the global GroupKeyStore object here (#1266) - err = CHIP_NO_ERROR; exit: diff --git a/src/platform/ESP32/ConfigurationManagerImpl.h b/src/platform/ESP32/ConfigurationManagerImpl.h index 1416df35d93b65..63b3e094763a75 100644 --- a/src/platform/ESP32/ConfigurationManagerImpl.h +++ b/src/platform/ESP32/ConfigurationManagerImpl.h @@ -98,6 +98,9 @@ class ConfigurationManagerImpl : public Internal::GenericConfigurationManagerImp // ===== Private members reserved for use by this class only. static void DoFactoryReset(intptr_t arg); + + static uint32_t mTotalOperationalHours; + static void TotalOperationalHoursTimerCallback(TimerHandle_t timer); }; /** diff --git a/src/platform/Linux/CHIPLinuxStorage.cpp b/src/platform/Linux/CHIPLinuxStorage.cpp index 9a8afb59d706fa..f1ce35b58cb38c 100644 --- a/src/platform/Linux/CHIPLinuxStorage.cpp +++ b/src/platform/Linux/CHIPLinuxStorage.cpp @@ -53,7 +53,6 @@ CHIP_ERROR ChipLinuxStorage::Init(const char * configFile) { CHIP_ERROR retval = CHIP_NO_ERROR; - ChipLogDetail(DeviceLayer, "ChipLinuxStorage::Init: Using KVS config file: %s", StringOrNullMarker(configFile)); if (mInitialized) { ChipLogError(DeviceLayer, "ChipLinuxStorage::Init: Attempt to re-initialize with KVS config file: %s", @@ -61,6 +60,8 @@ CHIP_ERROR ChipLinuxStorage::Init(const char * configFile) return CHIP_NO_ERROR; } + ChipLogDetail(DeviceLayer, "ChipLinuxStorage::Init: Using KVS config file: %s", StringOrNullMarker(configFile)); + mConfigPath.assign(configFile); retval = ChipLinuxStorageIni::Init(); diff --git a/src/platform/Linux/CHIPLinuxStorageIni.cpp b/src/platform/Linux/CHIPLinuxStorageIni.cpp index 7186fe3d477fbf..6c629d29bd9451 100644 --- a/src/platform/Linux/CHIPLinuxStorageIni.cpp +++ b/src/platform/Linux/CHIPLinuxStorageIni.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -91,34 +92,23 @@ CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile) // 3. Using rename() to overwrite the existing file CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile) { - CHIP_ERROR retval = CHIP_NO_ERROR; - std::string tmpPath = configFile + "-XXXXXX"; - - int fd = mkstemp(&tmpPath[0]); - if (fd != -1) - { - std::ofstream ofs; - ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc); - mConfigStore.generate(ofs); - close(fd); - - if (rename(tmpPath.c_str(), configFile.c_str()) == 0) - { - ChipLogDetail(DeviceLayer, "wrote settings to %s", configFile.c_str()); - } - else - { - ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno); - retval = CHIP_ERROR_WRITE_FAILED; - } - } - else - { - ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str()); - retval = CHIP_ERROR_OPEN_FAILED; - } - - return retval; + TemporaryFileStream tmpFile(configFile + "-XXXXXX"); + VerifyOrReturnError( + tmpFile.IsOpen(), CHIP_ERROR_OPEN_FAILED, + ChipLogError(DeviceLayer, "Failed to create temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno))); + + mConfigStore.generate(tmpFile); + VerifyOrReturnError( + tmpFile.DataSync(), CHIP_ERROR_WRITE_FAILED, + ChipLogError(DeviceLayer, "Failed to sync temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno))); + + int rv = rename(tmpFile.GetFileName().c_str(), configFile.c_str()); + VerifyOrReturnError(rv == 0, CHIP_ERROR_WRITE_FAILED, + ChipLogError(DeviceLayer, "Failed to rename %s to %s: %s", tmpFile.GetFileName().c_str(), + configFile.c_str(), strerror(errno))); + + ChipLogDetail(DeviceLayer, "Wrote settings to %s", configFile.c_str()); + return CHIP_NO_ERROR; } CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val) diff --git a/src/platform/NuttX/CHIPLinuxStorageIni.cpp b/src/platform/NuttX/CHIPLinuxStorageIni.cpp index e9dbe107ffad9e..686d04fc629e30 100644 --- a/src/platform/NuttX/CHIPLinuxStorageIni.cpp +++ b/src/platform/NuttX/CHIPLinuxStorageIni.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -91,38 +92,23 @@ CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile) // 3. Using rename() to overwrite the existing file CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile) { - CHIP_ERROR retval = CHIP_NO_ERROR; - std::string tmpPath = configFile + "-XXXXXX"; - - int fd = mkstemp(&tmpPath[0]); - if (fd != -1) - { - std::ofstream ofs; - - ChipLogProgress(DeviceLayer, "writing settings to file (%s)", tmpPath.c_str()); - - ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc); - mConfigStore.generate(ofs); - - close(fd); - - if (rename(tmpPath.c_str(), configFile.c_str()) == 0) - { - ChipLogProgress(DeviceLayer, "renamed tmp file to file (%s)", configFile.c_str()); - } - else - { - ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno); - retval = CHIP_ERROR_WRITE_FAILED; - } - } - else - { - ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str()); - retval = CHIP_ERROR_OPEN_FAILED; - } - - return retval; + TemporaryFileStream tmpFile(configFile + "-XXXXXX"); + VerifyOrReturnError( + tmpFile.IsOpen(), CHIP_ERROR_OPEN_FAILED, + ChipLogError(DeviceLayer, "Failed to create temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno))); + + mConfigStore.generate(tmpFile); + VerifyOrReturnError( + tmpFile.DataSync(), CHIP_ERROR_WRITE_FAILED, + ChipLogError(DeviceLayer, "Failed to sync temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno))); + + int rv = rename(tmpFile.GetFileName().c_str(), configFile.c_str()); + VerifyOrReturnError(rv == 0, CHIP_ERROR_WRITE_FAILED, + ChipLogError(DeviceLayer, "Failed to rename %s to %s: %s", tmpFile.GetFileName().c_str(), + configFile.c_str(), strerror(errno))); + + ChipLogDetail(DeviceLayer, "Wrote settings to %s", configFile.c_str()); + return CHIP_NO_ERROR; } CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val) diff --git a/src/platform/webos/CHIPWebOSStorageIni.cpp b/src/platform/webos/CHIPWebOSStorageIni.cpp index 96a3002f1afeea..cec712da8f2495 100644 --- a/src/platform/webos/CHIPWebOSStorageIni.cpp +++ b/src/platform/webos/CHIPWebOSStorageIni.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -70,38 +71,23 @@ CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile) // 3. Using rename() to overwrite the existing file CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile) { - CHIP_ERROR retval = CHIP_NO_ERROR; - std::string tmpPath = configFile + "-XXXXXX"; - - int fd = mkstemp(&tmpPath[0]); - if (fd != -1) - { - std::ofstream ofs; - - ChipLogProgress(DeviceLayer, "writing settings to file (%s)", tmpPath.c_str()); - - ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc); - mConfigStore.generate(ofs); - - close(fd); - - if (rename(tmpPath.c_str(), configFile.c_str()) == 0) - { - ChipLogProgress(DeviceLayer, "renamed tmp file to file (%s)", configFile.c_str()); - } - else - { - ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno); - retval = CHIP_ERROR_WRITE_FAILED; - } - } - else - { - ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str()); - retval = CHIP_ERROR_OPEN_FAILED; - } - - return retval; + TemporaryFileStream tmpFile(configFile + "-XXXXXX"); + VerifyOrReturnError( + tmpFile.IsOpen(), CHIP_ERROR_OPEN_FAILED, + ChipLogError(DeviceLayer, "Failed to create temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno))); + + mConfigStore.generate(tmpFile); + VerifyOrReturnError( + tmpFile.DataSync(), CHIP_ERROR_WRITE_FAILED, + ChipLogError(DeviceLayer, "Failed to sync temp file %s: %s", tmpFile.GetFileName().c_str(), strerror(errno))); + + int rv = rename(tmpFile.GetFileName().c_str(), configFile.c_str()); + VerifyOrReturnError(rv == 0, CHIP_ERROR_WRITE_FAILED, + ChipLogError(DeviceLayer, "Failed to rename %s to %s: %s", tmpFile.GetFileName().c_str(), + configFile.c_str(), strerror(errno))); + + ChipLogDetail(DeviceLayer, "Wrote settings to %s", configFile.c_str()); + return CHIP_NO_ERROR; } CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val) diff --git a/src/protocols/bdx/BdxTransferDiagnosticLog.cpp b/src/protocols/bdx/BdxTransferDiagnosticLog.cpp index 51e56fba9ac254..4804a01c578c26 100644 --- a/src/protocols/bdx/BdxTransferDiagnosticLog.cpp +++ b/src/protocols/bdx/BdxTransferDiagnosticLog.cpp @@ -18,6 +18,8 @@ #include "BdxTransferDiagnosticLog.h" +#include + namespace chip { namespace bdx { @@ -201,5 +203,23 @@ void BdxTransferDiagnosticLog::OnExchangeClosing(Messaging::ExchangeContext * ec LogErrorOnFailure(OnTransferSessionEnd(CHIP_ERROR_INTERNAL)); } +bool BdxTransferDiagnosticLog::IsForFabric(FabricIndex fabricIndex) const +{ + if (mExchangeCtx == nullptr || !mExchangeCtx->HasSessionHandle()) + { + return false; + } + + auto session = mExchangeCtx->GetSessionHandle(); + return session->GetFabricIndex() == fabricIndex; +} + +void BdxTransferDiagnosticLog::AbortTransfer() +{ + // No need to mTransfer.AbortTransfer() here, since that just tries to async + // send a StatusReport to the other side, but we are going away here. + Reset(); +} + } // namespace bdx } // namespace chip diff --git a/src/protocols/bdx/BdxTransferDiagnosticLog.h b/src/protocols/bdx/BdxTransferDiagnosticLog.h index bc7a8773cd2dff..0f62aff7f837e5 100644 --- a/src/protocols/bdx/BdxTransferDiagnosticLog.h +++ b/src/protocols/bdx/BdxTransferDiagnosticLog.h @@ -18,14 +18,17 @@ #pragma once -#include +#include #include +#include #include #include namespace chip { namespace bdx { +class BdxTransferDiagnosticLogPoolDelegate; + class BdxTransferDiagnosticLog : public Responder { public: @@ -45,6 +48,13 @@ class BdxTransferDiagnosticLog : public Responder void OnExchangeClosing(Messaging::ExchangeContext * ec) override; + /** + * Lifetime management, to allow us to abort transfers when a fabric + * identity is being shut down. + */ + bool IsForFabric(FabricIndex fabricIndex) const; + void AbortTransfer(); + protected: /** * Called when a BDX message is received over the exchange context diff --git a/src/protocols/bdx/BdxTransferDiagnosticLogPool.h b/src/protocols/bdx/BdxTransferDiagnosticLogPool.h index a2d7d3f5d11105..70f8fdfaceb55a 100644 --- a/src/protocols/bdx/BdxTransferDiagnosticLogPool.h +++ b/src/protocols/bdx/BdxTransferDiagnosticLogPool.h @@ -18,15 +18,15 @@ #pragma once +#include #include +#include #include #include namespace chip { namespace bdx { -class BdxTransferDiagnosticLog; - class BdxTransferDiagnosticLogPoolDelegate { public: @@ -50,6 +50,17 @@ class BdxTransferDiagnosticLogPool : public BdxTransferDiagnosticLogPoolDelegate void Release(BdxTransferDiagnosticLog * transfer) override { mTransferPool.ReleaseObject(transfer); } + void AbortTransfersForFabric(FabricIndex fabricIndex) + { + mTransferPool.ForEachActiveObject([fabricIndex](BdxTransferDiagnosticLog * transfer) { + if (transfer->IsForFabric(fabricIndex)) + { + transfer->AbortTransfer(); + } + return Loop::Continue; + }); + } + private: ObjectPool mTransferPool; }; diff --git a/src/protocols/bdx/BdxTransferServer.h b/src/protocols/bdx/BdxTransferServer.h index 0fac0e88b672de..0bc3cf10e124e9 100644 --- a/src/protocols/bdx/BdxTransferServer.h +++ b/src/protocols/bdx/BdxTransferServer.h @@ -20,6 +20,7 @@ #include +#include #include #include #include @@ -42,6 +43,8 @@ class BDXTransferServer : public Messaging::UnsolicitedMessageHandler void SetDelegate(BDXTransferServerDelegate * delegate) { mDelegate = delegate; } + void AbortTransfersForFabric(FabricIndex fabricIndex) { mPoolDelegate.AbortTransfersForFabric(fabricIndex); } + protected: CHIP_ERROR OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, Messaging::ExchangeDelegate *& newDelegate) override; diff --git a/src/python_testing/TC_ACL_2_11.py b/src/python_testing/TC_ACL_2_11.py index a580cc2575017e..f5ebed221b272b 100644 --- a/src/python_testing/TC_ACL_2_11.py +++ b/src/python_testing/TC_ACL_2_11.py @@ -105,11 +105,11 @@ async def test_TC_ACL_2_11(self): self.step(1) wildcard_read = (await dev_ctrl.Read(self.dut_node_id, [()])) - has_arl, has_carl = arls_populated(wildcard_read.tlvAttributes) + arl_data = arls_populated(wildcard_read.tlvAttributes) asserts.assert_true( - has_arl, "ARL attribute must contain at least one restriction to run this test. Please follow manufacturer-specific steps to add access restrictions and re-run this test") + arl_data.have_arl, "ARL attribute must contain at least one restriction to run this test. Please follow manufacturer-specific steps to add access restrictions and re-run this test") asserts.assert_true( - has_carl, "CommissioningARL attribute must contain at least one restriction to run this test. Please follow manufacturer-specific steps to add access restrictions and re-run this test") + arl_data.have_carl, "CommissioningARL attribute must contain at least one restriction to run this test. Please follow manufacturer-specific steps to add access restrictions and re-run this test") self.step(2) await self.read_single_attribute_check_success( diff --git a/src/python_testing/TestConformanceTest.py b/src/python_testing/TestConformanceTest.py index 12ecb25fef6f0c..f0ff8032eab519 100644 --- a/src/python_testing/TestConformanceTest.py +++ b/src/python_testing/TestConformanceTest.py @@ -251,33 +251,33 @@ async def test_macl_restrictions(self): self.endpoints_tlv = {0: root, 1: nim} # device with no macl - have_arl, have_carl = arls_populated(self.endpoints_tlv) - asserts.assert_false(have_arl, "Unexpected ARL found") - asserts.assert_false(have_carl, "Unexpected CommissioningARL found") + arl_data = arls_populated(self.endpoints_tlv) + asserts.assert_false(arl_data.have_arl, "Unexpected ARL found") + asserts.assert_false(arl_data.have_carl, "Unexpected CommissioningARL found") # device with unpopulated macl self.add_macl(root) - have_arl, have_carl = arls_populated(self.endpoints_tlv) - asserts.assert_false(have_arl, "Unexpected ARL found") - asserts.assert_false(have_carl, "Unexpected CommissioningARL found") + arl_data = arls_populated(self.endpoints_tlv) + asserts.assert_false(arl_data.have_arl, "Unexpected ARL found") + asserts.assert_false(arl_data.have_carl, "Unexpected CommissioningARL found") # device with populated ARL self.add_macl(root, populate_arl=True) - have_arl, have_carl = arls_populated(self.endpoints_tlv) - asserts.assert_true(have_arl, "Did not find expected ARL") - asserts.assert_false(have_carl, "Unexpected CommissioningARL found") + arl_data = arls_populated(self.endpoints_tlv) + asserts.assert_true(arl_data.have_arl, "Did not find expected ARL") + asserts.assert_false(arl_data.have_carl, "Unexpected CommissioningARL found") # device with populated commissioning ARL self.add_macl(root, populate_commissioning_arl=True) - have_arl, have_carl = arls_populated(self.endpoints_tlv) - asserts.assert_false(have_arl, "Unexpected ARL found") - asserts.assert_true(have_carl, "Did not find expected Commissioning ARL") + arl_data = arls_populated(self.endpoints_tlv) + asserts.assert_false(arl_data.have_arl, "Unexpected ARL found") + asserts.assert_true(arl_data.have_carl, "Did not find expected Commissioning ARL") # device with both self.add_macl(root, populate_arl=True, populate_commissioning_arl=True) - have_arl, have_carl = arls_populated(self.endpoints_tlv) - asserts.assert_true(have_arl, "Did not find expected ARL") - asserts.assert_true(have_carl, "Did not find expected Commissioning ARL") + arl_data = arls_populated(self.endpoints_tlv) + asserts.assert_true(arl_data.have_arl, "Did not find expected ARL") + asserts.assert_true(arl_data.have_carl, "Did not find expected Commissioning ARL") if __name__ == "__main__": diff --git a/src/python_testing/basic_composition_support.py b/src/python_testing/basic_composition_support.py index 781007cdbdf685..debf902e76a414 100644 --- a/src/python_testing/basic_composition_support.py +++ b/src/python_testing/basic_composition_support.py @@ -23,6 +23,7 @@ import pathlib import sys import typing +from dataclasses import dataclass from pprint import pformat, pprint from typing import Any, Optional @@ -33,21 +34,27 @@ from mobly import asserts -def arls_populated(tlv_data: dict[int, Any]) -> tuple[bool, bool]: +@dataclass +class ArlData: + have_arl: bool + have_carl: bool + + +def arls_populated(tlv_data: dict[int, Any]) -> ArlData: """ Returns a tuple indicating if the ARL and CommissioningARL are populated. Requires a wildcard read of the device TLV. """ # ACL is always on endpoint 0 if 0 not in tlv_data or Clusters.AccessControl.id not in tlv_data[0]: - return (False, False) + return ArlData(have_arl=False, have_carl=False) # Both attributes are mandatory for this feature, so if one doesn't exist, neither should the other. if Clusters.AccessControl.Attributes.Arl.attribute_id not in tlv_data[0][Clusters.AccessControl.id][Clusters.AccessControl.Attributes.AttributeList.attribute_id]: - return (False, False) + return ArlData(have_arl=False, have_carl=False) have_arl = tlv_data[0][Clusters.AccessControl.id][Clusters.AccessControl.Attributes.Arl.attribute_id] have_carl = tlv_data[0][Clusters.AccessControl.id][Clusters.AccessControl.Attributes.CommissioningARL.attribute_id] - return (have_arl, have_carl) + return ArlData(have_arl=have_arl, have_carl=have_carl) def MatterTlvToJson(tlv_data: dict[int, Any]) -> dict[str, Any]: @@ -187,11 +194,11 @@ async def setup_class_helper(self, allow_pase: bool = True): logging.info("Start of actual tests") logging.info("###########################################################") - have_arl, have_carl = arls_populated(self.endpoints_tlv) + arl_data = arls_populated(self.endpoints_tlv) asserts.assert_false( - have_arl, "ARL cannot be populated for this test - Please follow manufacturer-specific steps to remove the access restrictions and re-run this test") + arl_data.have_arl, "ARL cannot be populated for this test - Please follow manufacturer-specific steps to remove the access restrictions and re-run this test") asserts.assert_false( - have_carl, "CommissioningARL cannot be populated for this test - Please follow manufacturer-specific steps to remove the access restrictions and re-run this test") + arl_data.have_carl, "CommissioningARL cannot be populated for this test - Please follow manufacturer-specific steps to remove the access restrictions and re-run this test") def get_test_name(self) -> str: """Return the function name of the caller. Used to create logging entries.""" diff --git a/src/python_testing/execute_python_tests.py b/src/python_testing/execute_python_tests.py index 956e252de6c8c2..de8908ca13be25 100644 --- a/src/python_testing/execute_python_tests.py +++ b/src/python_testing/execute_python_tests.py @@ -124,7 +124,7 @@ def main(search_directory, env_file): # Run each script with the base command for script in python_files: full_command = f"{base_command} --load-from-env {env_file} --script {script}" - print(f"Running command: {full_command}") + print(f"Running command: {full_command}", flush=True) # Flush print to stdout immediately subprocess.run(full_command, shell=True, check=True)