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<chip::Percent> 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<uint16_t> mTimeout; chip::Optional<bool> mDiscoverOnce; chip::Optional<bool> mUseOnlyOnNetworkDiscovery; @@ -272,18 +272,18 @@ class PairingCommand : public CHIPCommand, TypedComplexArgument<chip::app::DataModel::List<chip::app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::Type>> 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/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. --> <configurator> <global> - <attribute side="client" code="0xFFFD" define="CLUSTER_REVISION_CLIENT" type="int16u" default="1">ClusterRevision</attribute> <attribute side="server" code="0xFFFD" define="CLUSTER_REVISION_SERVER" type="int16u" default="1">ClusterRevision</attribute> - <attribute side="client" code="0xFFFC" define="FEATURE_MAP_CLIENT" type="bitmap32" default="0">FeatureMap</attribute> <attribute side="server" code="0xFFFC" define="FEATURE_MAP_SERVER" type="bitmap32" default="0">FeatureMap</attribute> <attribute side="server" code="0xFFFB" define="ATTRIBUTE_LIST_SERVER" type="array" entryType="attrib_id">AttributeList</attribute> <attribute side="server" code="0xFFFA" define="EVENT_LIST" type="array" entryType="event_id">EventList</attribute> 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.</description> <globalAttribute side="server" code="0xFFFC" value="0" /> - <globalAttribute side="either" code="0xFFFD" value="1" /> + <globalAttribute side="either" code="0xFFFD" value="2" /> <features> <feature bit="0" code="LS" name="LatchingSwitch" summary="Switch is latching"> 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 38de3a95f5d935..e22f00dd4e6a0b 100644 --- a/src/darwin/Framework/CHIP/MTRDefines_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDefines_Internal.h @@ -132,20 +132,6 @@ typedef struct {} variable_hidden_by_mtr_hide; #endif #endif -#ifndef MTR_OPTIONAL_COLLECTION_ATTRIBUTE -#define MTR_OPTIONAL_COLLECTION_ATTRIBUTE(ATTRIBUTE, COLLECTION, DICTIONARY) \ - if ([COLLECTION count] > 0) { \ - CFDictionarySetValue((CFMutableDictionaryRef) DICTIONARY, (CFStringRef) ATTRIBUTE, (const void *) COLLECTION); \ - } -#endif - -#ifndef MTR_OPTIONAL_NUMBER_ATTRIBUTE -#define MTR_OPTIONAL_NUMBER_ATTRIBUTE(ATTRIBUTE, NUMBER, DICTIONARY) \ - if ([NUMBER intValue] != 0) { \ - CFDictionarySetValue((CFMutableDictionaryRef) DICTIONARY, (CFStringRef) ATTRIBUTE, (const void *) NUMBER); \ - } -#endif - #ifndef MTR_REMOVE_ATTRIBUTE #define MTR_REMOVE_ATTRIBUTE(ATTRIBUTE, DICTIONARY) \ if (ATTRIBUTE != nil && DICTIONARY) { \ 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<MTRDeviceDelegate>)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<MTRDeviceDelegate>)delegate @@ -1459,6 +1460,17 @@ - (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue } } +- (NSArray<NSDictionary<NSString *, id> *> *)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.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index f6dc54fbc1dd19..7eb76c4e4e17c3 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 <os/lock.h> +// 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<MTRDeviceControllerParameters *>(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,59 @@ - (BOOL)isRunning - (BOOL)isSuspended { - return _suspended; + @synchronized(self) { + return _suspended; + } } - (void)suspend { - _suspended = YES; + MTR_LOG("%@ suspending", self); - // 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. + @synchronized(self) { + _suspended = YES; + + NSMutableArray * devicesToSuspend = [NSMutableArray array]; + { + std::lock_guard lock(*self.deviceMapLock); + NSEnumerator * devices = [self.nodeIDToDeviceMap objectEnumerator]; + for (MTRDevice * device in devices) { + [devicesToSuspend addObject:device]; + } + } + + 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; + + NSMutableArray * devicesToResume = [NSMutableArray array]; + { + std::lock_guard lock(*self.deviceMapLock); + NSEnumerator * devices = [self.nodeIDToDeviceMap objectEnumerator]; + for (MTRDevice * device in devices) { + [devicesToResume addObject:device]; + } + } - // TODO: In the concrete class (which is unused so far!), iterate our - // MTRDevices, tell them to restart subscriptions. + for (MTRDevice * device in devicesToResume) { + [device controllerResumed]; + } + } } - (BOOL)matchesPendingShutdownControllerWithOperationalCertificate:(nullable MTRCertificateDERBytes)operationalCertificate andRootCertificate:(nullable MTRCertificateDERBytes)rootCertificate @@ -1718,6 +1759,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<MTRDevicePairingDelegate>)delegate { @@ -1770,6 +1814,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 <MTROperationalCertificateIssuer> @property (nonatomic, readonly) id<MTRNOCChainIssuer> 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<MTRDeviceControllerStorageDelegate>)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..4f19035f27da85 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 <setup_payload/ManualSetupPayloadGenerator.h> @@ -80,8 +80,11 @@ #include <atomic> #include <dns_sd.h> +#include <optional> #include <string> +#import <os/lock.h> + 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<MTROTAProviderDelegate> otaProviderDelegate; @@ -114,19 +116,26 @@ @interface MTRDeviceController_Concrete () @end @implementation MTRDeviceController_Concrete { - // queue used to serialize all work performed by the MTRDeviceController std::atomic<chip::FabricIndex> _storedFabricIndex; std::atomic<std::optional<uint64_t>> _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<MTRDeviceControllerParameters *>(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<unsigned long>(_keepRunningAssertionCounter)); + } +} + +- (void)removeRunAssertion; +{ + std::lock_guard lock(_assertionLock); + + if (_keepRunningAssertionCounter > 0) { + --_keepRunningAssertionCounter; + MTR_LOG("%@ Removing keep running assertion, total %lu", self, static_cast<unsigned long>(_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<unsigned long>(_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. @@ -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<NSNumber *, NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *> * _Nonnull clusterDataByNode) { - MTR_LOG("%@ Loaded attribute values for %lu nodes from storage for controller uuid %@", self, static_cast<unsigned long>(clusterDataByNode.count), self->_uniqueIdentifier); + MTR_LOG("%@ Loaded attribute values for %lu nodes from storage for controller uuid %@", self, static_cast<unsigned long>(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<chip::EndpointId>(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<chip::EndpointId>(endpoint.endpointID.unsignedLongLongValue), - self->_uniqueIdentifier); + MTR_LOG("%@ Removed server endpoint %u from controller %@", self, static_cast<chip::EndpointId>(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<NSNumber *, MTRDevice *> * 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.mm b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm index c58514ed2496ec..5b6dcf93f586f8 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm @@ -34,7 +34,6 @@ @interface MTRDeviceController_XPC () -@property (nonatomic, readwrite, retain) NSUUID * uniqueIdentifier; @property (nonnull, atomic, readwrite, retain) MTRXPCDeviceControllerParameters * xpcParameters; @property (atomic, readwrite, assign) NSTimeInterval xpcRetryTimeInterval; @property (atomic, readwrite, assign) BOOL xpcConnectedOrConnecting; @@ -45,55 +44,62 @@ @interface MTRDeviceController_XPC () @implementation MTRDeviceController_XPC -@synthesize uniqueIdentifier = _uniqueIdentifier; - -- (NSXPCInterface *)_interfaceForServerProtocol ++ (NSMutableSet *)_allowedClasses { - NSXPCInterface * interface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCServerProtocol)]; - - NSSet * allowedClasses = [NSSet setWithArray:@[ + static NSArray * sBaseAllowedClasses = @[ [NSString class], [NSNumber class], [NSData class], [NSArray class], [NSDictionary class], [NSError class], - [MTRCommandPath class], - [MTRAttributePath class], [NSDate class], + ]; + + return [NSMutableSet setWithArray:sBaseAllowedClasses]; +} + +- (NSXPCInterface *)_interfaceForServerProtocol +{ + NSXPCInterface * interface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCServerProtocol)]; + + 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], - [NSDate 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; } @@ -193,15 +199,8 @@ - (BOOL)_setupXPCConnection }] deviceController:self.uniqueIdentifier registerNodeID:nodeID]; } - __block BOOL barrierComplete = NO; - - [self.xpcConnection scheduleSendBarrierBlock:^{ - barrierComplete = YES; - MTR_LOG("%@: Barrier complete: %d", self, barrierComplete); - }]; - - MTR_LOG("%@ Done existing NodeID Registration, barrierComplete: %d", self, barrierComplete); - self.xpcConnectedOrConnecting = barrierComplete; + MTR_LOG("%@ Done existing NodeID Registration", self); + self.xpcConnectedOrConnecting = YES; } else { MTR_LOG_ERROR("%@ Failed to set up XPC Connection", self); self.xpcConnectedOrConnecting = NO; @@ -237,10 +236,9 @@ - (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParamete return nil; } + self.uniqueIdentifier = UUID; self.xpcParameters = xpcParameters; self.chipWorkQueue = dispatch_queue_create("MTRDeviceController_XPC_queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); - self.nodeIDToDeviceMap = [NSMapTable strongToWeakObjectsMapTable]; - self.uniqueIdentifier = UUID; if (![self _setupXPCConnection]) { return nil; @@ -288,6 +286,13 @@ - (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(N MTRDevice * deviceToReturn = [[MTRDevice_XPC alloc] initWithNodeID:nodeID controller:self]; [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; } diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm index f554d14f9e7215..6e3ca4bf32b299 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm +++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm @@ -719,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, @@ -749,10 +758,10 @@ - (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]]; } } @@ -1240,6 +1249,11 @@ - (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 @@ -1505,6 +1519,7 @@ - (void)unitTestSetMostRecentReportTimes:(NSMutableArray<NSDate *> *)mostRecentR std::lock_guard lock(_descriptionLock); _mostRecentReportTimeForDescription = [mostRecentReportTimes lastObject]; } + std::lock_guard lock(_lock); [self _notifyDelegateOfPrivateInternalPropertiesChanges]; } #endif @@ -3094,6 +3109,12 @@ - (void)_performScheduledExpirationCheck - (NSDictionary<NSString *, id> *)_attributeValueDictionaryForAttributePath:(MTRAttributePath *)attributePath { std::lock_guard lock(_lock); + return [self _lockedAttributeValueDictionaryForAttributePath:attributePath]; +} + +- (NSDictionary<NSString *, id> *)_lockedAttributeValueDictionaryForAttributePath:(MTRAttributePath *)attributePath +{ + os_unfair_lock_assert_owner(&self->_lock); // First check expected value cache NSArray * expectedValue = _expectedValueCache[attributePath]; @@ -3480,6 +3501,31 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt return attributesToReport; } +- (NSArray<NSDictionary<NSString *, id> *> *)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 { @@ -3977,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 baa34399bdb4d4..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<MTRDeviceDelegate> delegate))block; #endif +// Used to generate attribute report that contains all known attributes, taking into consideration expected values +- (NSArray<NSDictionary<NSString *, id> *> *)getAllAttributesReport; + +// Hooks for controller suspend/resume. +- (void)controllerSuspended; +- (void)controllerResumed; + @end #pragma mark - MTRDevice internal state monitoring 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/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 <Matter/Matter.h> +#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<NSDictionary<NSString *, id> *> * 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<NSString *, id> * newResponseValueDict in allAttributesReport) { + MTRAttributePath * newPath = newResponseValueDict[MTRAttributePathKey]; + NSDictionary<NSString *, id> * newDataValueDict = newResponseValueDict[MTRDataKey]; + NSNumber * newValue = newDataValueDict[MTRValueKey]; + XCTAssertNotNil(newValue); + + for (NSDictionary<NSString *, id> * originalResponseValueDict in attributeReport) { + MTRAttributePath * originalPath = originalResponseValueDict[MTRAttributePathKey]; + // Find same attribute path and compare value + if ([newPath isEqual:originalPath]) { + NSDictionary<NSString *, id> * 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 <Matter/Matter.h> #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 <Foundation/Foundation.h> -@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<NSString *> *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase; +- (BOOL)startAppWithName:(NSString *)name arguments:(NSArray<NSString *> *)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<NSString *> *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase; ++ (BOOL)startAppWithName:(NSString *)name arguments:(NSArray<NSString *> *)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 <Matter/Matter.h> -#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<NSString *> *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase isCrossTest:(BOOL)isCrossTest +#if HAVE_NSTASK ++ (NSTask *)doStartAppWithName:(NSString *)name arguments:(NSArray<NSString *> *)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<NSSt NSNumber * passcode = parsedPayload.setupPasscode; __auto_type * executable = [NSString stringWithFormat:@"out/debug/%@-app/chip-%@-app", name, name]; - _appTask = [testcase createTaskForPath:executable]; + __auto_type * appTask = [self createTaskForPath:executable]; __auto_type * forcedArguments = @[ // Make sure we only advertise on the local interface. @@ -74,44 +61,50 @@ - (instancetype)initInternalWithAppName:(NSString *)name arguments:(NSArray<NSSt @"--passcode", [NSString stringWithFormat:@"%llu", passcode.unsignedLongLongValue], @"--KVS", - [NSString stringWithFormat:@"/tmp/chip-%@-kvs%u", name, _uniqueIndex], + [NSString stringWithFormat:@"/tmp/chip-%@-kvs%u", name, uniqueIndex], @"--product-id", [NSString stringWithFormat:@"%u", parsedPayload.productID.unsignedShortValue], ]; __auto_type * allArguments = [forcedArguments arrayByAddingObjectsFromArray:arguments]; - [_appTask setArguments:allArguments]; + [appTask setArguments:allArguments]; - NSString * outFile = [NSString stringWithFormat:@"/tmp/darwin/framework-tests/%@-app-%u.log", name, _uniqueIndex]; - NSString * errorFile = [NSString stringWithFormat:@"/tmp/darwin/framework-tests/%@-app-err-%u.log", name, _uniqueIndex]; + NSString * outFile = [NSString stringWithFormat:@"/tmp/darwin/framework-tests/%@-app-%u.log", name, uniqueIndex]; + NSString * errorFile = [NSString stringWithFormat:@"/tmp/darwin/framework-tests/%@-app-err-%u.log", name, uniqueIndex]; // Make sure the files exist. [[NSFileManager defaultManager] createFileAtPath:outFile contents:nil attributes:nil]; [[NSFileManager defaultManager] createFileAtPath:errorFile contents:nil attributes:nil]; - _appTask.standardOutput = [NSFileHandle fileHandleForWritingAtPath:outFile]; - _appTask.standardError = [NSFileHandle fileHandleForWritingAtPath:errorFile]; + appTask.standardOutput = [NSFileHandle fileHandleForWritingAtPath:outFile]; + appTask.standardError = [NSFileHandle fileHandleForWritingAtPath:errorFile]; - if (isCrossTest) { - [testcase launchCrossTestTask:_appTask]; - } else { - [testcase launchTask:_appTask]; - } + NSLog(@"Started chip-%@-app (%@) with arguments %@ stdout=%@ and stderr=%@", name, appTask, allArguments, outFile, errorFile); - NSLog(@"Started chip-%@-app (%@) with arguments %@ stdout=%@ and stderr=%@", name, _appTask, allArguments, outFile, errorFile); - - return self; -#endif // HAVE_NSTASK + return appTask; } +#endif // HAVE_NSTASK -- (instancetype)initWithAppName:(NSString *)name arguments:(NSArray<NSString *> *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase +- (BOOL)startAppWithName:(NSString *)name arguments:(NSArray<NSString *> *)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<NSString *> *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase ++ (BOOL)startAppWithName:(NSString *)name arguments:(NSArray<NSString *> *)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 = "<group>"; }; 51F522672AE70734000C4050 /* MTRDeviceTypeMetadata.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceTypeMetadata.mm; sourceTree = "<group>"; }; 51F522692AE70761000C4050 /* MTRDeviceTypeMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceTypeMetadata.h; sourceTree = "<group>"; }; - 51F9F9D32BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestServerAppRunner.h; sourceTree = "<group>"; }; - 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestServerAppRunner.m; sourceTree = "<group>"; }; + 51F9F9D32BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTRTestCase+ServerAppRunner.h"; sourceTree = "<group>"; }; + 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestCase+ServerAppRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTRTestCase+ServerAppRunner.m"; sourceTree = "<group>"; }; 51FE72342ACDB40000437032 /* MTRCommandPayloads_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCommandPayloads_Internal.h; sourceTree = "<group>"; }; 51FE723E2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCommandPayloadExtensions_Internal.h; sourceTree = "<group>"; }; 5A60370727EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MTRClusterStateCacheContainer+XPC.h"; sourceTree = "<group>"; }; @@ -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/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<ESP32Config>::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/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 <protocols/bdx/BdxTransferDiagnosticLogPool.h> + 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 <protocols/bdx/BdxTransferDiagnosticLogPool.h> +#include <lib/core/DataModelTypes.h> #include <protocols/bdx/BdxTransferProxyDiagnosticLog.h> +#include <protocols/bdx/BdxTransferServerDelegate.h> #include <protocols/bdx/TransferFacilitator.h> #include <system/SystemLayer.h> 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 <lib/core/DataModelTypes.h> #include <lib/support/Pool.h> +#include <protocols/bdx/BdxTransferDiagnosticLog.h> #include <protocols/bdx/BdxTransferServerDelegate.h> #include <system/SystemLayer.h> 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<BdxTransferDiagnosticLog, N> 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 <protocols/bdx/BdxTransferDiagnosticLogPool.h> +#include <lib/core/DataModelTypes.h> #include <messaging/ExchangeDelegate.h> #include <messaging/ExchangeMgr.h> #include <protocols/bdx/BdxTransferDiagnosticLog.h> @@ -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)