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 4319327b7dd87d..e7f2052c8255c0 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 @@ -1939,6 +1939,146 @@ server cluster EthernetNetworkDiagnostics = 55 { command ResetCounts(): DefaultSuccess = 0; } +/** Accurate time is required for a number of reasons, including scheduling, display and validating security materials. */ +server cluster TimeSynchronization = 56 { + enum GranularityEnum : ENUM8 { + kNoTimeGranularity = 0; + kMinutesGranularity = 1; + kSecondsGranularity = 2; + kMillisecondsGranularity = 3; + kMicrosecondsGranularity = 4; + } + + enum StatusCode : ENUM8 { + kTimeNotAccepted = 2; + } + + enum TimeSourceEnum : ENUM8 { + kNone = 0; + kUnknown = 1; + kAdmin = 2; + kNodeTimeCluster = 3; + kNonMatterSNTP = 4; + kNonMatterNTP = 5; + kMatterSNTP = 6; + kMatterNTP = 7; + kMixedNTP = 8; + kNonMatterSNTPNTS = 9; + kNonMatterNTPNTS = 10; + kMatterSNTPNTS = 11; + kMatterNTPNTS = 12; + kMixedNTPNTS = 13; + kCloudSource = 14; + kPTP = 15; + kGNSS = 16; + } + + enum TimeZoneDatabaseEnum : ENUM8 { + kFull = 0; + kPartial = 1; + kNone = 2; + } + + bitmap Feature : BITMAP32 { + kTimeZone = 0x1; + kNTPClient = 0x2; + kNTPServer = 0x4; + kTimeSyncClient = 0x8; + } + + struct DSTOffsetStruct { + int32s offset = 0; + epoch_us validStarting = 1; + nullable epoch_us validUntil = 2; + } + + struct FabricScopedTrustedTimeSourceStruct { + node_id nodeID = 0; + endpoint_no endpoint = 1; + } + + struct TimeZoneStruct { + int32s offset = 0; + epoch_us validAt = 1; + optional char_string<64> name = 2; + } + + struct TrustedTimeSourceStruct { + fabric_idx fabricIndex = 0; + node_id nodeID = 1; + endpoint_no endpoint = 2; + } + + info event DSTTableEmpty = 0 { + } + + info event DSTStatus = 1 { + boolean DSTOffsetActive = 0; + } + + info event TimeZoneStatus = 2 { + INT32S offset = 0; + optional CHAR_STRING name = 1; + } + + info event TimeFailure = 3 { + } + + info event MissingTrustedTimeSource = 4 { + } + + readonly attribute nullable epoch_us UTCTime = 0; + readonly attribute GranularityEnum granularity = 1; + readonly attribute TimeSourceEnum timeSource = 2; + readonly attribute nullable TrustedTimeSourceStruct trustedTimeSource = 3; + readonly attribute nullable char_string<128> defaultNTP = 4; + readonly attribute TimeZoneStruct timeZone[] = 5; + readonly attribute DSTOffsetStruct DSTOffset[] = 6; + readonly attribute nullable epoch_us localTime = 7; + readonly attribute TimeZoneDatabaseEnum timeZoneDatabase = 8; + readonly attribute int8u timeZoneListMaxSize = 10; + readonly attribute int8u DSTOffsetListMaxSize = 11; + readonly attribute boolean supportsDNSResolve = 12; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct SetUTCTimeRequest { + epoch_us UTCTime = 0; + GranularityEnum granularity = 1; + optional TimeSourceEnum timeSource = 2; + } + + request struct SetTrustedTimeSourceRequest { + nullable FabricScopedTrustedTimeSourceStruct trustedTimeSource = 0; + } + + request struct SetTimeZoneRequest { + TimeZoneStruct timeZone[] = 0; + } + + request struct SetDSTOffsetRequest { + DSTOffsetStruct DSTOffset[] = 0; + } + + request struct SetDefaultNTPRequest { + nullable CHAR_STRING<128> defaultNTP = 0; + } + + response struct SetTimeZoneResponse = 3 { + boolean DSTOffsetRequired = 0; + } + + command access(invoke: administer) SetUTCTime(SetUTCTimeRequest): DefaultSuccess = 0; + fabric command access(invoke: administer) SetTrustedTimeSource(SetTrustedTimeSourceRequest): DefaultSuccess = 1; + command access(invoke: manage) SetTimeZone(SetTimeZoneRequest): SetTimeZoneResponse = 2; + command access(invoke: manage) SetDSTOffset(SetDSTOffsetRequest): DefaultSuccess = 4; + command access(invoke: administer) SetDefaultNTP(SetDefaultNTPRequest): DefaultSuccess = 5; +} + /** This cluster exposes interactions with a switch device, for the purpose of using those interactions by other devices. 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. */ @@ -5929,6 +6069,32 @@ endpoint 0 { ram attribute clusterRevision default = 1; } + server cluster TimeSynchronization { + emits event DSTTableEmpty; + emits event DSTStatus; + emits event TimeZoneStatus; + emits event TimeFailure; + emits event MissingTrustedTimeSource; + callback attribute UTCTime; + callback attribute granularity default = 0x00; + ram attribute timeSource default = 0x00; + callback attribute trustedTimeSource; + callback attribute defaultNTP; + callback attribute timeZone default = 1; + callback attribute DSTOffset; + callback attribute localTime default = 1; + ram attribute timeZoneDatabase default = 0; + callback attribute timeZoneListMaxSize default = 3; + callback attribute DSTOffsetListMaxSize; + ram attribute supportsDNSResolve default = false; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0x0B; + ram attribute clusterRevision default = 2; + } + server cluster AdministratorCommissioning { callback attribute windowStatus default = 0; callback attribute adminFabricIndex default = 1; diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 0f19686c8795de..fde21d164e306b 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -5434,6 +5434,451 @@ } ] }, + { + "name": "Time Synchronization", + "code": 56, + "mfgCode": null, + "define": "TIME_SYNCHRONIZATION_CLUSTER", + "side": "client", + "enabled": 0, + "commands": [ + { + "name": "SetUTCTime", + "code": 0, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 1 + }, + { + "name": "SetTrustedTimeSource", + "code": 1, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 1 + }, + { + "name": "SetTimeZone", + "code": 2, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "SetDSTOffset", + "code": 4, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "SetDefaultNTP", + "code": 5, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + } + ], + "attributes": [ + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "client", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "client", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Time Synchronization", + "code": 56, + "mfgCode": null, + "define": "TIME_SYNCHRONIZATION_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "SetTimeZoneResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "incoming": 0, + "outgoing": 1 + } + ], + "attributes": [ + { + "name": "UTCTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "epoch_us", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Granularity", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "GranularityEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TimeSource", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "TimeSourceEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TrustedTimeSource", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "TrustedTimeSourceStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DefaultNTP", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TimeZone", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DSTOffset", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LocalTime", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "epoch_us", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TimeZoneDatabase", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "TimeZoneDatabaseEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NTPServerAvailable", + "code": 9, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 0, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TimeZoneListMaxSize", + "code": 10, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "3", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DSTOffsetListMaxSize", + "code": 11, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportsDNSResolve", + "code": 12, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "false", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0B", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "DSTTableEmpty", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "DSTStatus", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "TimeZoneStatus", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "TimeFailure", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "MissingTrustedTimeSource", + "code": 4, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, { "name": "Switch", "code": 59, diff --git a/examples/all-clusters-app/esp32/main/CMakeLists.txt b/examples/all-clusters-app/esp32/main/CMakeLists.txt index e2311a75f51f76..86249f3d81df0a 100644 --- a/examples/all-clusters-app/esp32/main/CMakeLists.txt +++ b/examples/all-clusters-app/esp32/main/CMakeLists.txt @@ -90,6 +90,7 @@ set(SRC_DIRS_LIST "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/pump-configuration-and-control-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/power-source-configuration-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/power-source-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/time-synchronization-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/all-clusters-app/all-clusters-common/src" ) diff --git a/examples/darwin-framework-tool/templates/tests/ciTests.json b/examples/darwin-framework-tool/templates/tests/ciTests.json index 4d33395f379753..f8f9dca68c2060 100644 --- a/examples/darwin-framework-tool/templates/tests/ciTests.json +++ b/examples/darwin-framework-tool/templates/tests/ciTests.json @@ -35,6 +35,7 @@ "TestIcdManagementCluster", "Disabled due to using Time Synchronization (TimeSynchronization) cluster, which is provisional on Darwin for now:", "Test_TC_TIMESYNC_1_1", + "TestTimeSynchronization", "Disabled due to using provisional Ceramic Filter Monitoring (CeramicFilterMonitoring) cluster:", "Test_TC_CFREMON_1_1", "Test_TC_CFREMON_2_1", diff --git a/examples/light-switch-app/esp32/main/CMakeLists.txt b/examples/light-switch-app/esp32/main/CMakeLists.txt index 540b8d5794d4a8..37a97c711e6cf6 100644 --- a/examples/light-switch-app/esp32/main/CMakeLists.txt +++ b/examples/light-switch-app/esp32/main/CMakeLists.txt @@ -57,6 +57,7 @@ idf_component_register(PRIV_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/ota-requestor" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/groups-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/group-key-mgmt-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/time-synchronization-server" PRIV_REQUIRES chip QRCode bt app_update driver nvs_flash spi_flash) get_filename_component(CHIP_ROOT ${CMAKE_SOURCE_DIR}/third_party/connectedhomeip REALPATH) 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 6afb285e34c9b9..1b9ed214c9fc70 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 @@ -1495,6 +1495,146 @@ server cluster EthernetNetworkDiagnostics = 55 { command ResetCounts(): DefaultSuccess = 0; } +/** Accurate time is required for a number of reasons, including scheduling, display and validating security materials. */ +server cluster TimeSynchronization = 56 { + enum GranularityEnum : ENUM8 { + kNoTimeGranularity = 0; + kMinutesGranularity = 1; + kSecondsGranularity = 2; + kMillisecondsGranularity = 3; + kMicrosecondsGranularity = 4; + } + + enum StatusCode : ENUM8 { + kTimeNotAccepted = 2; + } + + enum TimeSourceEnum : ENUM8 { + kNone = 0; + kUnknown = 1; + kAdmin = 2; + kNodeTimeCluster = 3; + kNonMatterSNTP = 4; + kNonMatterNTP = 5; + kMatterSNTP = 6; + kMatterNTP = 7; + kMixedNTP = 8; + kNonMatterSNTPNTS = 9; + kNonMatterNTPNTS = 10; + kMatterSNTPNTS = 11; + kMatterNTPNTS = 12; + kMixedNTPNTS = 13; + kCloudSource = 14; + kPTP = 15; + kGNSS = 16; + } + + enum TimeZoneDatabaseEnum : ENUM8 { + kFull = 0; + kPartial = 1; + kNone = 2; + } + + bitmap Feature : BITMAP32 { + kTimeZone = 0x1; + kNTPClient = 0x2; + kNTPServer = 0x4; + kTimeSyncClient = 0x8; + } + + struct DSTOffsetStruct { + int32s offset = 0; + epoch_us validStarting = 1; + nullable epoch_us validUntil = 2; + } + + struct FabricScopedTrustedTimeSourceStruct { + node_id nodeID = 0; + endpoint_no endpoint = 1; + } + + struct TimeZoneStruct { + int32s offset = 0; + epoch_us validAt = 1; + optional char_string<64> name = 2; + } + + struct TrustedTimeSourceStruct { + fabric_idx fabricIndex = 0; + node_id nodeID = 1; + endpoint_no endpoint = 2; + } + + info event DSTTableEmpty = 0 { + } + + info event DSTStatus = 1 { + boolean DSTOffsetActive = 0; + } + + info event TimeZoneStatus = 2 { + INT32S offset = 0; + optional CHAR_STRING name = 1; + } + + info event TimeFailure = 3 { + } + + info event MissingTrustedTimeSource = 4 { + } + + readonly attribute nullable epoch_us UTCTime = 0; + readonly attribute GranularityEnum granularity = 1; + readonly attribute TimeSourceEnum timeSource = 2; + readonly attribute nullable TrustedTimeSourceStruct trustedTimeSource = 3; + readonly attribute nullable char_string<128> defaultNTP = 4; + readonly attribute TimeZoneStruct timeZone[] = 5; + readonly attribute DSTOffsetStruct DSTOffset[] = 6; + readonly attribute nullable epoch_us localTime = 7; + readonly attribute TimeZoneDatabaseEnum timeZoneDatabase = 8; + readonly attribute int8u timeZoneListMaxSize = 10; + readonly attribute int8u DSTOffsetListMaxSize = 11; + readonly attribute boolean supportsDNSResolve = 12; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct SetUTCTimeRequest { + epoch_us UTCTime = 0; + GranularityEnum granularity = 1; + optional TimeSourceEnum timeSource = 2; + } + + request struct SetTrustedTimeSourceRequest { + nullable FabricScopedTrustedTimeSourceStruct trustedTimeSource = 0; + } + + request struct SetTimeZoneRequest { + TimeZoneStruct timeZone[] = 0; + } + + request struct SetDSTOffsetRequest { + DSTOffsetStruct DSTOffset[] = 0; + } + + request struct SetDefaultNTPRequest { + nullable CHAR_STRING<128> defaultNTP = 0; + } + + response struct SetTimeZoneResponse = 3 { + boolean DSTOffsetRequired = 0; + } + + command access(invoke: administer) SetUTCTime(SetUTCTimeRequest): DefaultSuccess = 0; + fabric command access(invoke: administer) SetTrustedTimeSource(SetTrustedTimeSourceRequest): DefaultSuccess = 1; + command access(invoke: manage) SetTimeZone(SetTimeZoneRequest): SetTimeZoneResponse = 2; + command access(invoke: manage) SetDSTOffset(SetDSTOffsetRequest): DefaultSuccess = 4; + command access(invoke: administer) SetDefaultNTP(SetDefaultNTPRequest): DefaultSuccess = 5; +} + /** This cluster exposes interactions with a switch device, for the purpose of using those interactions by other devices. 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. */ @@ -2364,6 +2504,32 @@ endpoint 0 { ram attribute clusterRevision default = 1; } + server cluster TimeSynchronization { + emits event DSTTableEmpty; + emits event DSTStatus; + emits event TimeZoneStatus; + emits event TimeFailure; + emits event MissingTrustedTimeSource; + callback attribute UTCTime; + callback attribute granularity default = 0x00; + ram attribute timeSource default = 0x00; + callback attribute trustedTimeSource; + callback attribute defaultNTP; + callback attribute timeZone default = 1; + callback attribute DSTOffset; + callback attribute localTime; + ram attribute timeZoneDatabase default = 0; + callback attribute timeZoneListMaxSize default = 3; + callback attribute DSTOffsetListMaxSize; + ram attribute supportsDNSResolve default = false; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0x0B; + ram attribute clusterRevision default = 1; + } + server cluster AdministratorCommissioning { callback attribute windowStatus default = 0; callback attribute adminFabricIndex default = 1; diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.zap b/examples/light-switch-app/light-switch-common/light-switch-app.zap index 109cb28b638583..cbe503464f3c45 100644 --- a/examples/light-switch-app/light-switch-common/light-switch-app.zap +++ b/examples/light-switch-app/light-switch-common/light-switch-app.zap @@ -4610,6 +4610,451 @@ } ] }, + { + "name": "Time Synchronization", + "code": 56, + "mfgCode": null, + "define": "TIME_SYNCHRONIZATION_CLUSTER", + "side": "client", + "enabled": 0, + "commands": [ + { + "name": "SetUTCTime", + "code": 0, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 1 + }, + { + "name": "SetTrustedTimeSource", + "code": 1, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 1 + }, + { + "name": "SetTimeZone", + "code": 2, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "SetDSTOffset", + "code": 4, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "SetDefaultNTP", + "code": 5, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + } + ], + "attributes": [ + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "client", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "client", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Time Synchronization", + "code": 56, + "mfgCode": null, + "define": "TIME_SYNCHRONIZATION_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "SetTimeZoneResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "incoming": 0, + "outgoing": 1 + } + ], + "attributes": [ + { + "name": "UTCTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "epoch_us", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Granularity", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "GranularityEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TimeSource", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "TimeSourceEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TrustedTimeSource", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "TrustedTimeSourceStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DefaultNTP", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TimeZone", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DSTOffset", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LocalTime", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "epoch_us", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TimeZoneDatabase", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "TimeZoneDatabaseEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NTPServerAvailable", + "code": 9, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 0, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TimeZoneListMaxSize", + "code": 10, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "3", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DSTOffsetListMaxSize", + "code": 11, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportsDNSResolve", + "code": 12, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "false", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0B", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "DSTTableEmpty", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "DSTStatus", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "TimeZoneStatus", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "TimeFailure", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "MissingTrustedTimeSource", + "code": 4, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, { "name": "Switch", "code": 59, @@ -9334,5 +9779,6 @@ "endpointVersion": 1, "deviceIdentifier": 15 } - ] + ], + "log": [] } \ No newline at end of file diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 7d90aab90698eb..32a6157445c687 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -250,6 +250,12 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/PendingNotificationMap.cpp", "${_app_root}/clusters/${cluster}/PendingNotificationMap.h", ] + } else if (cluster == "time-synchronization-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/DefaultTimeSyncDelegate.cpp", + "${_app_root}/clusters/${cluster}/TimeSyncDataProvider.cpp", + ] } else if (cluster == "scenes-server") { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp", diff --git a/src/app/clusters/time-synchronization-server/DefaultTimeSyncDelegate.cpp b/src/app/clusters/time-synchronization-server/DefaultTimeSyncDelegate.cpp new file mode 100644 index 00000000000000..0d115593e1f9d9 --- /dev/null +++ b/src/app/clusters/time-synchronization-server/DefaultTimeSyncDelegate.cpp @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DefaultTimeSyncDelegate.h" +#include "inet/IPAddress.h" + +using chip::TimeSyncDataProvider; +using namespace chip::app::Clusters::TimeSynchronization; + +void DefaultTimeSyncDelegate::TimeZoneListChanged(const Span timeZoneList) +{ + // placeholder implementation +} + +bool DefaultTimeSyncDelegate::HandleUpdateDSTOffset(chip::CharSpan name) +{ + // placeholder implementation + return false; +} + +bool DefaultTimeSyncDelegate::IsNTPAddressValid(chip::CharSpan ntp) +{ + // placeholder implementation + chip::Inet::IPAddress addr; + return chip::Inet::IPAddress::FromString(ntp.data(), ntp.size(), addr) && addr.IsIPv6(); +} + +bool DefaultTimeSyncDelegate::IsNTPAddressDomain(chip::CharSpan ntp) +{ + // placeholder implementation + return false; +} diff --git a/src/app/clusters/time-synchronization-server/DefaultTimeSyncDelegate.h b/src/app/clusters/time-synchronization-server/DefaultTimeSyncDelegate.h new file mode 100644 index 00000000000000..bac1f14fb28e93 --- /dev/null +++ b/src/app/clusters/time-synchronization-server/DefaultTimeSyncDelegate.h @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "time-synchronization-delegate.h" + +namespace chip { +namespace app { +namespace Clusters { +namespace TimeSynchronization { +class DefaultTimeSyncDelegate : public Delegate +{ + +public: + DefaultTimeSyncDelegate() : Delegate(){}; + void TimeZoneListChanged(const Span timeZoneList) override; + bool HandleUpdateDSTOffset(CharSpan name) override; + bool IsNTPAddressValid(CharSpan ntp) override; + bool IsNTPAddressDomain(CharSpan ntp) override; +}; + +} // namespace TimeSynchronization +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/time-synchronization-server/TimeSyncDataProvider.cpp b/src/app/clusters/time-synchronization-server/TimeSyncDataProvider.cpp new file mode 100644 index 00000000000000..93021a0fae6733 --- /dev/null +++ b/src/app/clusters/time-synchronization-server/TimeSyncDataProvider.cpp @@ -0,0 +1,241 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TimeSyncDataProvider.h" +#include + +#include +namespace chip { + +constexpr size_t kTrustedTimeSourceMaxSerializedSize = + TLV::EstimateStructOverhead(sizeof(FabricIndex), sizeof(NodeId), sizeof(EndpointId)); +constexpr size_t kTimeZoneMaxSerializedSize = + TLV::EstimateStructOverhead(sizeof(int32_t), sizeof(uint64_t), TimeSyncDataProvider::kTimeZoneNameLength); +constexpr size_t kDSTOffsetMaxSerializedSize = TLV::EstimateStructOverhead(sizeof(int32_t), sizeof(uint64_t), sizeof(uint64_t)); + +// Multiply the serialized size by the maximum number of list size and add 2 bytes for the array start and end. +constexpr size_t kTimeZoneListMaxSerializedSize = + kTimeZoneMaxSerializedSize * CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE + TLV::EstimateStructOverhead(); +constexpr size_t kDSTOffsetListMaxSerializedSize = + kDSTOffsetMaxSerializedSize * CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE + TLV::EstimateStructOverhead(); + +CHIP_ERROR TimeSyncDataProvider::StoreTrustedTimeSource(const TrustedTimeSource & timeSource) +{ + uint8_t buffer[kTrustedTimeSourceMaxSerializedSize]; + TLV::TLVWriter writer; + + writer.Init(buffer); + ReturnErrorOnFailure(timeSource.Encode(writer, TLV::AnonymousTag())); + + return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::TSTrustedTimeSource().KeyName(), buffer, + static_cast(writer.GetLengthWritten())); +} + +CHIP_ERROR TimeSyncDataProvider::LoadTrustedTimeSource(TrustedTimeSource & timeSource) +{ + uint8_t buffer[kTrustedTimeSourceMaxSerializedSize]; + MutableByteSpan bufferSpan(buffer); + + ReturnErrorOnFailure(Load(DefaultStorageKeyAllocator::TSTrustedTimeSource().KeyName(), bufferSpan)); + + TLV::TLVReader reader; + + reader.Init(bufferSpan); + ReturnErrorOnFailure(reader.Next(TLV::AnonymousTag())); + ReturnErrorOnFailure(timeSource.Decode(reader)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR TimeSyncDataProvider::ClearTrustedTimeSource() +{ + return Clear(DefaultStorageKeyAllocator::TSTrustedTimeSource().KeyName()); +} + +CHIP_ERROR TimeSyncDataProvider::StoreDefaultNtp(const CharSpan & defaultNtp) +{ + return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::TSDefaultNTP().KeyName(), defaultNtp.data(), + static_cast(defaultNtp.size())); +} + +CHIP_ERROR TimeSyncDataProvider::LoadDefaultNtp(MutableCharSpan & defaultNtp) +{ + MutableByteSpan byteSpan(Uint8::from_char(defaultNtp.data()), defaultNtp.size()); + ReturnErrorOnFailure(Load(DefaultStorageKeyAllocator::TSDefaultNTP().KeyName(), byteSpan)); + defaultNtp.reduce_size(byteSpan.size()); + return CHIP_NO_ERROR; +} + +CHIP_ERROR TimeSyncDataProvider::ClearDefaultNtp() +{ + return Clear(DefaultStorageKeyAllocator::TSDefaultNTP().KeyName()); +} + +CHIP_ERROR TimeSyncDataProvider::StoreTimeZone(const Span & timeZoneList) +{ + uint8_t buffer[kTimeZoneListMaxSerializedSize]; + TLV::TLVWriter writer; + TLV::TLVType outerType; + + writer.Init(buffer); + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, outerType)); + + for (auto const & tzStore : timeZoneList) + { + ReturnErrorOnFailure(tzStore.timeZone.Encode(writer, TLV::AnonymousTag())); + } + + ReturnErrorOnFailure(writer.EndContainer(outerType)); + + return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::TSTimeZone().KeyName(), buffer, + static_cast(writer.GetLengthWritten())); +} +CHIP_ERROR TimeSyncDataProvider::LoadTimeZone(TimeZoneObj & timeZoneObj) +{ + uint8_t buffer[kTimeZoneListMaxSerializedSize]; + MutableByteSpan bufferSpan(buffer); + CHIP_ERROR err; + timeZoneObj.validSize = 0; + auto & tzStoreList = timeZoneObj.timeZoneList; + + ReturnErrorOnFailure(Load(DefaultStorageKeyAllocator::TSTimeZone().KeyName(), bufferSpan)); + + TLV::TLVReader reader; + TLV::TLVType outerType; + size_t count, i = 0; + + reader.Init(bufferSpan); + ReturnErrorOnFailure(reader.Next(TLV::TLVType::kTLVType_Array, TLV::AnonymousTag())); + ReturnErrorOnFailure(reader.EnterContainer(outerType)); + reader.CountRemainingInContainer(&count); + VerifyOrReturnError(count <= tzStoreList.size(), CHIP_ERROR_BUFFER_TOO_SMALL); + + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + auto & tzStore = tzStoreList[i]; + app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type tz; + ReturnErrorOnFailure(tz.Decode(reader)); + tzStore.timeZone.offset = tz.offset; + tzStore.timeZone.validAt = tz.validAt; + if (tz.name.HasValue()) + { + MutableCharSpan tempSpan(tzStore.name); + ReturnErrorOnFailure(CopyCharSpanToMutableCharSpan(tz.name.Value(), tempSpan)); + tzStore.timeZone.name.SetValue(tempSpan); + } + else + { + tzStore.timeZone.name.ClearValue(); + } + i++; + } + VerifyOrReturnError(err == CHIP_END_OF_TLV, err); + ReturnErrorOnFailure(reader.ExitContainer(outerType)); + err = reader.Next(); + VerifyOrReturnError(err == CHIP_END_OF_TLV, err); + + timeZoneObj.validSize = i; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR TimeSyncDataProvider::ClearTimeZone() +{ + return Clear(DefaultStorageKeyAllocator::TSTimeZone().KeyName()); +} + +CHIP_ERROR TimeSyncDataProvider::StoreDSTOffset(const DSTOffsets & dstOffsetList) +{ + uint8_t buffer[kDSTOffsetListMaxSerializedSize]; + TLV::TLVWriter writer; + TLV::TLVType outerType; + + writer.Init(buffer); + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, outerType)); + + for (auto const & dstIter : dstOffsetList) + { + ReturnErrorOnFailure(dstIter.Encode(writer, TLV::AnonymousTag())); + } + + ReturnErrorOnFailure(writer.EndContainer(outerType)); + + return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::TSDSTOffset().KeyName(), buffer, + static_cast(writer.GetLengthWritten())); +} + +CHIP_ERROR TimeSyncDataProvider::LoadDSTOffset(DSTOffsetObj & dstOffsetObj) +{ + uint8_t buffer[kDSTOffsetListMaxSerializedSize]; + MutableByteSpan bufferSpan(buffer); + CHIP_ERROR err; + dstOffsetObj.validSize = 0; + + ReturnErrorOnFailure(Load(DefaultStorageKeyAllocator::TSDSTOffset().KeyName(), bufferSpan)); + + TLV::TLVReader reader; + TLV::TLVType outerType; + size_t count, i = 0; + + reader.Init(bufferSpan); + ReturnErrorOnFailure(reader.Next(TLV::TLVType::kTLVType_Array, TLV::AnonymousTag())); + ReturnErrorOnFailure(reader.EnterContainer(outerType)); + reader.CountRemainingInContainer(&count); + VerifyOrReturnError(count <= dstOffsetObj.dstOffsetList.size(), CHIP_ERROR_BUFFER_TOO_SMALL); + auto dst = dstOffsetObj.dstOffsetList.begin(); + + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + ReturnErrorOnFailure(dst[i].Decode(reader)); + i++; + } + VerifyOrReturnError(err == CHIP_END_OF_TLV, err); + ReturnErrorOnFailure(reader.ExitContainer(outerType)); + err = reader.Next(); + VerifyOrReturnError(err == CHIP_END_OF_TLV, err); + dstOffsetObj.validSize = i; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR TimeSyncDataProvider::ClearDSTOffset() +{ + return Clear(DefaultStorageKeyAllocator::TSDSTOffset().KeyName()); +} + +CHIP_ERROR TimeSyncDataProvider::Load(const char * key, MutableByteSpan & buffer) +{ + uint16_t size = static_cast(buffer.size()); + ReturnErrorOnFailure(mPersistentStorage->SyncGetKeyValue(key, buffer.data(), size)); + + buffer.reduce_size(size); + return CHIP_NO_ERROR; +} + +CHIP_ERROR TimeSyncDataProvider::Clear(const char * key) +{ + CHIP_ERROR err = mPersistentStorage->SyncDeleteKeyValue(key); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + // This value isn't in the storage yet, so consider it deleted + return CHIP_NO_ERROR; + } + return err; +} + +} // namespace chip diff --git a/src/app/clusters/time-synchronization-server/TimeSyncDataProvider.h b/src/app/clusters/time-synchronization-server/TimeSyncDataProvider.h new file mode 100644 index 00000000000000..df2a448057f85a --- /dev/null +++ b/src/app/clusters/time-synchronization-server/TimeSyncDataProvider.h @@ -0,0 +1,80 @@ +/** + * + * 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. + */ + +/** + * @file API declarations for time sync cluster. + */ + +#pragma once + +#include +#include +#include + +namespace chip { + +class TimeSyncDataProvider +{ + using TrustedTimeSource = chip::app::Clusters::TimeSynchronization::Structs::TrustedTimeSourceStruct::Type; + using TimeZoneStruct = chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type; + using DSTOffsets = chip::app::DataModel::List; + +public: + static constexpr size_t kTimeZoneNameLength = 64; + struct TimeZoneStore + { + TimeZoneStruct timeZone; + char name[kTimeZoneNameLength]; + }; + struct TimeZoneObj + { + Span timeZoneList; + size_t validSize; + }; + struct DSTOffsetObj + { + DSTOffsets dstOffsetList; + size_t validSize; + }; + + ~TimeSyncDataProvider() {} + + void Init(PersistentStorageDelegate & persistentStorage) { mPersistentStorage = &persistentStorage; } + + CHIP_ERROR StoreTrustedTimeSource(const TrustedTimeSource & timeSource); + CHIP_ERROR LoadTrustedTimeSource(TrustedTimeSource & timeSource); + CHIP_ERROR ClearTrustedTimeSource(); + + CHIP_ERROR StoreDefaultNtp(const CharSpan & defaultNtp); + CHIP_ERROR LoadDefaultNtp(MutableCharSpan & defaultNtp); + CHIP_ERROR ClearDefaultNtp(); + + CHIP_ERROR StoreTimeZone(const chip::Span & timeZoneList); + CHIP_ERROR LoadTimeZone(TimeZoneObj & timeZoneObj); + CHIP_ERROR ClearTimeZone(); + + CHIP_ERROR StoreDSTOffset(const DSTOffsets & dstOffsetList); + CHIP_ERROR LoadDSTOffset(DSTOffsetObj & dstOffsetObj); + CHIP_ERROR ClearDSTOffset(); + +private: + CHIP_ERROR Load(const char * key, MutableByteSpan & buffer); + PersistentStorageDelegate * mPersistentStorage = nullptr; + CHIP_ERROR Clear(const char * key); +}; + +} // namespace chip diff --git a/src/app/clusters/time-synchronization-server/time-synchronization-delegate.h b/src/app/clusters/time-synchronization-server/time-synchronization-delegate.h new file mode 100644 index 00000000000000..6701015205dd29 --- /dev/null +++ b/src/app/clusters/time-synchronization-server/time-synchronization-delegate.h @@ -0,0 +1,86 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TimeSyncDataProvider.h" + +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace TimeSynchronization { + +/** @brief + * Defines methods for implementing application-specific logic for the Time Synchronization Cluster. + */ +class Delegate +{ + // using TimeZoneList = Span; + +public: + inline bool HasFeature(Feature feature) + { + uint32_t map; + bool success = (Attributes::FeatureMap::Get(mEndpoint, &map) == EMBER_ZCL_STATUS_SUCCESS); + return success ? (map & to_underlying(feature)) : false; + } + + inline EndpointId GetEndpoint() { return mEndpoint; } + inline void SetEndpoint(EndpointId ep) { mEndpoint = ep; } + + /** + * @brief Notifies the delegate that the cluster's configured list of time zones has changed. + * + * @param timeZoneList new time zone list + */ + virtual void TimeZoneListChanged(const Span timeZoneList) = 0; + /** + * @brief Give the delegate the chance to call SetDSTOffset on the TimeSynchronizationServer with a list of + * DST offsets based on the provided time zone name. If the delegate does so, it should return true. + * If the delegate does not want to set DST offsets based on the time zone, it should return false. + * + * @param name name of active time zone + */ + virtual bool HandleUpdateDSTOffset(const CharSpan name) = 0; + /** + * @brief Returns true if the provided string is a valid NTP address (either domain name or IPv6 address). + * + * @param ntp NTP address + */ + virtual bool IsNTPAddressValid(const CharSpan ntp) = 0; + /** + * @brief Returns true if a valid NTP address is a domain name as opposed to an IPv6 address. + * + * @param ntp NTP address + */ + virtual bool IsNTPAddressDomain(const CharSpan ntp) = 0; + + virtual ~Delegate() = default; + +private: + EndpointId mEndpoint = kRootEndpointId; +}; + +} // namespace TimeSynchronization +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/time-synchronization-server/time-synchronization-server.cpp b/src/app/clusters/time-synchronization-server/time-synchronization-server.cpp new file mode 100644 index 00000000000000..4ec952a97537c9 --- /dev/null +++ b/src/app/clusters/time-synchronization-server/time-synchronization-server.cpp @@ -0,0 +1,1007 @@ +/* + * 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. + */ + +#include "time-synchronization-server.h" +#include "DefaultTimeSyncDelegate.h" +#include "time-synchronization-delegate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::DeviceLayer; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::TimeSynchronization; +using namespace chip::app::Clusters::TimeSynchronization::Attributes; +using chip::TimeSyncDataProvider; +using chip::Protocols::InteractionModel::Status; + +// ----------------------------------------------------------------------------- +// Delegate Implementation + +namespace { + +Delegate * gDelegate = nullptr; + +Delegate * GetDelegate() +{ + if (gDelegate == nullptr) + { + static DefaultTimeSyncDelegate dg; + gDelegate = &dg; + } + return gDelegate; +} +} // namespace + +namespace chip { +namespace app { +namespace Clusters { +namespace TimeSynchronization { + +void SetDefaultDelegate(Delegate * delegate) +{ + gDelegate = delegate; +} + +Delegate * GetDefaultDelegate() +{ + return GetDelegate(); +} + +} // namespace TimeSynchronization +} // namespace Clusters +} // namespace app +} // namespace chip + +constexpr uint64_t kChipEpochUsSinceUnixEpoch = + static_cast(kChipEpochSecondsSinceUnixEpoch) * chip::kMicrosecondsPerSecond; + +static bool ChipEpochToUnixEpochMicro(uint64_t chipEpochTime, uint64_t & unixEpochTime) +{ + // in case chipEpochTime is too big and result overflows return false + if (chipEpochTime + kChipEpochUsSinceUnixEpoch < kChipEpochUsSinceUnixEpoch) + { + return false; + } + unixEpochTime = chipEpochTime + kChipEpochUsSinceUnixEpoch; + return true; +} + +static bool UnixEpochToChipEpochMicro(uint64_t unixEpochTime, uint64_t & chipEpochTime) +{ + VerifyOrReturnValue(unixEpochTime >= kChipEpochUsSinceUnixEpoch, false); + chipEpochTime = unixEpochTime - kChipEpochUsSinceUnixEpoch; + + return true; +} + +static CHIP_ERROR UpdateUTCTime(uint64_t UTCTimeInChipEpochUs) +{ + uint64_t UTCTimeInUnixEpochUs; + + VerifyOrReturnError(ChipEpochToUnixEpochMicro(UTCTimeInChipEpochUs, UTCTimeInUnixEpochUs), CHIP_ERROR_INVALID_TIME); + uint64_t secs = UTCTimeInChipEpochUs / chip::kMicrosecondsPerSecond; + // https://github.com/project-chip/connectedhomeip/issues/27501 + VerifyOrReturnError(secs <= UINT32_MAX, CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); + ReturnErrorOnFailure(Server::GetInstance().GetFabricTable().SetLastKnownGoodChipEpochTime( + System::Clock::Seconds32(static_cast(secs)))); + ReturnErrorOnFailure(System::SystemClock().SetClock_RealTime(System::Clock::Microseconds64(UTCTimeInUnixEpochUs))); + + return CHIP_NO_ERROR; +} + +static bool emitDSTTableEmptyEvent(EndpointId ep) +{ + Events::DSTTableEmpty::Type event; + EventNumber eventNumber; + + CHIP_ERROR error = LogEvent(event, ep, eventNumber); + + if (CHIP_NO_ERROR != error) + { + ChipLogError(Zcl, "Unable to emit DSTTableEmpty event [ep=%d]", ep); + return false; + } + ChipLogProgress(Zcl, "Emit DSTTableEmpty event [ep=%d]", ep); + + // TODO: re-schedule event for after min 1hr https://github.com/project-chip/connectedhomeip/issues/27200 + // delegate->scheduleDSTTableEmptyEvent() + return true; +} + +static bool emitDSTStatusEvent(EndpointId ep, bool dstOffsetActive) +{ + Events::DSTStatus::Type event; + event.DSTOffsetActive = dstOffsetActive; + EventNumber eventNumber; + + CHIP_ERROR error = LogEvent(event, ep, eventNumber); + + if (CHIP_NO_ERROR != error) + { + ChipLogError(Zcl, "Unable to emit DSTStatus event [ep=%d]", ep); + return false; + } + + ChipLogProgress(Zcl, "Emit DSTStatus event [ep=%d]", ep); + return true; +} + +static bool emitTimeZoneStatusEvent(EndpointId ep) +{ + const auto & tzList = TimeSynchronizationServer::Instance().GetTimeZone(); + VerifyOrReturnValue(tzList.size() != 0, false); + const auto & tz = tzList[0].timeZone; + Events::TimeZoneStatus::Type event; + + event.offset = tz.offset; + if (tz.name.HasValue()) + { + event.name.SetValue(tz.name.Value()); + } + EventNumber eventNumber; + + CHIP_ERROR error = LogEvent(event, ep, eventNumber); + + if (CHIP_NO_ERROR != error) + { + ChipLogError(Zcl, "Unable to emit TimeZoneStatus event [ep=%d]", ep); + return false; + } + + ChipLogProgress(Zcl, "Emit TimeZoneStatus event [ep=%d]", ep); + return true; +} + +static bool emitTimeFailureEvent(EndpointId ep) +{ + Events::TimeFailure::Type event; + EventNumber eventNumber; + + CHIP_ERROR error = LogEvent(event, ep, eventNumber); + + if (CHIP_NO_ERROR != error) + { + ChipLogError(Zcl, "Unable to emit TimeFailure event [ep=%d]", ep); + return false; + } + + // TODO: re-schedule event for after min 1hr if no time is still available + // https://github.com/project-chip/connectedhomeip/issues/27200 + ChipLogProgress(Zcl, "Emit TimeFailure event [ep=%d]", ep); + return true; +} + +static bool emitMissingTrustedTimeSourceEvent(EndpointId ep) +{ + Events::MissingTrustedTimeSource::Type event; + EventNumber eventNumber; + + CHIP_ERROR error = LogEvent(event, ep, eventNumber); + + if (CHIP_NO_ERROR != error) + { + ChipLogError(Zcl, "Unable to emit MissingTrustedTimeSource event [ep=%d]", ep); + return false; + } + + // TODO: re-schedule event for after min 1hr if TTS is null or cannot be reached + // https://github.com/project-chip/connectedhomeip/issues/27200 + ChipLogProgress(Zcl, "Emit MissingTrustedTimeSource event [ep=%d]", ep); + return true; +} + +TimeSynchronizationServer TimeSynchronizationServer::sTimeSyncInstance; + +TimeSynchronizationServer & TimeSynchronizationServer::Instance() +{ + return sTimeSyncInstance; +} + +void TimeSynchronizationServer::Init() +{ + mTimeSyncDataProvider.Init(Server::GetInstance().GetPersistentStorage()); + + Structs::TrustedTimeSourceStruct::Type tts; + if (mTimeSyncDataProvider.LoadTrustedTimeSource(tts) == CHIP_NO_ERROR) + { + mTrustedTimeSource.SetNonNull(tts); + } + if (LoadTimeZone() != CHIP_NO_ERROR) + { + ClearTimeZone(); + } + if (LoadDSTOffset() != CHIP_NO_ERROR) + { + ClearDSTOffset(); + } + if (!mTrustedTimeSource.IsNull()) + { + // TODO: trusted time source is available, schedule a time read https://github.com/project-chip/connectedhomeip/issues/27201 + } + System::Clock::Microseconds64 utcTime; + if (System::SystemClock().GetClock_RealTime(utcTime) == CHIP_NO_ERROR) + { + mGranularity = GranularityEnum::kMinutesGranularity; + } + else + { + mGranularity = GranularityEnum::kNoTimeGranularity; + } +} + +CHIP_ERROR TimeSynchronizationServer::SetTrustedTimeSource(const DataModel::Nullable & tts) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + mTrustedTimeSource = tts; + if (!mTrustedTimeSource.IsNull()) + { + err = mTimeSyncDataProvider.StoreTrustedTimeSource(mTrustedTimeSource.Value()); + } + else + { + err = mTimeSyncDataProvider.ClearTrustedTimeSource(); + } + return err; +} + +CHIP_ERROR TimeSynchronizationServer::SetDefaultNTP(const DataModel::Nullable & dntp) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + if (!dntp.IsNull()) + { + err = mTimeSyncDataProvider.StoreDefaultNtp(dntp.Value()); + } + else + { + err = mTimeSyncDataProvider.ClearDefaultNtp(); + } + return err; +} + +void TimeSynchronizationServer::InitTimeZone() +{ + mTimeZoneObj.validSize = 1; // one default time zone item is needed + mTimeZoneObj.timeZoneList = Span(mTz); + for (auto & tzStore : mTimeZoneObj.timeZoneList) + { + memset(tzStore.name, 0, sizeof(tzStore.name)); + tzStore.timeZone = { .offset = 0, .validAt = 0, .name = MakeOptional(CharSpan(tzStore.name, sizeof(tzStore.name))) }; + } +} + +CHIP_ERROR TimeSynchronizationServer::SetTimeZone(const DataModel::DecodableList & tzL) +{ + size_t items; + VerifyOrReturnError(CHIP_NO_ERROR == tzL.ComputeSize(&items), CHIP_IM_GLOBAL_STATUS(InvalidCommand)); + + if (items > CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + if (items == 0) + { + return ClearTimeZone(); + } + + char name[TimeSyncDataProvider::kTimeZoneNameLength]; + Structs::TimeZoneStruct::Type lastTz; + TimeState lastTzState = UpdateTimeZoneState(); + + if (lastTzState != TimeState::kInvalid) + { + const auto & tzStore = GetTimeZone()[0]; + lastTz.offset = tzStore.timeZone.offset; + if (tzStore.timeZone.name.HasValue()) + { + lastTz.name.SetValue(CharSpan(name)); + memcpy(name, tzStore.name, sizeof(tzStore.name)); + } + } + + auto newTzL = tzL.begin(); + uint8_t i = 0; + InitTimeZone(); + + while (newTzL.Next()) + { + auto & tzStore = mTimeZoneObj.timeZoneList[i]; + const auto & newTz = newTzL.GetValue(); + if (newTz.offset < -43200 || newTz.offset > 50400) + { + ReturnErrorOnFailure(LoadTimeZone()); + return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + } + // first element shall have validAt entry of 0 + if (i == 0 && newTz.validAt != 0) + { + ReturnErrorOnFailure(LoadTimeZone()); + return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + } + // if second element, it shall have validAt entry of non-0 + if (i != 0 && newTz.validAt == 0) + { + ReturnErrorOnFailure(LoadTimeZone()); + return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + } + tzStore.timeZone.offset = newTz.offset; + tzStore.timeZone.validAt = newTz.validAt; + if (newTz.name.HasValue() && newTz.name.Value().size() > 0) + { + size_t len = newTz.name.Value().size(); + if (len > sizeof(tzStore.name)) + { + ReturnErrorOnFailure(LoadTimeZone()); + return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + } + memset(tzStore.name, 0, sizeof(tzStore.name)); + chip::MutableCharSpan tempSpan(tzStore.name, len); + if (CHIP_NO_ERROR != CopyCharSpanToMutableCharSpan(newTz.name.Value(), tempSpan)) + { + ReturnErrorOnFailure(LoadTimeZone()); + return CHIP_IM_GLOBAL_STATUS(InvalidCommand); + } + tzStore.timeZone.name.SetValue(CharSpan(tzStore.name, len)); + } + else + { + tzStore.timeZone.name.ClearValue(); + } + i++; + } + if (CHIP_NO_ERROR != newTzL.GetStatus()) + { + ReturnErrorOnFailure(LoadTimeZone()); + return CHIP_IM_GLOBAL_STATUS(InvalidCommand); + } + + mTimeZoneObj.validSize = i; + + if (lastTzState != TimeState::kInvalid && TimeState::kInvalid != UpdateTimeZoneState()) + { + bool emit = false; + const auto & tz = GetTimeZone()[0].timeZone; + if (tz.offset != lastTz.offset) + { + emit = true; + } + if ((tz.name.HasValue() && lastTz.name.HasValue()) && !(tz.name.Value().data_equal(lastTz.name.Value()))) + { + emit = true; + } + if (emit) + { + mEventFlag = TimeSyncEventFlag::kTimeZoneStatus; + } + } + return mTimeSyncDataProvider.StoreTimeZone(GetTimeZone()); +} + +CHIP_ERROR TimeSynchronizationServer::LoadTimeZone() +{ + InitTimeZone(); + return mTimeSyncDataProvider.LoadTimeZone(mTimeZoneObj); +} + +CHIP_ERROR TimeSynchronizationServer::ClearTimeZone() +{ + InitTimeZone(); + return mTimeSyncDataProvider.StoreTimeZone(GetTimeZone()); +} + +void TimeSynchronizationServer::InitDSTOffset() +{ + mDstOffsetObj.validSize = 0; + mDstOffsetObj.dstOffsetList = DataModel::List(mDst); +} + +CHIP_ERROR TimeSynchronizationServer::SetDSTOffset(const DataModel::DecodableList & dstL) +{ + size_t items; + VerifyOrReturnError(CHIP_NO_ERROR == dstL.ComputeSize(&items), CHIP_IM_GLOBAL_STATUS(InvalidCommand)); + + if (items > CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + + if (items == 0) + { + return ClearDSTOffset(); + } + + auto newDstL = dstL.begin(); + size_t i = 0; + InitDSTOffset(); + + while (newDstL.Next()) + { + auto & dst = mDstOffsetObj.dstOffsetList[i]; + dst = newDstL.GetValue(); + i++; + } + + if (CHIP_NO_ERROR != newDstL.GetStatus()) + { + ReturnErrorOnFailure(LoadDSTOffset()); + return CHIP_IM_GLOBAL_STATUS(InvalidCommand); + } + + mDstOffsetObj.validSize = i; + + // only 1 validuntil null value and shall be last in the list + uint64_t lastValidUntil = 0; + for (i = 0; i < mDstOffsetObj.validSize; i++) + { + const auto & dstItem = GetDSTOffset()[i]; + // list should be sorted by validStarting + // validUntil shall be larger than validStarting + if (!dstItem.validUntil.IsNull() && dstItem.validStarting >= dstItem.validUntil.Value()) + { + ReturnErrorOnFailure(LoadDSTOffset()); + return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + } + // validStarting shall not be smaller than validUntil of previous entry + if (dstItem.validStarting < lastValidUntil) + { + ReturnErrorOnFailure(LoadDSTOffset()); + return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + } + lastValidUntil = !dstItem.validUntil.IsNull() ? dstItem.validUntil.Value() : lastValidUntil; + // only 1 validUntil null value and shall be last in the list + if (dstItem.validUntil.IsNull() && (i != mDstOffsetObj.validSize - 1)) + { + ReturnErrorOnFailure(LoadDSTOffset()); + return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + } + } + + return mTimeSyncDataProvider.StoreDSTOffset(GetDSTOffset()); +} + +CHIP_ERROR TimeSynchronizationServer::LoadDSTOffset() +{ + InitDSTOffset(); + return mTimeSyncDataProvider.LoadDSTOffset(mDstOffsetObj); +} + +CHIP_ERROR TimeSynchronizationServer::ClearDSTOffset() +{ + InitDSTOffset(); + ReturnErrorOnFailure(mTimeSyncDataProvider.ClearDSTOffset()); + emitDSTTableEmptyEvent(GetDelegate()->GetEndpoint()); + return CHIP_NO_ERROR; +} + +DataModel::Nullable & TimeSynchronizationServer::GetTrustedTimeSource() +{ + return mTrustedTimeSource; +} + +CHIP_ERROR TimeSynchronizationServer::GetDefaultNtp(MutableCharSpan & dntp) +{ + return mTimeSyncDataProvider.LoadDefaultNtp(dntp); +} + +Span & TimeSynchronizationServer::GetTimeZone() +{ + mTimeZoneObj.timeZoneList = mTimeZoneObj.timeZoneList.SubSpan(0, mTimeZoneObj.validSize); + return mTimeZoneObj.timeZoneList; +} + +DataModel::List & TimeSynchronizationServer::GetDSTOffset() +{ + mDstOffsetObj.dstOffsetList = mDstOffsetObj.dstOffsetList.SubSpan(0, mDstOffsetObj.validSize); + return mDstOffsetObj.dstOffsetList; +} + +void TimeSynchronizationServer::ScheduleDelayedAction(System::Clock::Seconds32 delay, System::TimerCompleteCallback action, + void * aAppState) +{ + if (CHIP_NO_ERROR != SystemLayer().StartTimer(std::chrono::duration_cast(delay), action, aAppState)) + { + ChipLogError(Zcl, "Time Synchronization failed to schedule timer."); + } +} + +CHIP_ERROR TimeSynchronizationServer::SetUTCTime(EndpointId ep, uint64_t utcTime, GranularityEnum granularity, + TimeSourceEnum source) +{ + ReturnErrorOnFailure(UpdateUTCTime(utcTime)); + mGranularity = granularity; + if (EMBER_ZCL_STATUS_SUCCESS != TimeSource::Set(ep, source)) + { + ChipLogError(Zcl, "Writing TimeSource failed."); + return CHIP_IM_GLOBAL_STATUS(Failure); + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR TimeSynchronizationServer::GetLocalTime(EndpointId ep, DataModel::Nullable & localTime) +{ + int64_t timeZoneOffset = 0, dstOffset = 0; + System::Clock::Microseconds64 utcTime; + uint64_t chipEpochTime; + VerifyOrReturnError(TimeState::kInvalid != UpdateDSTOffsetState(), CHIP_ERROR_INVALID_TIME); + ReturnErrorOnFailure(System::SystemClock().GetClock_RealTime(utcTime)); + VerifyOrReturnError(UnixEpochToChipEpochMicro(utcTime.count(), chipEpochTime), CHIP_ERROR_INVALID_TIME); + if (TimeState::kChanged == UpdateTimeZoneState()) + { + emitTimeZoneStatusEvent(ep); + } + VerifyOrReturnError(GetTimeZone().size() != 0, CHIP_ERROR_INVALID_TIME); + const auto & tzStore = GetTimeZone()[0]; + timeZoneOffset = static_cast(tzStore.timeZone.offset); + VerifyOrReturnError(GetDSTOffset().size() != 0, CHIP_ERROR_INVALID_TIME); + const auto & dst = GetDSTOffset()[0]; + dstOffset = static_cast(dst.offset); + + uint64_t usRemainder = chipEpochTime % chip::kMicrosecondsPerSecond; // microseconds part of chipEpochTime + chipEpochTime = (chipEpochTime / chip::kMicrosecondsPerSecond); // make it safe to cast to int64 by converting to seconds + + uint64_t localTimeSec = static_cast(static_cast(chipEpochTime) + timeZoneOffset + dstOffset); + localTime.SetNonNull((localTimeSec * chip::kMicrosecondsPerSecond) + usRemainder); + return CHIP_NO_ERROR; +} + +TimeState TimeSynchronizationServer::UpdateTimeZoneState() +{ + System::Clock::Microseconds64 utcTime; + auto & tzList = GetTimeZone(); + size_t activeTzIndex = 0; + uint64_t chipEpochTime; + + VerifyOrReturnValue(System::SystemClock().GetClock_RealTime(utcTime) == CHIP_NO_ERROR, TimeState::kInvalid); + VerifyOrReturnValue(tzList.size() != 0, TimeState::kInvalid); + VerifyOrReturnValue(UnixEpochToChipEpochMicro(utcTime.count(), chipEpochTime), TimeState::kInvalid); + + for (size_t i = 0; i < tzList.size(); i++) + { + auto & tz = tzList[i].timeZone; + if (tz.validAt != 0 && tz.validAt <= chipEpochTime) + { + tz.validAt = 0; + activeTzIndex = i; + } + } + if (activeTzIndex != 0) + { + mTimeZoneObj.validSize = tzList.size() - activeTzIndex; + auto newTimeZoneList = tzList.SubSpan(activeTzIndex); + VerifyOrReturnValue(mTimeSyncDataProvider.StoreTimeZone(newTimeZoneList) == CHIP_NO_ERROR, TimeState::kInvalid); + VerifyOrReturnValue(LoadTimeZone() == CHIP_NO_ERROR, TimeState::kInvalid); + return TimeState::kChanged; + } + return TimeState::kActive; +} + +TimeState TimeSynchronizationServer::UpdateDSTOffsetState() +{ + System::Clock::Microseconds64 utcTime; + auto & dstList = GetDSTOffset(); + size_t activeDstIndex = 0; + uint64_t chipEpochTime; + bool dstStopped = true; + + VerifyOrReturnValue(System::SystemClock().GetClock_RealTime(utcTime) == CHIP_NO_ERROR, TimeState::kInvalid); + VerifyOrReturnValue(dstList.size() != 0, TimeState::kInvalid); + VerifyOrReturnValue(UnixEpochToChipEpochMicro(utcTime.count(), chipEpochTime), TimeState::kInvalid); + + for (size_t i = 0; i < dstList.size(); i++) + { + if (dstList[i].validStarting <= chipEpochTime) + { + activeDstIndex = i; + dstStopped = false; + } + } + VerifyOrReturnValue(!dstStopped, TimeState::kStopped); + // if offset is zero and validUntil is null then no DST is used + if (dstList[activeDstIndex].offset == 0 && dstList[activeDstIndex].validUntil.IsNull()) + { + return TimeState::kStopped; + } + if (!dstList[activeDstIndex].validUntil.IsNull() && dstList[activeDstIndex].validUntil.Value() <= chipEpochTime) + { + if (activeDstIndex + 1 >= mDstOffsetObj.validSize) // last item in the list + { + VerifyOrReturnValue(ClearDSTOffset() == CHIP_NO_ERROR, TimeState::kInvalid); + return TimeState::kInvalid; + } + dstList[activeDstIndex].offset = 0; // not using dst and last DST item in the list is not active yet + return TimeState::kStopped; + } + if (activeDstIndex > 0) + { + mDstOffsetObj.validSize = dstList.size() - activeDstIndex; + auto newDstOffsetList = dstList.SubSpan(activeDstIndex); + VerifyOrReturnValue(mTimeSyncDataProvider.StoreDSTOffset(newDstOffsetList) == CHIP_NO_ERROR, TimeState::kInvalid); + VerifyOrReturnValue(LoadDSTOffset() == CHIP_NO_ERROR, TimeState::kInvalid); + return TimeState::kChanged; + } + return TimeState::kActive; +} + +TimeSyncEventFlag TimeSynchronizationServer::GetEventFlag() +{ + return mEventFlag; +} + +void TimeSynchronizationServer::ClearEventFlag(TimeSyncEventFlag flag) +{ + uint8_t eventFlag = to_underlying(mEventFlag) ^ to_underlying(flag); + mEventFlag = static_cast(eventFlag); +} + +namespace { + +class TimeSynchronizationAttrAccess : public AttributeAccessInterface +{ +public: + // register for the TimeSync cluster on all endpoints + TimeSynchronizationAttrAccess() : AttributeAccessInterface(Optional::Missing(), Id) {} + + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + +private: + CHIP_ERROR ReadTrustedTimeSource(EndpointId endpoint, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadDefaultNtp(EndpointId endpoint, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadTimeZone(EndpointId endpoint, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadDSTOffset(EndpointId endpoint, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadLocalTime(EndpointId endpoint, AttributeValueEncoder & aEncoder); +}; + +TimeSynchronizationAttrAccess gAttrAccess; + +CHIP_ERROR TimeSynchronizationAttrAccess::ReadTrustedTimeSource(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + const auto & tts = TimeSynchronizationServer::Instance().GetTrustedTimeSource(); + return aEncoder.Encode(tts); +} + +CHIP_ERROR TimeSynchronizationAttrAccess::ReadDefaultNtp(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + char buffer[DefaultNTP::TypeInfo::MaxLength()]; + MutableCharSpan dntp(buffer); + err = TimeSynchronizationServer::Instance().GetDefaultNtp(dntp); + if (err == CHIP_NO_ERROR) + { + err = aEncoder.Encode(CharSpan(buffer, dntp.size())); + } + else if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + err = aEncoder.EncodeNull(); + } + return err; +} + +CHIP_ERROR TimeSynchronizationAttrAccess::ReadTimeZone(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + CHIP_ERROR err = aEncoder.EncodeList([](const auto & encoder) -> CHIP_ERROR { + const auto & tzList = TimeSynchronizationServer::Instance().GetTimeZone(); + for (const auto & tzStore : tzList) + { + ReturnErrorOnFailure(encoder.Encode(tzStore.timeZone)); + } + + return CHIP_NO_ERROR; + }); + + return err; +} + +CHIP_ERROR TimeSynchronizationAttrAccess::ReadDSTOffset(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + CHIP_ERROR err = aEncoder.EncodeList([](const auto & encoder) -> CHIP_ERROR { + const auto & dstList = TimeSynchronizationServer::Instance().GetDSTOffset(); + for (const auto & dstOffset : dstList) + { + ReturnErrorOnFailure(encoder.Encode(dstOffset)); + } + + return CHIP_NO_ERROR; + }); + + return err; +} + +CHIP_ERROR TimeSynchronizationAttrAccess::ReadLocalTime(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + DataModel::Nullable localTime; + CHIP_ERROR err = TimeSynchronizationServer::Instance().GetLocalTime(endpoint, localTime); + err = aEncoder.Encode(localTime); + return err; +} + +CHIP_ERROR TimeSynchronizationAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + if (aPath.mClusterId != Id) + { + return CHIP_ERROR_INVALID_PATH_LIST; + } + + switch (aPath.mAttributeId) + { + case UTCTime::Id: { + System::Clock::Microseconds64 utcTimeUnix; + uint64_t chipEpochTime; + + VerifyOrReturnError(System::SystemClock().GetClock_RealTime(utcTimeUnix) == CHIP_NO_ERROR, aEncoder.EncodeNull()); + VerifyOrReturnError(UnixEpochToChipEpochMicro(utcTimeUnix.count(), chipEpochTime), aEncoder.EncodeNull()); + return aEncoder.Encode(chipEpochTime); + } + case Granularity::Id: { + return aEncoder.Encode(TimeSynchronizationServer::Instance().GetGranularity()); + } + case TrustedTimeSource::Id: { + return ReadTrustedTimeSource(aPath.mEndpointId, aEncoder); + } + case DefaultNTP::Id: { + return ReadDefaultNtp(aPath.mEndpointId, aEncoder); + } + case TimeZone::Id: { + return ReadTimeZone(aPath.mEndpointId, aEncoder); + } + case DSTOffset::Id: { + return ReadDSTOffset(aPath.mEndpointId, aEncoder); + } + case TimeZoneListMaxSize::Id: { + uint8_t max = CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE; + return aEncoder.Encode(max); + } + case DSTOffsetListMaxSize::Id: { + uint8_t max = CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE; + return aEncoder.Encode(max); + } + case LocalTime::Id: { + return ReadLocalTime(aPath.mEndpointId, aEncoder); + } + default: { + break; + } + } + + return err; +} +} // anonymous namespace + +bool emberAfTimeSynchronizationClusterSetUTCTimeCallback( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::TimeSynchronization::Commands::SetUTCTime::DecodableType & commandData) +{ + const auto & utcTime = commandData.UTCTime; + const auto & granularity = commandData.granularity; + const auto & timeSource = commandData.timeSource; + + auto currentGranularity = TimeSynchronizationServer::Instance().GetGranularity(); + if (granularity < GranularityEnum::kNoTimeGranularity || granularity > GranularityEnum::kMicrosecondsGranularity) + { + commandObj->AddStatus(commandPath, Status::InvalidCommand); + return true; + } + if (timeSource.HasValue() && (timeSource.Value() < TimeSourceEnum::kNone || timeSource.Value() > TimeSourceEnum::kGnss)) + { + commandObj->AddStatus(commandPath, Status::InvalidCommand); + return true; + } + + if (granularity != GranularityEnum::kNoTimeGranularity && + (currentGranularity == GranularityEnum::kNoTimeGranularity || granularity >= currentGranularity) && + CHIP_NO_ERROR == + TimeSynchronizationServer::Instance().SetUTCTime(commandPath.mEndpointId, utcTime, granularity, TimeSourceEnum::kAdmin)) + { + commandObj->AddStatus(commandPath, Status::Success); + } + else + { + commandObj->AddClusterSpecificFailure(commandPath, to_underlying(StatusCode::kTimeNotAccepted)); + } + return true; +} + +bool emberAfTimeSynchronizationClusterSetTrustedTimeSourceCallback( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::TimeSynchronization::Commands::SetTrustedTimeSource::DecodableType & commandData) +{ + const auto & timeSource = commandData.trustedTimeSource; + DataModel::Nullable tts; + + if (!timeSource.IsNull()) + { + + Structs::TrustedTimeSourceStruct::Type ts = { commandObj->GetAccessingFabricIndex(), timeSource.Value().nodeID, + timeSource.Value().endpoint }; + tts.SetNonNull(ts); + // TODO: schedule a utctime read from this time source and emit event only on failure to get time + emitTimeFailureEvent(commandPath.mEndpointId); + } + else + { + tts.SetNull(); + emitMissingTrustedTimeSourceEvent(commandPath.mEndpointId); + } + + TimeSynchronizationServer::Instance().SetTrustedTimeSource(tts); + commandObj->AddStatus(commandPath, Status::Success); + return true; +} + +bool emberAfTimeSynchronizationClusterSetTimeZoneCallback( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::DecodableType & commandData) +{ + const auto & timeZone = commandData.timeZone; + + CHIP_ERROR err = TimeSynchronizationServer::Instance().SetTimeZone(timeZone); + if (err != CHIP_NO_ERROR) + { + if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + commandObj->AddStatus(commandPath, Status::ResourceExhausted); + } + else if (err == CHIP_IM_GLOBAL_STATUS(InvalidCommand)) + { + commandObj->AddStatus(commandPath, Status::InvalidCommand); + } + else + { + commandObj->AddStatus(commandPath, Status::ConstraintError); + } + return true; + } + + if (to_underlying(TimeSynchronizationServer::Instance().GetEventFlag()) & to_underlying(TimeSyncEventFlag::kTimeZoneStatus)) + { + TimeSynchronizationServer::Instance().ClearEventFlag(TimeSyncEventFlag::kTimeZoneStatus); + emitTimeZoneStatusEvent(commandPath.mEndpointId); + } + GetDelegate()->TimeZoneListChanged(TimeSynchronizationServer::Instance().GetTimeZone()); + + TimeZoneDatabaseEnum tzDb; + TimeZoneDatabase::Get(commandPath.mEndpointId, &tzDb); + Commands::SetTimeZoneResponse::Type response; + TimeSynchronizationServer::Instance().UpdateTimeZoneState(); + const auto & tzList = TimeSynchronizationServer::Instance().GetTimeZone(); + if (GetDelegate()->HasFeature(Feature::kTimeZone) && tzDb != TimeZoneDatabaseEnum::kNone && tzList.size() != 0) + { + auto & tz = tzList[0].timeZone; + if (tz.name.HasValue() && GetDelegate()->HandleUpdateDSTOffset(tz.name.Value())) + { + response.DSTOffsetRequired = false; + emitDSTStatusEvent(commandPath.mEndpointId, true); + } + else + { + TimeState dstState = TimeSynchronizationServer::Instance().UpdateDSTOffsetState(); + TimeSynchronizationServer::Instance().ClearDSTOffset(); + if (dstState == TimeState::kActive || dstState == TimeState::kChanged) + { + emitDSTStatusEvent(commandPath.mEndpointId, false); + } + response.DSTOffsetRequired = true; + } + } + else + { + response.DSTOffsetRequired = true; + } + commandObj->AddResponse(commandPath, response); + return true; +} + +bool emberAfTimeSynchronizationClusterSetDSTOffsetCallback( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::DecodableType & commandData) +{ + const auto & dstOffset = commandData.DSTOffset; + + TimeState dstState = TimeSynchronizationServer::Instance().UpdateDSTOffsetState(); + + CHIP_ERROR err = TimeSynchronizationServer::Instance().SetDSTOffset(dstOffset); + if (err != CHIP_NO_ERROR) + { + if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + commandObj->AddStatus(commandPath, Status::ResourceExhausted); + } + else if (err == CHIP_IM_GLOBAL_STATUS(InvalidCommand)) + { + commandObj->AddStatus(commandPath, Status::InvalidCommand); + } + else + { + commandObj->AddStatus(commandPath, Status::ConstraintError); + } + return true; + } + // if DST state changes, generate DSTStatus event + if (dstState != TimeSynchronizationServer::Instance().UpdateDSTOffsetState()) + { + emitDSTStatusEvent(commandPath.mEndpointId, + TimeState::kActive == TimeSynchronizationServer::Instance().UpdateDSTOffsetState()); + } + + commandObj->AddStatus(commandPath, Status::Success); + return true; +} + +bool emberAfTimeSynchronizationClusterSetDefaultNTPCallback( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::TimeSynchronization::Commands::SetDefaultNTP::DecodableType & commandData) +{ + Status status = Status::Success; + const auto & dNtpChar = commandData.defaultNTP; + + if (!dNtpChar.IsNull() && dNtpChar.Value().size() > 0) + { + size_t len = dNtpChar.Value().size(); + if (len > DefaultNTP::TypeInfo::MaxLength()) + { + commandObj->AddStatus(commandPath, Status::ConstraintError); + return true; + } + if (!GetDelegate()->IsNTPAddressValid(dNtpChar.Value())) + { + commandObj->AddStatus(commandPath, Status::InvalidCommand); + return true; + } + if (GetDelegate()->IsNTPAddressDomain(dNtpChar.Value())) + { + bool dnsResolve; + if (EMBER_ZCL_STATUS_SUCCESS != SupportsDNSResolve::Get(commandPath.mEndpointId, &dnsResolve)) + { + commandObj->AddStatus(commandPath, Status::Failure); + return true; + } + if (!dnsResolve) + { + commandObj->AddStatus(commandPath, Status::InvalidCommand); + return true; + } + } + } + + status = (CHIP_NO_ERROR == TimeSynchronizationServer::Instance().SetDefaultNTP(dNtpChar)) ? Status::Success : Status::Failure; + + commandObj->AddStatus(commandPath, status); + return true; +} + +void MatterTimeSynchronizationPluginServerInitCallback() +{ + TimeSynchronizationServer::Instance().Init(); + registerAttributeAccessOverride(&gAttrAccess); +} diff --git a/src/app/clusters/time-synchronization-server/time-synchronization-server.h b/src/app/clusters/time-synchronization-server/time-synchronization-server.h new file mode 100644 index 00000000000000..e98ad10b067b19 --- /dev/null +++ b/src/app/clusters/time-synchronization-server/time-synchronization-server.h @@ -0,0 +1,114 @@ +/** + * + * 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. + */ + +/** + * @file API declarations for time sync cluster. + */ + +#pragma once + +#include "TimeSyncDataProvider.h" + +#include +#include +#include +#include + +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace TimeSynchronization { + +/** + * @brief Describes the state of time zone and DSTOffset in use. + */ +enum class TimeState : uint8_t +{ + kInvalid = 0, // No valid offset available + kActive = 1, // An offset is currently being used + kChanged = 2, // An offset expired or changed to a new value + kStopped = 3, // Permanent item in use +}; + +/** + * @brief Flags for tracking event types to emit. + */ +enum class TimeSyncEventFlag : uint8_t +{ + kNone = 0, + kDSTTableEmpty = 1, + kDSTStatus = 2, + kTimeZoneStatus = 4, + kTimeFailure = 8, + kMissingTTSource = 16, +}; + +class TimeSynchronizationServer +{ +public: + void Init(); + + static TimeSynchronizationServer & Instance(void); + TimeSyncDataProvider & GetDataProvider(void) { return mTimeSyncDataProvider; } + + CHIP_ERROR SetTrustedTimeSource(const DataModel::Nullable & tts); + CHIP_ERROR SetDefaultNTP(const DataModel::Nullable & dntp); + void InitTimeZone(void); + CHIP_ERROR SetTimeZone(const DataModel::DecodableList & tzL); + CHIP_ERROR LoadTimeZone(void); + CHIP_ERROR ClearTimeZone(void); + void InitDSTOffset(void); + CHIP_ERROR SetDSTOffset(const DataModel::DecodableList & dstL); + CHIP_ERROR LoadDSTOffset(void); + CHIP_ERROR ClearDSTOffset(void); + DataModel::Nullable & GetTrustedTimeSource(void); + Span & GetTimeZone(void); + DataModel::List & GetDSTOffset(void); + CHIP_ERROR GetDefaultNtp(MutableCharSpan & dntp); + + CHIP_ERROR SetUTCTime(chip::EndpointId ep, uint64_t utcTime, GranularityEnum granularity, TimeSourceEnum source); + CHIP_ERROR GetLocalTime(chip::EndpointId ep, DataModel::Nullable & localTime); + GranularityEnum & GetGranularity() { return mGranularity; } + + void ScheduleDelayedAction(System::Clock::Seconds32 delay, System::TimerCompleteCallback action, void * aAppState); + + TimeState UpdateTimeZoneState(); + TimeState UpdateDSTOffsetState(); + TimeSyncEventFlag GetEventFlag(void); + void ClearEventFlag(TimeSyncEventFlag flag); + +private: + DataModel::Nullable mTrustedTimeSource; + TimeSyncDataProvider::TimeZoneObj mTimeZoneObj{ Span(mTz), 0 }; + TimeSyncDataProvider::DSTOffsetObj mDstOffsetObj{ DataModel::List(mDst), 0 }; + GranularityEnum mGranularity; + + TimeSyncDataProvider::TimeZoneStore mTz[CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE]; + Structs::DSTOffsetStruct::Type mDst[CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE]; + + TimeSyncDataProvider mTimeSyncDataProvider; + static TimeSynchronizationServer sTimeSyncInstance; + TimeSyncEventFlag mEventFlag = TimeSyncEventFlag::kNone; +}; + +} // namespace TimeSynchronization +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 0faf6d0b06a05c..bda2852a949ece 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -79,6 +79,15 @@ source_set("ota-requestor-test-srcs") { ] } +source_set("time-sync-data-provider-test-srcs") { + sources = [ "${chip_root}/src/app/clusters/time-synchronization-server/TimeSyncDataProvider.cpp" ] + + public_deps = [ + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/lib/core", + ] +} + source_set("scenes-table-test-srcs") { sources = [ "${chip_root}/src/app/clusters/scenes-server/ExtensionFieldSets.h", @@ -128,6 +137,7 @@ chip_test_suite("tests") { "TestSceneTable.cpp", "TestStatusIB.cpp", "TestStatusResponseMessage.cpp", + "TestTimeSyncDataProvider.cpp", "TestTimedHandler.cpp", "TestWriteInteraction.cpp", ] @@ -163,6 +173,7 @@ chip_test_suite("tests") { ":icd-management-test-srcs", ":ota-requestor-test-srcs", ":scenes-table-test-srcs", + ":time-sync-data-provider-test-srcs", "${chip_root}/src/app", "${chip_root}/src/app/common:cluster-objects", "${chip_root}/src/app/icd:manager-srcs", diff --git a/src/app/tests/TestTimeSyncDataProvider.cpp b/src/app/tests/TestTimeSyncDataProvider.cpp new file mode 100644 index 00000000000000..a4a658f330f83b --- /dev/null +++ b/src/app/tests/TestTimeSyncDataProvider.cpp @@ -0,0 +1,298 @@ +/* + * + * 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. + */ + +#include +#include +#include +#include + +#include + +using namespace chip; +using namespace chip::DeviceLayer; +using namespace chip::app::Clusters::TimeSynchronization; +using chip::TimeSyncDataProvider; + +using TrustedTimeSource = app::Clusters::TimeSynchronization::Structs::TrustedTimeSourceStruct::Type; +using TimeZoneList = Span; +using TimeZone = app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type; +using DSTOffsetList = app::DataModel::List; +using DSTOffset = app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::Type; + +namespace { + +void TestTrustedTimeSourceStoreLoad(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + TimeSyncDataProvider timeSyncDataProv; + timeSyncDataProv.Init(persistentStorage); + + TrustedTimeSource tts = { chip::FabricIndex(1), chip::NodeId(20), chip::EndpointId(0) }; + + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == timeSyncDataProv.StoreTrustedTimeSource(tts)); + + TrustedTimeSource retrievedTrustedTimeSource; + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == timeSyncDataProv.LoadTrustedTimeSource(retrievedTrustedTimeSource)); + NL_TEST_ASSERT(inSuite, retrievedTrustedTimeSource.fabricIndex == chip::FabricIndex(1)); + NL_TEST_ASSERT(inSuite, retrievedTrustedTimeSource.nodeID == chip::NodeId(20)); + NL_TEST_ASSERT(inSuite, retrievedTrustedTimeSource.endpoint == chip::EndpointId(0)); +} + +void TestTrustedTimeSourceEmpty(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + TimeSyncDataProvider timeSyncDataProv; + timeSyncDataProv.Init(persistentStorage); + + TrustedTimeSource tts; + + NL_TEST_ASSERT(inSuite, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == timeSyncDataProv.LoadTrustedTimeSource(tts)); +} + +void TestDefaultNTPStoreLoad(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + TimeSyncDataProvider timeSyncDataProv; + timeSyncDataProv.Init(persistentStorage); + + char ntp[10] = "localhost"; + chip::CharSpan defaultNTP(ntp); + + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == timeSyncDataProv.StoreDefaultNtp(defaultNTP)); + + char buf[5]; + chip::MutableCharSpan getDefaultNtp(buf); + + NL_TEST_ASSERT(inSuite, CHIP_ERROR_BUFFER_TOO_SMALL == timeSyncDataProv.LoadDefaultNtp(getDefaultNtp)); + NL_TEST_ASSERT(inSuite, getDefaultNtp.size() == 5); + + char buf1[20]; + chip::MutableCharSpan getDefaultNtp1(buf1); + + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == timeSyncDataProv.LoadDefaultNtp(getDefaultNtp1)); + NL_TEST_ASSERT(inSuite, getDefaultNtp1.size() == 10); +} + +void TestDefaultNTPEmpty(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + TimeSyncDataProvider timeSyncDataProv; + timeSyncDataProv.Init(persistentStorage); + + chip::MutableCharSpan defaultNTP; + + NL_TEST_ASSERT(inSuite, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == timeSyncDataProv.LoadDefaultNtp(defaultNTP)); +} + +void TestTimeZoneStoreLoad(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + TimeSyncDataProvider timeSyncDataProv; + timeSyncDataProv.Init(persistentStorage); + + const auto makeTimeZone = [](int32_t offset = 0, uint64_t validAt = 0, char * name = nullptr, uint8_t len = 0) { + TimeSyncDataProvider::TimeZoneStore tzS; + tzS.timeZone.offset = offset; + tzS.timeZone.validAt = validAt; + if (name != nullptr && len) + { + memcpy(tzS.name, name, len); + tzS.timeZone.name.SetValue(chip::CharSpan(tzS.name, len)); + } + return tzS; + }; + char tzShort[] = "LA"; + char tzLong[] = "MunichOnTheLongRiverOfIsarInNiceSummerWeatherWithAugustinerBeer"; + char tzBerlin[] = "Berlin"; + TimeSyncDataProvider::TimeZoneStore tzS[3] = { makeTimeZone(1, 1, tzShort, sizeof(tzShort)), + makeTimeZone(2, 2, tzLong, sizeof(tzLong)), + makeTimeZone(3, 3, tzBerlin, sizeof(tzBerlin)) }; + TimeZoneList tzL(tzS); + NL_TEST_ASSERT(inSuite, tzL.size() == 3); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == timeSyncDataProv.StoreTimeZone(tzL)); + + TimeSyncDataProvider::TimeZoneStore emptyTzS[3] = { makeTimeZone(), makeTimeZone(), makeTimeZone() }; + + tzL = TimeZoneList(emptyTzS); + TimeSyncDataProvider::TimeZoneObj tzObj{ tzL, 3 }; + NL_TEST_ASSERT(inSuite, tzL.size() == 3); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == timeSyncDataProv.LoadTimeZone(tzObj)); + NL_TEST_ASSERT(inSuite, tzObj.validSize == 3); + + NL_TEST_ASSERT(inSuite, !tzL.empty()); + + if (!tzL.empty()) + { + auto & tz = tzL[0].timeZone; + NL_TEST_ASSERT(inSuite, tz.offset == 1); + NL_TEST_ASSERT(inSuite, tz.validAt == 1); + NL_TEST_ASSERT(inSuite, tz.name.HasValue()); + NL_TEST_ASSERT(inSuite, tz.name.Value().size() == 3); + + tzL = tzL.SubSpan(1); + } + + if (!tzL.empty()) + { + auto & tz = tzL[0].timeZone; + NL_TEST_ASSERT(inSuite, tz.offset == 2); + NL_TEST_ASSERT(inSuite, tz.validAt == 2); + NL_TEST_ASSERT(inSuite, tz.name.HasValue()); + NL_TEST_ASSERT(inSuite, tz.name.Value().size() == 64); + + tzL = tzL.SubSpan(1); + } + + if (!tzL.empty()) + { + auto & tz = tzL[0].timeZone; + NL_TEST_ASSERT(inSuite, tz.offset == 3); + NL_TEST_ASSERT(inSuite, tz.validAt == 3); + NL_TEST_ASSERT(inSuite, tz.name.HasValue()); + NL_TEST_ASSERT(inSuite, tz.name.Value().size() == 7); + + tzL = tzL.SubSpan(1); + } + + NL_TEST_ASSERT(inSuite, tzL.empty()); +} + +void TestTimeZoneEmpty(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + TimeSyncDataProvider timeSyncDataProv; + timeSyncDataProv.Init(persistentStorage); + + TimeSyncDataProvider::TimeZoneObj timeZoneObj; + + NL_TEST_ASSERT(inSuite, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == timeSyncDataProv.LoadTimeZone(timeZoneObj)); + NL_TEST_ASSERT(inSuite, !timeZoneObj.timeZoneList.begin()); + NL_TEST_ASSERT(inSuite, timeZoneObj.validSize == 0); +} + +void TestDSTOffset(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + TimeSyncDataProvider timeSyncDataProv; + timeSyncDataProv.Init(persistentStorage); + + const auto makeDSTOffset = [](int32_t offset = 0, uint64_t validStarting = 0, uint64_t validUntil = 0) { + DSTOffset dst; + dst.offset = offset; + dst.validStarting = validStarting; + if (validUntil) + dst.validUntil.SetNonNull(validUntil); + return dst; + }; + DSTOffset dstS[3] = { makeDSTOffset(1, 1, 2), makeDSTOffset(2, 2, 3), makeDSTOffset(3, 3) }; + DSTOffsetList dstL(dstS); + TimeSyncDataProvider::DSTOffsetObj dstObj{ dstL, 3 }; + NL_TEST_ASSERT(inSuite, dstObj.validSize == 3); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == timeSyncDataProv.StoreDSTOffset(dstL)); + + DSTOffset emtpyDstS[3] = { makeDSTOffset(), makeDSTOffset(), makeDSTOffset() }; + + dstObj.dstOffsetList = DSTOffsetList(emtpyDstS); + dstObj.validSize = 0; + NL_TEST_ASSERT(inSuite, dstObj.dstOffsetList.size() == 3); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == timeSyncDataProv.LoadDSTOffset(dstObj)); + NL_TEST_ASSERT(inSuite, dstObj.validSize == 3); + + NL_TEST_ASSERT(inSuite, !dstObj.dstOffsetList.empty()); + + if (!dstObj.dstOffsetList.empty()) + { + auto & dst = dstObj.dstOffsetList.data()[0]; + NL_TEST_ASSERT(inSuite, dst.offset == 1); + NL_TEST_ASSERT(inSuite, dst.validStarting == 1); + NL_TEST_ASSERT(inSuite, !dst.validUntil.IsNull()); + NL_TEST_ASSERT(inSuite, dst.validUntil.Value() == 2); + + dstObj.dstOffsetList = dstObj.dstOffsetList.SubSpan(1); + } + + if (!dstObj.dstOffsetList.empty()) + { + auto & dst = dstObj.dstOffsetList.data()[0]; + NL_TEST_ASSERT(inSuite, dst.offset == 2); + NL_TEST_ASSERT(inSuite, dst.validStarting == 2); + NL_TEST_ASSERT(inSuite, !dst.validUntil.IsNull()); + NL_TEST_ASSERT(inSuite, dst.validUntil.Value() == 3); + + dstObj.dstOffsetList = dstObj.dstOffsetList.SubSpan(1); + } + + if (!dstObj.dstOffsetList.empty()) + { + auto & dst = dstObj.dstOffsetList.data()[0]; + NL_TEST_ASSERT(inSuite, dst.offset == 3); + NL_TEST_ASSERT(inSuite, dst.validStarting == 3); + NL_TEST_ASSERT(inSuite, dst.validUntil.IsNull()); + + dstObj.dstOffsetList = dstObj.dstOffsetList.SubSpan(1); + } + + NL_TEST_ASSERT(inSuite, dstObj.dstOffsetList.empty()); +} + +void TestDSTOffsetEmpty(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + TimeSyncDataProvider timeSyncDataProv; + timeSyncDataProv.Init(persistentStorage); + + TimeSyncDataProvider::DSTOffsetObj dstObj; + + NL_TEST_ASSERT(inSuite, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == timeSyncDataProv.LoadDSTOffset(dstObj)); + NL_TEST_ASSERT(inSuite, !dstObj.dstOffsetList.begin()); + NL_TEST_ASSERT(inSuite, dstObj.validSize == 0); +} + +const nlTest sTests[] = { NL_TEST_DEF("Test TrustedTimeSource store load", TestTrustedTimeSourceStoreLoad), + NL_TEST_DEF("Test TrustedTimeSource empty", TestTrustedTimeSourceEmpty), + NL_TEST_DEF("Test default NTP store load", TestDefaultNTPStoreLoad), + NL_TEST_DEF("Test default NTP empty", TestDefaultNTPEmpty), + NL_TEST_DEF("Test time zone store load", TestTimeZoneStoreLoad), + NL_TEST_DEF("Test time zone (empty list)", TestTimeZoneEmpty), + NL_TEST_DEF("Test DSTOffset", TestDSTOffset), + NL_TEST_DEF("Test DSTOffset (empty list)", TestDSTOffsetEmpty), + NL_TEST_SENTINEL() }; + +int TestSetup(void * inContext) +{ + VerifyOrReturnError(CHIP_NO_ERROR == chip::Platform::MemoryInit(), FAILURE); + return SUCCESS; +} + +int TestTearDown(void * inContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +} // namespace + +int TestTimeSyncDataProvider() +{ + nlTestSuite theSuite = { "Time Sync data provider tests", &sTests[0], TestSetup, TestTearDown }; + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestTimeSyncDataProvider) diff --git a/src/app/tests/suites/TestDescriptorCluster.yaml b/src/app/tests/suites/TestDescriptorCluster.yaml index 7fa7c7ee79aa0f..03d26a73eb5830 100644 --- a/src/app/tests/suites/TestDescriptorCluster.yaml +++ b/src/app/tests/suites/TestDescriptorCluster.yaml @@ -59,6 +59,7 @@ tests: 0x0035, # Thread Network Diagnostiscs 0x0036, # WiFi Network Diagnostics 0x0037, # Ethernet Network Diagnostics + 0x0038, # Time Synchronization 0x003C, # Administrator Commissioning 0x003E, # Operational Credentials 0x003F, # Group Key Management diff --git a/src/app/tests/suites/TestTimeSynchronization.yaml b/src/app/tests/suites/TestTimeSynchronization.yaml new file mode 100644 index 00000000000000..5f34a1967d90bf --- /dev/null +++ b/src/app/tests/suites/TestTimeSynchronization.yaml @@ -0,0 +1,228 @@ +# 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: Time Synchronization Tests + +config: + nodeId: 0x12344321 + cluster: "Time Synchronization" + endpoint: 0 + +tests: + - label: "Wait for the commissioned device to be retrieved" + cluster: "DelayCommands" + command: "WaitForCommissionee" + arguments: + values: + - name: "nodeId" + value: nodeId + + - label: "Read Time Zone" + command: "readAttribute" + attribute: "TimeZone" + response: + value: [{ Offset: 0, ValidAt: 0 }] + + - label: "Set Time Zone list" + command: "SetTimeZone" + arguments: + values: + - name: "TimeZone" + value: [{ Offset: 100, ValidAt: 0, Name: "CET" }] + response: + values: + - name: "DSTOffsetRequired" + value: true + + - label: "Read Time Zone" + command: "readAttribute" + attribute: "TimeZone" + response: + value: [{ Offset: 100, ValidAt: 0, Name: "CET" }] + + - label: "Set Time Zone with missing optional name field" + command: "SetTimeZone" + arguments: + values: + - name: "TimeZone" + value: [{ Offset: 100, ValidAt: 0 }] + response: + values: + - name: "DSTOffsetRequired" + value: true + + - label: "Read Time Zone" + command: "readAttribute" + attribute: "TimeZone" + response: + value: [{ Offset: 100, ValidAt: 0 }] + + - label: "Set Time Zone with very long name" + command: "SetTimeZone" + arguments: + values: + - name: "TimeZone" + value: + [ + { + Offset: 100, + ValidAt: 0, + Name: "MunichOnTheLongRiverOfIsarInNiceSummerWeatherWithAugustinerBeerss", + }, + ] + response: + error: CONSTRAINT_ERROR + + - label: "Set Time Zone with first item validAt not zero" + command: "SetTimeZone" + arguments: + values: + - name: "TimeZone" + value: [{ Offset: 100, ValidAt: 1, Name: "CDT" }] + response: + error: CONSTRAINT_ERROR + + - label: "Set Time Zone with second item validAt zero" + command: "SetTimeZone" + arguments: + values: + - name: "TimeZone" + value: + [ + { Offset: 100, ValidAt: 0, Name: "CST" }, + { Offset: 200, ValidAt: 0, Name: "CDT" }, + ] + response: + error: CONSTRAINT_ERROR + + - label: "Set Time Zone with more than supported list count" + command: "SetTimeZone" + arguments: + values: + - name: "TimeZone" + value: + [ + { Offset: 100, ValidAt: 0, Name: "CST" }, + { Offset: 200, ValidAt: 1, Name: "CDT" }, + { Offset: 200, ValidAt: 2, Name: "CET" }, + ] + response: + error: RESOURCE_EXHAUSTED + + - label: "Set Time Zone empty" + command: "SetTimeZone" + arguments: + values: + - name: "TimeZone" + value: [] + response: + values: + - name: "DSTOffsetRequired" + value: true + + - label: "Read Time Zone" + command: "readAttribute" + attribute: "TimeZone" + response: + value: [{ Offset: 0, ValidAt: 0 }] + + - label: "Read DSTOffset" + command: "readAttribute" + attribute: "DSTOffset" + response: + value: [] + + - label: "Set DSTOffset single item" + command: "SetDSTOffset" + arguments: + values: + - name: "DSTOffset" + value: [{ Offset: 1, ValidStarting: 1, ValidUntil: 2 }] + + - label: "Set DSTOffset empty" + command: "SetDSTOffset" + arguments: + values: + - name: "DSTOffset" + value: [] + + - label: "Set DSTOffset with more than 1 null value" + command: "SetDSTOffset" + arguments: + values: + - name: "DSTOffset" + value: + [ + { Offset: 1, ValidStarting: 1, ValidUntil: null }, + { Offset: 0, ValidStarting: 2, ValidUntil: null }, + ] + response: + error: CONSTRAINT_ERROR + + - label: "Set unsorted DSTOffset entries" + command: "SetDSTOffset" + arguments: + values: + - name: "DSTOffset" + value: + [ + { Offset: 1, ValidStarting: 2, ValidUntil: 3 }, + { Offset: 0, ValidStarting: 1, ValidUntil: null }, + ] + response: + error: CONSTRAINT_ERROR + + - label: "Set DSTOffset with multiple entries" + command: "SetDSTOffset" + arguments: + values: + - name: "DSTOffset" + value: + [ + { Offset: 1, ValidStarting: 1, ValidUntil: 2 }, + { Offset: 0, ValidStarting: 3, ValidUntil: null }, + ] + + - label: "Read DSTOffset" + command: "readAttribute" + attribute: "DSTOffset" + response: + value: + [ + { Offset: 1, ValidStarting: 1, ValidUntil: 2 }, + { Offset: 0, ValidStarting: 3, ValidUntil: null }, + ] + + - label: "Set DSTOffset with same validStarting and validUntil" + command: "SetDSTOffset" + arguments: + values: + - name: "DSTOffset" + value: [{ Offset: 0, ValidStarting: 1, ValidUntil: 1 }] + response: + error: CONSTRAINT_ERROR + + - label: "Set DSTOffset with more than supported list count" + command: "SetDSTOffset" + arguments: + values: + - name: "DSTOffset" + value: + [ + { Offset: 1, ValidStarting: 1, ValidUntil: 2 }, + { Offset: 0, ValidStarting: 3, ValidUntil: 5 }, + { Offset: 0, ValidStarting: 6, ValidUntil: null }, + ] + response: + error: RESOURCE_EXHAUSTED diff --git a/src/app/tests/suites/ciTests.json b/src/app/tests/suites/ciTests.json index 9f352be98b3b17..818a91722600f6 100644 --- a/src/app/tests/suites/ciTests.json +++ b/src/app/tests/suites/ciTests.json @@ -252,7 +252,8 @@ "TestAccessControlConstraints", "TestLevelControlWithOnOffDependency", "TestCommissioningWindow", - "TestCommissionerNodeId" + "TestCommissionerNodeId", + "TestTimeSynchronization" ], "MultiAdmin": ["TestMultiAdmin"], "SoftwareDiagnostics": ["Test_TC_DGSW_1_1"], diff --git a/src/app/util/util.cpp b/src/app/util/util.cpp index d7702eeb2e5c6b..ed27deaa540461 100644 --- a/src/app/util/util.cpp +++ b/src/app/util/util.cpp @@ -132,7 +132,6 @@ void MatterTimePluginServerInitCallback() {} void MatterAclPluginServerInitCallback() {} void MatterPollControlPluginServerInitCallback() {} void MatterUnitLocalizationPluginServerInitCallback() {} -void MatterTimeSynchronizationPluginServerInitCallback() {} void MatterProxyValidPluginServerInitCallback() {} void MatterProxyDiscoveryPluginServerInitCallback() {} void MatterProxyConfigurationPluginServerInitCallback() {} diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json index ce387388c772ad..83c069ef5c4744 100644 --- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json +++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json @@ -300,7 +300,10 @@ "TimeZone", "DSTOffset", "UTCTime", - "LocalTime" + "LocalTime", + "Granularity", + "TimeZoneListMaxSize", + "DSTOffsetListMaxSize" ], "Temperature Control": ["SupportedTemperatureLevels"], "Operational State": ["OperationalState", "OperationalError"], diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json index 686ca4d51c3795..b6554bf6854653 100644 --- a/src/app/zap-templates/zcl/zcl.json +++ b/src/app/zap-templates/zcl/zcl.json @@ -298,7 +298,10 @@ "TimeZone", "DSTOffset", "UTCTime", - "LocalTime" + "LocalTime", + "Granularity", + "TimeZoneListMaxSize", + "DSTOffsetListMaxSize" ], "Temperature Control": ["SupportedTemperatureLevels"], "Operational State": ["OperationalState", "OperationalError"], diff --git a/src/app/zap_cluster_list.json b/src/app/zap_cluster_list.json index 5f6fa66ad798cf..bdfc33f3babce8 100644 --- a/src/app/zap_cluster_list.json +++ b/src/app/zap_cluster_list.json @@ -291,7 +291,7 @@ ], "TIME_CLUSTER": [], "TIME_FORMAT_LOCALIZATION_CLUSTER": ["time-format-localization-server"], - "TIME_SYNCHRONIZATION_CLUSTER": [], + "TIME_SYNCHRONIZATION_CLUSTER": ["time-synchronization-server"], "TONER_CARTRIDGE_MONITORING_CLUSTER": [], "TOTAL_COLIFORM_BACTERIA_CONCENTRATION_MEASUREMENT_CLUSTER": [], "TOTAL_TRIHALOMETHANES_CONCENTRATION_MEASUREMENT_CLUSTER": [], diff --git a/src/controller/data_model/controller-clusters.zap b/src/controller/data_model/controller-clusters.zap index 648c8e7bbd3b6d..20d598ef63a499 100644 --- a/src/controller/data_model/controller-clusters.zap +++ b/src/controller/data_model/controller-clusters.zap @@ -8107,7 +8107,7 @@ "side": "server", "type": "GranularityEnum", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, "defaultValue": "0x00", @@ -8276,6 +8276,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "SupportsDNSResolve", + "code": 12, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "false", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "GeneratedCommandList", "code": 65528, @@ -8350,7 +8366,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "0x0F", "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/src/include/platform/PlatformManager.h b/src/include/platform/PlatformManager.h index cc788f353be5ec..41334a465b4549 100644 --- a/src/include/platform/PlatformManager.h +++ b/src/include/platform/PlatformManager.h @@ -45,7 +45,6 @@ class ConfigurationManagerImpl; class DeviceControlServer; class TraitManager; class ThreadStackManagerImpl; -class TimeSyncManager; namespace Internal { class BLEManagerImpl; @@ -258,7 +257,6 @@ class PlatformManager friend class FailSafeContext; friend class TraitManager; friend class ThreadStackManagerImpl; - friend class TimeSyncManager; friend class Internal::BLEManagerImpl; template friend class Internal::GenericPlatformManagerImpl; diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index c6c6726ff5fcc8..a9b1e5739e5a59 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1420,6 +1420,32 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_MAX_SCENES_CONCURRENT_ITERATORS 2 #endif +/** + * @def CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE + * + * Defines the size of the time zone list + */ +#ifndef CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE +#define CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE 2 +#endif + +#if (CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE < 1 || CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE > 2) +#error "Please ensure CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE meets minimum and maximum requirements." +#endif + +/** + * @def CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE + * + * Defines the size of the DSTOffset list + */ +#ifndef CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE +#define CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE 2 +#endif + +#if (CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE < 1) +#error "Please ensure CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE meets minimum requirements." +#endif + /** * @def CHIP_CONFIG_SKIP_APP_SPECIFIC_GENERATED_HEADER_INCLUDES * diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index b437062d049b66..9adc8fdf287353 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -215,6 +215,12 @@ class DefaultStorageKeyAllocator { return StorageKeyName::Formatted("f/%x/e/%x/sc/%x", fabric, endpoint, idx); } + + // Time synchronization cluster + static StorageKeyName TSTrustedTimeSource() { return StorageKeyName::FromConst("g/ts/tts"); } + static StorageKeyName TSDefaultNTP() { return StorageKeyName::FromConst("g/ts/dntp"); } + static StorageKeyName TSTimeZone() { return StorageKeyName::FromConst("g/ts/tz"); } + static StorageKeyName TSDSTOffset() { return StorageKeyName::FromConst("g/ts/dsto"); } }; } // namespace chip diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp index 37bdff68f5df2f..9ed3737c63f06b 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp @@ -5081,37 +5081,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value) namespace TimeSynchronization { namespace Attributes { -namespace Granularity { - -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::GranularityEnum * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::GranularityEnum value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE); -} - -} // namespace Granularity - namespace TimeSource { EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeSourceEnum * value) @@ -5205,68 +5174,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, bool value) } // namespace NTPServerAvailable -namespace TimeZoneListMaxSize { - -EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -} // namespace TimeZoneListMaxSize - -namespace DSTOffsetListMaxSize { - -EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -} // namespace DSTOffsetListMaxSize - namespace SupportsDNSResolve { EmberAfStatus Get(chip::EndpointId endpoint, bool * value) diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h index d73efc57382d15..9e65d3df8943ee 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h @@ -974,11 +974,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value); namespace TimeSynchronization { namespace Attributes { -namespace Granularity { -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::GranularityEnum * value); // GranularityEnum -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::GranularityEnum value); -} // namespace Granularity - namespace TimeSource { EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeSourceEnum * value); // TimeSourceEnum EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeSourceEnum value); @@ -995,16 +990,6 @@ EmberAfStatus Get(chip::EndpointId endpoint, bool * value); // boolean EmberAfStatus Set(chip::EndpointId endpoint, bool value); } // namespace NTPServerAvailable -namespace TimeZoneListMaxSize { -EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value); // int8u -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value); -} // namespace TimeZoneListMaxSize - -namespace DSTOffsetListMaxSize { -EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value); // int8u -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value); -} // namespace DSTOffsetListMaxSize - namespace SupportsDNSResolve { EmberAfStatus Get(chip::EndpointId endpoint, bool * value); // boolean EmberAfStatus Set(chip::EndpointId endpoint, bool value); diff --git a/zzz_generated/chip-tool/zap-generated/test/Commands.h b/zzz_generated/chip-tool/zap-generated/test/Commands.h index e740cfc7979371..4b025cfcd7d3fd 100644 --- a/zzz_generated/chip-tool/zap-generated/test/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/test/Commands.h @@ -283,6 +283,7 @@ class TestList : public Command printf("TestLevelControlWithOnOffDependency\n"); printf("TestCommissioningWindow\n"); printf("TestCommissionerNodeId\n"); + printf("TestTimeSynchronization\n"); printf("TestMultiAdmin\n"); printf("Test_TC_DGSW_1_1\n"); printf("TestSubscribe_OnOff\n"); @@ -83303,22 +83304,24 @@ class TestDescriptorClusterSuite : public TestCommand VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 19)); VerifyOrReturn(CheckValue("serverList[19]", iter_0.GetValue(), 55UL)); VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 20)); - VerifyOrReturn(CheckValue("serverList[20]", iter_0.GetValue(), 60UL)); + VerifyOrReturn(CheckValue("serverList[20]", iter_0.GetValue(), 56UL)); VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 21)); - VerifyOrReturn(CheckValue("serverList[21]", iter_0.GetValue(), 62UL)); + VerifyOrReturn(CheckValue("serverList[21]", iter_0.GetValue(), 60UL)); VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 22)); - VerifyOrReturn(CheckValue("serverList[22]", iter_0.GetValue(), 63UL)); + VerifyOrReturn(CheckValue("serverList[22]", iter_0.GetValue(), 62UL)); VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 23)); - VerifyOrReturn(CheckValue("serverList[23]", iter_0.GetValue(), 64UL)); + VerifyOrReturn(CheckValue("serverList[23]", iter_0.GetValue(), 63UL)); VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 24)); - VerifyOrReturn(CheckValue("serverList[24]", iter_0.GetValue(), 65UL)); + VerifyOrReturn(CheckValue("serverList[24]", iter_0.GetValue(), 64UL)); VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 25)); - VerifyOrReturn(CheckValue("serverList[25]", iter_0.GetValue(), 70UL)); + VerifyOrReturn(CheckValue("serverList[25]", iter_0.GetValue(), 65UL)); VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 26)); - VerifyOrReturn(CheckValue("serverList[26]", iter_0.GetValue(), 1029UL)); + VerifyOrReturn(CheckValue("serverList[26]", iter_0.GetValue(), 70UL)); VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 27)); - VerifyOrReturn(CheckValue("serverList[27]", iter_0.GetValue(), 4294048774UL)); - VerifyOrReturn(CheckNoMoreListItems("serverList", iter_0, 28)); + VerifyOrReturn(CheckValue("serverList[27]", iter_0.GetValue(), 1029UL)); + VerifyOrReturn(CheckNextListItemDecodes("serverList", iter_0, 28)); + VerifyOrReturn(CheckValue("serverList[28]", iter_0.GetValue(), 4294048774UL)); + VerifyOrReturn(CheckNoMoreListItems("serverList", iter_0, 29)); } } break; @@ -88871,6 +88874,596 @@ class TestCommissionerNodeIdSuite : public TestCommand } }; +class TestTimeSynchronizationSuite : public TestCommand +{ +public: + TestTimeSynchronizationSuite(CredentialIssuerCommands * credsIssuerConfig) : + TestCommand("TestTimeSynchronization", 21, credsIssuerConfig) + { + AddArgument("nodeId", 0, UINT64_MAX, &mNodeId); + AddArgument("cluster", &mCluster); + AddArgument("endpoint", 0, UINT16_MAX, &mEndpoint); + AddArgument("timeout", 0, UINT16_MAX, &mTimeout); + } + + ~TestTimeSynchronizationSuite() {} + + chip::System::Clock::Timeout GetWaitDuration() const override + { + return chip::System::Clock::Seconds16(mTimeout.ValueOr(kTimeoutInSeconds)); + } + +private: + chip::Optional mNodeId; + chip::Optional mCluster; + chip::Optional mEndpoint; + chip::Optional mTimeout; + + chip::EndpointId GetEndpoint(chip::EndpointId endpoint) { return mEndpoint.HasValue() ? mEndpoint.Value() : endpoint; } + + // + // Tests methods + // + + void OnResponse(const chip::app::StatusIB & status, chip::TLV::TLVReader * data) override + { + bool shouldContinue = false; + + switch (mTestIndex - 1) + { + case 0: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + shouldContinue = true; + break; + case 1: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::DecodableList< + chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::DecodableType> + value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + { + auto iter_0 = value.begin(); + VerifyOrReturn(CheckNextListItemDecodes("timeZone", iter_0, 0)); + VerifyOrReturn(CheckValue("timeZone[0].offset", iter_0.GetValue().offset, 0L)); + VerifyOrReturn(CheckValue("timeZone[0].validAt", iter_0.GetValue().validAt, 0ULL)); + VerifyOrReturn(CheckNoMoreListItems("timeZone", iter_0, 1)); + } + } + break; + case 2: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZoneResponse::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("DSTOffsetRequired", value.DSTOffsetRequired, true)); + } + break; + case 3: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::DecodableList< + chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::DecodableType> + value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + { + auto iter_0 = value.begin(); + VerifyOrReturn(CheckNextListItemDecodes("timeZone", iter_0, 0)); + VerifyOrReturn(CheckValue("timeZone[0].offset", iter_0.GetValue().offset, 100L)); + VerifyOrReturn(CheckValue("timeZone[0].validAt", iter_0.GetValue().validAt, 0ULL)); + VerifyOrReturn(CheckValuePresent("timeZone[0].name", iter_0.GetValue().name)); + VerifyOrReturn( + CheckValueAsString("timeZone[0].name.Value()", iter_0.GetValue().name.Value(), chip::CharSpan("CET", 3))); + VerifyOrReturn(CheckNoMoreListItems("timeZone", iter_0, 1)); + } + } + break; + case 4: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZoneResponse::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("DSTOffsetRequired", value.DSTOffsetRequired, true)); + } + break; + case 5: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::DecodableList< + chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::DecodableType> + value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + { + auto iter_0 = value.begin(); + VerifyOrReturn(CheckNextListItemDecodes("timeZone", iter_0, 0)); + VerifyOrReturn(CheckValue("timeZone[0].offset", iter_0.GetValue().offset, 100L)); + VerifyOrReturn(CheckValue("timeZone[0].validAt", iter_0.GetValue().validAt, 0ULL)); + VerifyOrReturn(CheckNoMoreListItems("timeZone", iter_0, 1)); + } + } + break; + case 6: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_CONSTRAINT_ERROR)); + break; + case 7: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_CONSTRAINT_ERROR)); + break; + case 8: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_CONSTRAINT_ERROR)); + break; + case 9: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_RESOURCE_EXHAUSTED)); + break; + case 10: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZoneResponse::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("DSTOffsetRequired", value.DSTOffsetRequired, true)); + } + break; + case 11: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::DecodableList< + chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::DecodableType> + value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + { + auto iter_0 = value.begin(); + VerifyOrReturn(CheckNextListItemDecodes("timeZone", iter_0, 0)); + VerifyOrReturn(CheckValue("timeZone[0].offset", iter_0.GetValue().offset, 0L)); + VerifyOrReturn(CheckValue("timeZone[0].validAt", iter_0.GetValue().validAt, 0ULL)); + VerifyOrReturn(CheckNoMoreListItems("timeZone", iter_0, 1)); + } + } + break; + case 12: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::DecodableList< + chip::app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::DecodableType> + value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + { + auto iter_0 = value.begin(); + VerifyOrReturn(CheckNoMoreListItems("DSTOffset", iter_0, 0)); + } + } + break; + case 13: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + break; + case 14: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + break; + case 15: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_CONSTRAINT_ERROR)); + break; + case 16: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_CONSTRAINT_ERROR)); + break; + case 17: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + break; + case 18: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::DecodableList< + chip::app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::DecodableType> + value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + { + auto iter_0 = value.begin(); + VerifyOrReturn(CheckNextListItemDecodes("DSTOffset", iter_0, 0)); + VerifyOrReturn(CheckValue("DSTOffset[0].offset", iter_0.GetValue().offset, 1L)); + VerifyOrReturn(CheckValue("DSTOffset[0].validStarting", iter_0.GetValue().validStarting, 1ULL)); + VerifyOrReturn(CheckValueNonNull("DSTOffset[0].validUntil", iter_0.GetValue().validUntil)); + VerifyOrReturn(CheckValue("DSTOffset[0].validUntil.Value()", iter_0.GetValue().validUntil.Value(), 2ULL)); + VerifyOrReturn(CheckNextListItemDecodes("DSTOffset", iter_0, 1)); + VerifyOrReturn(CheckValue("DSTOffset[1].offset", iter_0.GetValue().offset, 0L)); + VerifyOrReturn(CheckValue("DSTOffset[1].validStarting", iter_0.GetValue().validStarting, 3ULL)); + VerifyOrReturn(CheckValueNull("DSTOffset[1].validUntil", iter_0.GetValue().validUntil)); + VerifyOrReturn(CheckNoMoreListItems("DSTOffset", iter_0, 2)); + } + } + break; + case 19: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_CONSTRAINT_ERROR)); + break; + case 20: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_RESOURCE_EXHAUSTED)); + break; + default: + LogErrorOnFailure(ContinueOnChipMainThread(CHIP_ERROR_INVALID_ARGUMENT)); + } + + if (shouldContinue) + { + ContinueOnChipMainThread(CHIP_NO_ERROR); + } + } + + CHIP_ERROR DoTestStep(uint16_t testIndex) override + { + using namespace chip::app::Clusters; + switch (testIndex) + { + case 0: { + LogStep(0, "Wait for the commissioned device to be retrieved"); + ListFreer listFreer; + chip::app::Clusters::DelayCommands::Commands::WaitForCommissionee::Type value; + value.nodeId = mNodeId.HasValue() ? mNodeId.Value() : 305414945ULL; + return WaitForCommissionee(kIdentityAlpha, value); + } + case 1: { + LogStep(1, "Read Time Zone"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Attributes::TimeZone::Id, true, chip::NullOptional); + } + case 2: { + LogStep(2, "Set Time Zone list"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::Type value; + + { + auto * listHolder_0 = new ListHolder(1); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 100L; + listHolder_0->mList[0].validAt = 0ULL; + listHolder_0->mList[0].name.Emplace(); + listHolder_0->mList[0].name.Value() = chip::Span("CETgarbage: not in length on purpose", 3); + + value.timeZone = + chip::app::DataModel::List( + listHolder_0->mList, 1); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetTimeZone::Id, value, chip::NullOptional + + ); + } + case 3: { + LogStep(3, "Read Time Zone"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Attributes::TimeZone::Id, true, chip::NullOptional); + } + case 4: { + LogStep(4, "Set Time Zone with missing optional name field"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::Type value; + + { + auto * listHolder_0 = new ListHolder(1); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 100L; + listHolder_0->mList[0].validAt = 0ULL; + + value.timeZone = + chip::app::DataModel::List( + listHolder_0->mList, 1); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetTimeZone::Id, value, chip::NullOptional + + ); + } + case 5: { + LogStep(5, "Read Time Zone"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Attributes::TimeZone::Id, true, chip::NullOptional); + } + case 6: { + LogStep(6, "Set Time Zone with very long name"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::Type value; + + { + auto * listHolder_0 = new ListHolder(1); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 100L; + listHolder_0->mList[0].validAt = 0ULL; + listHolder_0->mList[0].name.Emplace(); + listHolder_0->mList[0].name.Value() = chip::Span( + "MunichOnTheLongRiverOfIsarInNiceSummerWeatherWithAugustinerBeerssgarbage: not in length on purpose", 65); + + value.timeZone = + chip::app::DataModel::List( + listHolder_0->mList, 1); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetTimeZone::Id, value, chip::NullOptional + + ); + } + case 7: { + LogStep(7, "Set Time Zone with first item validAt not zero"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::Type value; + + { + auto * listHolder_0 = new ListHolder(1); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 100L; + listHolder_0->mList[0].validAt = 1ULL; + listHolder_0->mList[0].name.Emplace(); + listHolder_0->mList[0].name.Value() = chip::Span("CDTgarbage: not in length on purpose", 3); + + value.timeZone = + chip::app::DataModel::List( + listHolder_0->mList, 1); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetTimeZone::Id, value, chip::NullOptional + + ); + } + case 8: { + LogStep(8, "Set Time Zone with second item validAt zero"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::Type value; + + { + auto * listHolder_0 = new ListHolder(2); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 100L; + listHolder_0->mList[0].validAt = 0ULL; + listHolder_0->mList[0].name.Emplace(); + listHolder_0->mList[0].name.Value() = chip::Span("CSTgarbage: not in length on purpose", 3); + + listHolder_0->mList[1].offset = 200L; + listHolder_0->mList[1].validAt = 0ULL; + listHolder_0->mList[1].name.Emplace(); + listHolder_0->mList[1].name.Value() = chip::Span("CDTgarbage: not in length on purpose", 3); + + value.timeZone = + chip::app::DataModel::List( + listHolder_0->mList, 2); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetTimeZone::Id, value, chip::NullOptional + + ); + } + case 9: { + LogStep(9, "Set Time Zone with more than supported list count"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::Type value; + + { + auto * listHolder_0 = new ListHolder(3); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 100L; + listHolder_0->mList[0].validAt = 0ULL; + listHolder_0->mList[0].name.Emplace(); + listHolder_0->mList[0].name.Value() = chip::Span("CSTgarbage: not in length on purpose", 3); + + listHolder_0->mList[1].offset = 200L; + listHolder_0->mList[1].validAt = 1ULL; + listHolder_0->mList[1].name.Emplace(); + listHolder_0->mList[1].name.Value() = chip::Span("CDTgarbage: not in length on purpose", 3); + + listHolder_0->mList[2].offset = 200L; + listHolder_0->mList[2].validAt = 2ULL; + listHolder_0->mList[2].name.Emplace(); + listHolder_0->mList[2].name.Value() = chip::Span("CETgarbage: not in length on purpose", 3); + + value.timeZone = + chip::app::DataModel::List( + listHolder_0->mList, 3); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetTimeZone::Id, value, chip::NullOptional + + ); + } + case 10: { + LogStep(10, "Set Time Zone empty"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::Type value; + + value.timeZone = chip::app::DataModel::List(); + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetTimeZone::Id, value, chip::NullOptional + + ); + } + case 11: { + LogStep(11, "Read Time Zone"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Attributes::TimeZone::Id, true, chip::NullOptional); + } + case 12: { + LogStep(12, "Read DSTOffset"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Attributes::DSTOffset::Id, true, chip::NullOptional); + } + case 13: { + LogStep(13, "Set DSTOffset single item"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::Type value; + + { + auto * listHolder_0 = new ListHolder(1); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 1L; + listHolder_0->mList[0].validStarting = 1ULL; + listHolder_0->mList[0].validUntil.SetNonNull(); + listHolder_0->mList[0].validUntil.Value() = 2ULL; + + value.DSTOffset = + chip::app::DataModel::List( + listHolder_0->mList, 1); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetDSTOffset::Id, value, chip::NullOptional + + ); + } + case 14: { + LogStep(14, "Set DSTOffset empty"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::Type value; + + value.DSTOffset = + chip::app::DataModel::List(); + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetDSTOffset::Id, value, chip::NullOptional + + ); + } + case 15: { + LogStep(15, "Set DSTOffset with more than 1 null value"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::Type value; + + { + auto * listHolder_0 = new ListHolder(2); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 1L; + listHolder_0->mList[0].validStarting = 1ULL; + listHolder_0->mList[0].validUntil.SetNull(); + + listHolder_0->mList[1].offset = 0L; + listHolder_0->mList[1].validStarting = 2ULL; + listHolder_0->mList[1].validUntil.SetNull(); + + value.DSTOffset = + chip::app::DataModel::List( + listHolder_0->mList, 2); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetDSTOffset::Id, value, chip::NullOptional + + ); + } + case 16: { + LogStep(16, "Set unsorted DSTOffset entries"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::Type value; + + { + auto * listHolder_0 = new ListHolder(2); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 1L; + listHolder_0->mList[0].validStarting = 2ULL; + listHolder_0->mList[0].validUntil.SetNonNull(); + listHolder_0->mList[0].validUntil.Value() = 3ULL; + + listHolder_0->mList[1].offset = 0L; + listHolder_0->mList[1].validStarting = 1ULL; + listHolder_0->mList[1].validUntil.SetNull(); + + value.DSTOffset = + chip::app::DataModel::List( + listHolder_0->mList, 2); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetDSTOffset::Id, value, chip::NullOptional + + ); + } + case 17: { + LogStep(17, "Set DSTOffset with multiple entries"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::Type value; + + { + auto * listHolder_0 = new ListHolder(2); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 1L; + listHolder_0->mList[0].validStarting = 1ULL; + listHolder_0->mList[0].validUntil.SetNonNull(); + listHolder_0->mList[0].validUntil.Value() = 2ULL; + + listHolder_0->mList[1].offset = 0L; + listHolder_0->mList[1].validStarting = 3ULL; + listHolder_0->mList[1].validUntil.SetNull(); + + value.DSTOffset = + chip::app::DataModel::List( + listHolder_0->mList, 2); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetDSTOffset::Id, value, chip::NullOptional + + ); + } + case 18: { + LogStep(18, "Read DSTOffset"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Attributes::DSTOffset::Id, true, chip::NullOptional); + } + case 19: { + LogStep(19, "Set DSTOffset with same validStarting and validUntil"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::Type value; + + { + auto * listHolder_0 = new ListHolder(1); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 0L; + listHolder_0->mList[0].validStarting = 1ULL; + listHolder_0->mList[0].validUntil.SetNonNull(); + listHolder_0->mList[0].validUntil.Value() = 1ULL; + + value.DSTOffset = + chip::app::DataModel::List( + listHolder_0->mList, 1); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetDSTOffset::Id, value, chip::NullOptional + + ); + } + case 20: { + LogStep(20, "Set DSTOffset with more than supported list count"); + ListFreer listFreer; + chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::Type value; + + { + auto * listHolder_0 = new ListHolder(3); + listFreer.add(listHolder_0); + + listHolder_0->mList[0].offset = 1L; + listHolder_0->mList[0].validStarting = 1ULL; + listHolder_0->mList[0].validUntil.SetNonNull(); + listHolder_0->mList[0].validUntil.Value() = 2ULL; + + listHolder_0->mList[1].offset = 0L; + listHolder_0->mList[1].validStarting = 3ULL; + listHolder_0->mList[1].validUntil.SetNonNull(); + listHolder_0->mList[1].validUntil.Value() = 5ULL; + + listHolder_0->mList[2].offset = 0L; + listHolder_0->mList[2].validStarting = 6ULL; + listHolder_0->mList[2].validUntil.SetNull(); + + value.DSTOffset = + chip::app::DataModel::List( + listHolder_0->mList, 3); + } + return SendCommand(kIdentityAlpha, GetEndpoint(0), TimeSynchronization::Id, + TimeSynchronization::Commands::SetDSTOffset::Id, value, chip::NullOptional + + ); + } + } + return CHIP_NO_ERROR; + } +}; + class TestMultiAdminSuite : public TestCommand { public: @@ -134863,6 +135456,7 @@ void registerCommandsTests(Commands & commands, CredentialIssuerCommands * creds make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), diff --git a/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h index 4ddc2e17fccfc2..88a07202867a62 100644 --- a/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h +++ b/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h @@ -128495,7 +128495,7 @@ class TestDescriptorCluster : public TestCommandBridge { { id actualValue = value; - VerifyOrReturn(CheckValue("ServerList", [actualValue count], static_cast(28))); + VerifyOrReturn(CheckValue("ServerList", [actualValue count], static_cast(29))); VerifyOrReturn(CheckValue("", actualValue[0], 3UL)); VerifyOrReturn(CheckValue("", actualValue[1], 4UL)); VerifyOrReturn(CheckValue("", actualValue[2], 29UL)); @@ -128516,14 +128516,15 @@ class TestDescriptorCluster : public TestCommandBridge { VerifyOrReturn(CheckValue("", actualValue[17], 53UL)); VerifyOrReturn(CheckValue("", actualValue[18], 54UL)); VerifyOrReturn(CheckValue("", actualValue[19], 55UL)); - VerifyOrReturn(CheckValue("", actualValue[20], 60UL)); - VerifyOrReturn(CheckValue("", actualValue[21], 62UL)); - VerifyOrReturn(CheckValue("", actualValue[22], 63UL)); - VerifyOrReturn(CheckValue("", actualValue[23], 64UL)); - VerifyOrReturn(CheckValue("", actualValue[24], 65UL)); - VerifyOrReturn(CheckValue("", actualValue[25], 70UL)); - VerifyOrReturn(CheckValue("", actualValue[26], 1029UL)); - VerifyOrReturn(CheckValue("", actualValue[27], 4294048774UL)); + VerifyOrReturn(CheckValue("", actualValue[20], 56UL)); + VerifyOrReturn(CheckValue("", actualValue[21], 60UL)); + VerifyOrReturn(CheckValue("", actualValue[22], 62UL)); + VerifyOrReturn(CheckValue("", actualValue[23], 63UL)); + VerifyOrReturn(CheckValue("", actualValue[24], 64UL)); + VerifyOrReturn(CheckValue("", actualValue[25], 65UL)); + VerifyOrReturn(CheckValue("", actualValue[26], 70UL)); + VerifyOrReturn(CheckValue("", actualValue[27], 1029UL)); + VerifyOrReturn(CheckValue("", actualValue[28], 4294048774UL)); } NextTest();