blob: 12e588b0424e1107429a6112a5adcbdb879e1d38 [file] [log] [blame]
#
# 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.
#
import ipaddress
from datetime import timedelta
import chip.clusters as Clusters
from chip.clusters.Types import NullValue
from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main, utc_time_in_matter_epoch
from mobly import asserts
class TC_TIMESYNC_2_1(MatterBaseTest):
async def read_ts_attribute_expect_success(self, endpoint, attribute):
cluster = Clusters.Objects.TimeSynchronization
return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute)
def pics_TC_TIMESYNC_2_1(self) -> list[str]:
return ["TIMESYNC.S"]
@async_test_body
async def test_TC_TIMESYNC_2_1(self):
endpoint = self.user_params.get("endpoint", 0)
self.print_step(1, "Commissioning, already done")
attributes = Clusters.TimeSynchronization.Attributes
self.print_step(2, "Read Granularity attribute")
if self.check_pics("TIMESYNC.S.A0001"):
granularity_dut = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.Granularity)
asserts.assert_less(granularity_dut, Clusters.TimeSynchronization.Enums.GranularityEnum.kUnknownEnumValue,
"Granularity is not in valid range")
else:
asserts.assert_true(False, "Granularity is a mandatory attribute and must be present in the PICS file")
self.print_step(3, "Read TimeSource")
if self.check_pics("TIMESYNC.S.A0002"):
time_source = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.TimeSource)
asserts.assert_less(time_source, Clusters.TimeSynchronization.Enums.TimeSourceEnum.kUnknownEnumValue,
"TimeSource is not in valid range")
self.print_step(4, "Read TrustedTimeSource")
if self.check_pics("TIMESYNC.S.A0003"):
trusted_time_source = await self.read_ts_attribute_expect_success(endpoint=endpoint,
attribute=attributes.TrustedTimeSource)
if trusted_time_source is not NullValue:
asserts.assert_less_equal(trusted_time_source.fabricIndex, 0xFE,
"FabricIndex for the TrustedTimeSource is out of range")
asserts.assert_greater_equal(trusted_time_source.fabricIndex, 1,
"FabricIndex for the TrustedTimeSource is out of range")
elif self.check_pics("TIMESYNC.S.F03"):
asserts.assert_true(False, "TrustedTimeSource is mandatory if the TSC feature (TIMESYNC.S.F03) is supported")
self.print_step(5, "Read DefaultNTP")
if self.check_pics("TIMESYNC.S.A0004"):
default_ntp = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.DefaultNTP)
if default_ntp is not NullValue:
asserts.assert_less_equal(len(default_ntp), 128, "DefaultNTP length must be less than 128")
# Assume this is a valid web address if it has at least one . in the name
is_web_addr = default_ntp.find('.') != -1
try:
ipaddress.IPv6Address(default_ntp)
is_ip_addr = True
except ipaddress.AddressValueError:
is_ip_addr = False
pass
asserts.assert_true(is_web_addr or is_ip_addr, "Returned DefaultNTP value is not a IP address or web address")
elif self.check_pics("TIMESYNC.S.F01"):
asserts.assert_true(False, "DefaultNTP is mandatory if the NTPC (TIMESYNC.S.F01) feature is supported")
self.print_step(6, "Read TimeZone")
if self.check_pics("TIMESYNC.S.A0005"):
tz_dut = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.TimeZone)
asserts.assert_greater_equal(len(tz_dut), 1, "TimeZone must have at least one entry in the list")
asserts.assert_less_equal(len(tz_dut), 2, "TimeZone may have a maximum of two entries in the list")
for entry in tz_dut:
asserts.assert_greater_equal(entry.offset, -43200, "TimeZone offset is out of range")
asserts.assert_less_equal(entry.offset, 50400, "TimeZone offset is out of range")
if entry.name:
asserts.assert_less_equal(len(entry.name), 64, "TimeZone name is too long")
asserts.assert_equal(tz_dut[0].validAt, 0, "TimeZone list first entry must have a 0 ValidAt time")
if len(tz_dut) > 1:
asserts.assert_not_equal(tz_dut[1].validAt, 0, "TimeZone list second entry must have a non-zero ValidAt time")
elif self.check_pics("TIMESYNC.S.F00"):
asserts.assert_true(False, "TimeZone is mandatory if the TZ (TIMESYNC.S.F00) feature is supported")
self.print_step(7, "Read DSTOffset")
if self.check_pics("TIMESYNC.S.A0006"):
dst_dut = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.DSTOffset)
last_valid_until = -1
last_valid_starting = -1
for dst in dst_dut:
asserts.assert_greater(dst.validStarting, last_valid_starting,
"DSTOffset list must be sorted by ValidStarting time")
last_valid_starting = dst.validStarting
asserts.assert_greater_equal(dst.validStarting, last_valid_until,
"DSTOffset list must have every ValidStarting > ValidUntil of the previous entry")
last_valid_until = dst.validUntil
if dst.validUntil is NullValue or dst.validUntil is None:
asserts.assert_equal(dst, dst_dut[-1], "DSTOffset list must have Null ValidUntil at the end")
elif self.check_pics("TIMESYNC.S.F00"):
asserts.assert_true(False, "DSTOffset is mandatory if the TZ (TIMESYNC.S.F00) feature is supported")
self.print_step(8, "Read UTCTime")
if self.check_pics("TIMESYNC.S.A0000"):
utc_dut = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.UTCTime)
if utc_dut is NullValue:
asserts.assert_equal(granularity_dut, Clusters.TimeSynchronization.Enums.GranularityEnum.kNoTimeGranularity)
else:
asserts.assert_not_equal(granularity_dut, Clusters.TimeSynchronization.Enums.GranularityEnum.kNoTimeGranularity)
if granularity_dut is Clusters.TimeSynchronization.Enums.GranularityEnum.kMinutesGranularity:
toleranace = timedelta(minutes=10)
else:
toleranace = timedelta(minutes=1)
delta_us = abs(utc_dut - utc_time_in_matter_epoch())
delta = timedelta(microseconds=delta_us)
asserts.assert_less_equal(delta, toleranace, "UTC time is not within tolerance of TH")
else:
asserts.assert_true(False, "UTCTime is a mandatory attribute and must be present in the PICS file")
self.print_step(9, "Read LocalTime")
if self.check_pics("TIMESYNC.S.A0007"):
utc_dut = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.UTCTime)
local_dut = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.LocalTime)
if utc_dut is NullValue:
asserts.assert_true(local_dut is NullValue, "LocalTime must be Null if UTC time is Null")
elif len(dst_dut) == 0:
asserts.assert_true(local_dut is NullValue, "LocalTime must be Null if the DST table is empty")
else:
local_calculated = utc_dut + dst_dut[0].offset + tz_dut[0].offset
delta_us = abs(local_dut, local_calculated)
delta = timedelta(microseconds=delta_us)
toleranace = timedelta(minutes=1)
asserts.assert_less_equal(delta, toleranace, "Local time caluclation is not within tolerance of calculated value")
elif self.check_pics("TIMESYNC.S.F00"):
asserts.assert_true(False, "LocalTime is mandatory if the TZ (TIMESYNC.S.F00) feature is supported")
self.print_step(10, "Read TimeZoneDatabase")
if self.check_pics("TIMESYNC.S.A0008"):
tz_db_dut = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.TimeZoneDatabase)
asserts.assert_less(tz_db_dut, Clusters.TimeSynchronization.Enums.TimeZoneDatabaseEnum.kUnknownEnumValue,
"TimeZoneDatabase is not in valid range")
elif self.check_pics("TIMESYNC.S.F00"):
asserts.assert_true(False, "TimeZoneDatabase is mandatory if the TZ (TIMESYNC.S.F00) feature is supported")
self.print_step(11, "Read NTPServerAvailable")
if self.check_pics("TIMESYNC.S.A0009"):
# bool typechecking happens in the test read functions, so all we need to do here is do the read
await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.NTPServerAvailable)
elif self.check_pics("TIMESYNC.S.F02"):
asserts.assert_true(False, "NTPServerAvailable is mandatory if the NTPS (TIMESYNC.S.F02) feature is supported")
self.print_step(12, "Read TimeZoneListMaxSize")
if self.check_pics("TIMESYNC.S.A000a"):
size = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.TimeZoneListMaxSize)
asserts.assert_greater_equal(size, 1, "TimeZoneListMaxSize must be at least 1")
asserts.assert_less_equal(size, 2, "TimeZoneListMaxSize must be max 2")
elif self.check_pics("TIMESYNC.S.F00"):
asserts.assert_true(False, "TimeZoneListMaxSize is mandatory if the TZ (TIMESYNC.S.F00) feature is supported")
self.print_step(13, "Read DSTOffsetListMaxSize")
if self.check_pics("TIMESYNC.S.A000b"):
size = await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.DSTOffsetListMaxSize)
asserts.assert_greater_equal(size, 1, "DSTOffsetListMaxSize must be at least 1")
elif self.check_pics("TIMESYNC.S.F00"):
asserts.assert_true(False, "DSTOffsetListMaxSize is mandatory if the TZ (TIMESYNC.S.F00) feature is supported")
self.print_step(14, "Read SupportsDNSResolve")
if self.check_pics("TIMESYNC.S.A0004"):
# bool typechecking happens in the test read functions, so all we need to do here is do the read
await self.read_ts_attribute_expect_success(endpoint=endpoint, attribute=attributes.SupportsDNSResolve)
elif self.check_pics("TIMESYNC.S.F01"):
asserts.assert_true(False, "SupportsDNSResolve is mandatory if the NTPC (TIMESYNC.S.F01) feature is supported")
if __name__ == "__main__":
default_matter_test_main()