Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cdae19b

Browse files
committedOct 24, 2023
Set a timezone and DST offsets during commissioning on Darwin.
This will set things up right if the commissionee implements the Time Synchronization cluster. Fixes #29768
1 parent 0e67c82 commit cdae19b

File tree

8 files changed

+158
-20
lines changed

8 files changed

+158
-20
lines changed
 

‎.github/workflows/darwin.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ jobs:
112112
# but to instrument the code in the underlying libCHIP we need to pass CHIP_IS_UBSAN=YES
113113
TEST_RUNNER_ASAN_OPTIONS=__CURRENT_VALUE__:detect_stack_use_after_return=1 xcodebuild test -target "Matter" -scheme "Matter Framework Tests" -sdk macosx -enableAddressSanitizer YES -enableUndefinedBehaviorSanitizer YES OTHER_CFLAGS='${inherited} -Werror -Wconversion' CHIP_IS_UBSAN=YES CHIP_IS_BLE=NO GCC_PREPROCESSOR_DEFINITIONS='${inherited} MTR_NO_AVAILABILITY=1'> >(tee /tmp/darwin/framework-tests/darwin-tests-asan.log) 2> >(tee /tmp/darwin/framework-tests/darwin-tests-asan-err.log >&2)
114114
# And the same thing, but with MTR_PER_CONTROLLER_STORAGE_ENABLED turned on.
115-
TEST_RUNNER_ASAN_OPTIONS=__CURRENT_VALUE__:detect_stack_use_after_return=1 xcodebuild test -target "Matter" -scheme "Matter Framework Tests" -sdk macosx -enableAddressSanitizer YES -enableUndefinedBehaviorSanitizer YES OTHER_CFLAGS='${inherited} -Werror -Wconversion' CHIP_IS_UBSAN=YES CHIP_IS_BLE=NO GCC_PREPROCESSOR_DEFINITIONS='${inherited} MTR_NO_AVAILABILITY=1 MTR_PER_CONTROLLER_STORAGE_ENABLED=1' > >(tee /tmp/darwin/framework-tests/darwin-tests-asan-provisional.log) 2> >(tee /tmp/darwin/framework-tests/darwin-tests-asan-provisional-err.log >&2)
115+
TEST_RUNNER_ASAN_OPTIONS=__CURRENT_VALUE__:detect_stack_use_after_return=1 xcodebuild test -target "Matter" -scheme "Matter Framework Tests" -sdk macosx -enableAddressSanitizer YES -enableUndefinedBehaviorSanitizer YES OTHER_CFLAGS='${inherited} -Werror -Wconversion' CHIP_IS_UBSAN=YES CHIP_IS_BLE=NO GCC_PREPROCESSOR_DEFINITIONS='${inherited} MTR_NO_AVAILABILITY=1 MTR_PER_CONTROLLER_STORAGE_ENABLED=1' > >(tee /tmp/darwin/framework-tests/darwin-tests-asan-controller-storage.log) 2> >(tee /tmp/darwin/framework-tests/darwin-tests-asan-controller-storage-err.log >&2)
116+
# And the same thing, but with MTR_ENABLE_PROVISIONAL also turned on.
117+
TEST_RUNNER_ASAN_OPTIONS=__CURRENT_VALUE__:detect_stack_use_after_return=1 xcodebuild test -target "Matter" -scheme "Matter Framework Tests" -sdk macosx -enableAddressSanitizer YES -enableUndefinedBehaviorSanitizer YES OTHER_CFLAGS='${inherited} -Werror -Wconversion' CHIP_IS_UBSAN=YES CHIP_IS_BLE=NO GCC_PREPROCESSOR_DEFINITIONS='${inherited} MTR_NO_AVAILABILITY=1 MTR_PER_CONTROLLER_STORAGE_ENABLED=1 MTR_ENABLE_PROVISIONAL=1' > >(tee /tmp/darwin/framework-tests/darwin-tests-asan-provisional.log) 2> >(tee /tmp/darwin/framework-tests/darwin-tests-asan-provisional-err.log >&2)
116118
# And the same thing, but with MTR_NO_AVAILABILITY not turned on. This requires -Wno-unguarded-availability-new to avoid availability errors.
117119
TEST_RUNNER_ASAN_OPTIONS=__CURRENT_VALUE__:detect_stack_use_after_return=1 xcodebuild test -target "Matter" -scheme "Matter Framework Tests" -sdk macosx -enableAddressSanitizer YES -enableUndefinedBehaviorSanitizer YES OTHER_CFLAGS='${inherited} -Werror -Wconversion -Wno-unguarded-availability-new' CHIP_IS_UBSAN=YES CHIP_IS_BLE=NO GCC_PREPROCESSOR_DEFINITIONS='${inherited}' > >(tee /tmp/darwin/framework-tests/darwin-tests-asan-with-availability-annotations.log) 2> >(tee /tmp/darwin/framework-tests/darwin-tests-asan-with-availability-annotations-err.log >&2)
118120
# -enableThreadSanitizer instruments the code in Matter.framework,

‎src/controller/AutoCommissioner.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ CHIP_ERROR AutoCommissioner::SetCommissioningParameters(const CommissioningParam
201201
mTimeZoneBuf[i].name.SetValue(span);
202202
}
203203
}
204+
auto list = app::DataModel::List<app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type>(mTimeZoneBuf, size);
205+
mParams.SetTimeZone(list);
204206
}
205207

206208
return CHIP_NO_ERROR;

‎src/darwin/Framework/CHIP/MTRCertificateInfo.mm

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ - (MTRDistinguishedNameInfo *)subject
6262

6363
- (NSDate *)notBefore
6464
{
65-
return ChipEpochSecondsAsDate(_data.mNotBeforeTime);
65+
return MatterEpochSecondsAsDate(_data.mNotBeforeTime);
6666
}
6767

6868
- (NSDate *)notAfter
6969
{
7070
// "no expiry" is encoded as kNullCertTime (see ChipEpochToASN1Time)
71-
return (_data.mNotAfterTime != kNullCertTime) ? ChipEpochSecondsAsDate(_data.mNotAfterTime) : NSDate.distantFuture;
71+
return (_data.mNotAfterTime != kNullCertTime) ? MatterEpochSecondsAsDate(_data.mNotAfterTime) : NSDate.distantFuture;
7272
}
7373

7474
- (id)copyWithZone:(nullable NSZone *)zone

‎src/darwin/Framework/CHIP/MTRConversion.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,17 @@ AsNumber(chip::Optional<T> optional)
3535
return (optional.HasValue()) ? @(optional.Value()) : nil;
3636
}
3737

38-
inline NSDate * ChipEpochSecondsAsDate(uint32_t chipEpochSeconds)
38+
inline NSDate * MatterEpochSecondsAsDate(uint32_t matterEpochSeconds)
3939
{
40-
return [NSDate dateWithTimeIntervalSince1970:(chip::kChipEpochSecondsSinceUnixEpoch + (NSTimeInterval) chipEpochSeconds)];
40+
return [NSDate dateWithTimeIntervalSince1970:(chip::kChipEpochSecondsSinceUnixEpoch + (NSTimeInterval) matterEpochSeconds)];
4141
}
4242

43+
/**
44+
* Returns whether the conversion could be performed. Will return false if the
45+
* passed-in date is our of the range representable as a Matter epoch-s value.
46+
*/
47+
bool DateToMatterEpochSeconds(NSDate * date, uint32_t & epoch);
48+
4349
/**
4450
* Utilities for converting between NSSet<NSNumber *> and chip::CATValues.
4551
*/

‎src/darwin/Framework/CHIP/MTRConversion.mm

+19
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#import "MTRLogging_Internal.h"
1919

2020
#include <lib/support/SafeInt.h>
21+
#include <lib/support/TimeUtils.h>
2122

2223
CHIP_ERROR SetToCATValues(NSSet<NSNumber *> * catSet, chip::CATValues & values)
2324
{
@@ -59,3 +60,21 @@ CHIP_ERROR SetToCATValues(NSSet<NSNumber *> * catSet, chip::CATValues & values)
5960
}
6061
return [NSSet setWithSet:catSet];
6162
}
63+
64+
bool DateToMatterEpochSeconds(NSDate * date, uint32_t & matterEpochSeconds)
65+
{
66+
NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
67+
NSDateComponents * components = [calendar componentsInTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] fromDate:date];
68+
69+
if (!chip::CanCastTo<uint16_t>(components.year)) {
70+
return false;
71+
}
72+
73+
uint16_t year = static_cast<uint16_t>([components year]);
74+
uint8_t month = static_cast<uint8_t>([components month]);
75+
uint8_t day = static_cast<uint8_t>([components day]);
76+
uint8_t hour = static_cast<uint8_t>([components hour]);
77+
uint8_t minute = static_cast<uint8_t>([components minute]);
78+
uint8_t second = static_cast<uint8_t>([components second]);
79+
return chip::CalendarToChipEpochTime(year, month, day, hour, minute, second, matterEpochSeconds);
80+
}

‎src/darwin/Framework/CHIP/MTRDeviceController.mm

+53
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353

5454
#include <platform/CHIPDeviceBuildConfig.h>
5555

56+
#include <app-common/zap-generated/cluster-objects.h>
57+
#include <app/data-model/List.h>
5658
#include <controller/CHIPDeviceController.h>
5759
#include <controller/CHIPDeviceControllerFactory.h>
5860
#include <controller/CommissioningWindowOpener.h>
@@ -679,6 +681,57 @@ - (BOOL)commissionNodeWithID:(NSNumber *)nodeID
679681
params.SetCountryCode(AsCharSpan(commissioningParams.countryCode));
680682
}
681683

684+
// Set up the right timezone and DST information. For timezone, just
685+
// use our current timezone and don't schedule any sort of timezone
686+
// change.
687+
auto * tz = [NSTimeZone localTimeZone];
688+
using TimeZoneType = chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type;
689+
TimeZoneType timeZone;
690+
timeZone.validAt = 0;
691+
timeZone.offset = static_cast<int32_t>(tz.secondsFromGMT - tz.daylightSavingTimeOffset);
692+
timeZone.name.Emplace(AsCharSpan(tz.name));
693+
694+
params.SetTimeZone(chip::app::DataModel::List<TimeZoneType>(&timeZone, 1));
695+
696+
// For DST, there is no limit to the number of transitions we could try
697+
// to add, but in practice devices likely support only 2 and
698+
// AutoCommissioner caps the list at 10. Let's do up to 4 transitions
699+
// for now.
700+
using DSTOffsetType = chip::app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::Type;
701+
702+
DSTOffsetType dstOffsets[4];
703+
size_t dstOffsetCount = 0;
704+
auto nextOffset = tz.daylightSavingTimeOffset;
705+
uint64_t nextValidStarting = 0;
706+
auto * nextTransition = tz.nextDaylightSavingTimeTransition;
707+
for (auto & dstOffset : dstOffsets) {
708+
++dstOffsetCount;
709+
dstOffset.offset = static_cast<int32_t>(nextOffset);
710+
dstOffset.validStarting = nextValidStarting;
711+
if (nextTransition != nil) {
712+
uint32_t transitionEpochS;
713+
if (DateToMatterEpochSeconds(nextTransition, transitionEpochS)) {
714+
using Microseconds64 = chip::System::Clock::Microseconds64;
715+
using Seconds32 = chip::System::Clock::Seconds32;
716+
dstOffset.validUntil.SetNonNull(Microseconds64(Seconds32(transitionEpochS)).count());
717+
} else {
718+
// Out of range; treat as "forever".
719+
dstOffset.validUntil.SetNull();
720+
}
721+
} else {
722+
dstOffset.validUntil.SetNull();
723+
}
724+
725+
if (dstOffset.validUntil.IsNull()) {
726+
break;
727+
}
728+
729+
nextOffset = [tz daylightSavingTimeOffsetForDate:nextTransition];
730+
nextValidStarting = dstOffset.validUntil.Value();
731+
nextTransition = [tz nextDaylightSavingTimeTransitionAfterDate:nextTransition];
732+
}
733+
params.SetDSTOffsets(chip::app::DataModel::List<DSTOffsetType>(dstOffsets, dstOffsetCount));
734+
682735
chip::NodeId deviceId = [nodeID unsignedLongLongValue];
683736
self->_operationalCredentialsDelegate->SetDeviceID(deviceId);
684737
auto errorCode = self.cppCommissioner->Commission(deviceId, params);

‎src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm

+4-15
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535
#include <lib/core/Optional.h>
3636
#include <lib/core/TLV.h>
3737
#include <lib/support/PersistentStorageMacros.h>
38-
#include <lib/support/SafeInt.h>
39-
#include <lib/support/TimeUtils.h>
4038
#include <platform/LockTracker.h>
4139

4240
using namespace chip;
@@ -322,21 +320,12 @@
322320

323321
bool MTROperationalCredentialsDelegate::ToChipEpochTime(NSDate * date, uint32_t & epoch)
324322
{
325-
NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
326-
NSDateComponents * components = [calendar componentsInTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] fromDate:date];
327-
328-
if (CanCastTo<uint16_t>(components.year)) {
329-
uint16_t year = static_cast<uint16_t>([components year]);
330-
uint8_t month = static_cast<uint8_t>([components month]);
331-
uint8_t day = static_cast<uint8_t>([components day]);
332-
uint8_t hour = static_cast<uint8_t>([components hour]);
333-
uint8_t minute = static_cast<uint8_t>([components minute]);
334-
uint8_t second = static_cast<uint8_t>([components second]);
335-
if (chip::CalendarToChipEpochTime(year, month, day, hour, minute, second, epoch)) {
336-
return true;
337-
}
323+
if (DateToMatterEpochSeconds(date, epoch)) {
324+
return true;
338325
}
339326

327+
NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
328+
NSDateComponents * components = [calendar componentsInTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] fromDate:date];
340329
MTR_LOG_ERROR(
341330
"Year %lu is out of range for Matter epoch time. Please use [NSDate distantFuture] to represent \"never expires\".",
342331
static_cast<unsigned long>(components.year));

‎src/darwin/Framework/CHIPTests/MTRDeviceTests.m

+67
Original file line numberDiff line numberDiff line change
@@ -2538,6 +2538,73 @@ - (void)test027_AttestationChallenge
25382538
[self waitForExpectations:@[ attestationRequestedViaDevice ] timeout:kTimeoutInSeconds];
25392539
}
25402540

2541+
- (void)test028_TimeZoneAndDST
2542+
{
2543+
// Time synchronization is marked provisional so far, so we can only test it
2544+
// when MTR_ENABLE_PROVISIONAL is set.
2545+
#if MTR_ENABLE_PROVISIONAL
2546+
dispatch_queue_t queue = dispatch_get_main_queue();
2547+
2548+
__auto_type * device = GetConnectedDevice();
2549+
__auto_type * cluster = [[MTRBaseClusterTimeSynchronization alloc] initWithDevice:device endpointID:@(0) queue:queue];
2550+
2551+
XCTestExpectation * readTimeZoneExpectation = [self expectationWithDescription:@"Read TimeZone attribute"];
2552+
__block NSArray<MTRTimeSynchronizationClusterTimeZoneStruct *> * timeZone;
2553+
[cluster readAttributeTimeZoneWithCompletion:^(NSArray * _Nullable value, NSError * _Nullable error) {
2554+
XCTAssertNil(error);
2555+
timeZone = value;
2556+
[readTimeZoneExpectation fulfill];
2557+
}];
2558+
2559+
[self waitForExpectations:@[ readTimeZoneExpectation ] timeout:kTimeoutInSeconds];
2560+
2561+
__block NSArray<MTRTimeSynchronizationClusterDSTOffsetStruct *> * dstOffset;
2562+
XCTestExpectation * readDSTOffsetExpectation = [self expectationWithDescription:@"Read DSTOffset attribute"];
2563+
[cluster readAttributeDSTOffsetWithCompletion:^(NSArray * _Nullable value, NSError * _Nullable error) {
2564+
XCTAssertNil(error);
2565+
dstOffset = value;
2566+
[readDSTOffsetExpectation fulfill];
2567+
}];
2568+
2569+
[self waitForExpectations:@[ readDSTOffsetExpectation ] timeout:kTimeoutInSeconds];
2570+
2571+
// Check that the first DST offset entry matches what we expect. If we
2572+
// happened to cross a DST boundary during execution of this function, some
2573+
// of these checks will fail, but that seems pretty low-probability.
2574+
2575+
XCTAssertTrue(dstOffset.count > 0);
2576+
MTRTimeSynchronizationClusterDSTOffsetStruct * currentDSTOffset = dstOffset[0];
2577+
2578+
__auto_type * utcTz = [NSTimeZone timeZoneForSecondsFromGMT:0];
2579+
__auto_type * dateComponents = [[NSDateComponents alloc] init];
2580+
dateComponents.timeZone = utcTz;
2581+
dateComponents.year = 2000;
2582+
dateComponents.month = 1;
2583+
dateComponents.day = 1;
2584+
NSCalendar * gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
2585+
NSDate * matterEpoch = [gregorianCalendar dateFromComponents:dateComponents];
2586+
2587+
NSDate * nextReportedDSTTransition;
2588+
if (currentDSTOffset.validUntil == nil) {
2589+
nextReportedDSTTransition = nil;
2590+
} else {
2591+
double validUntilMicroSeconds = currentDSTOffset.validUntil.doubleValue;
2592+
nextReportedDSTTransition = [NSDate dateWithTimeInterval:validUntilMicroSeconds / 1e6 sinceDate:matterEpoch];
2593+
}
2594+
2595+
__auto_type * tz = [NSTimeZone localTimeZone];
2596+
NSDate * nextDSTTransition = tz.nextDaylightSavingTimeTransition;
2597+
XCTAssertEqualObjects(nextReportedDSTTransition, nextDSTTransition);
2598+
2599+
XCTAssertEqual(currentDSTOffset.offset.intValue, tz.daylightSavingTimeOffset);
2600+
2601+
// Now check the timezone info we got. We always set exactly one timezone.
2602+
XCTAssertEqual(timeZone.count, 1);
2603+
MTRTimeSynchronizationClusterTimeZoneStruct * currentTimeZone = timeZone[0];
2604+
XCTAssertEqual(tz.secondsFromGMT, currentTimeZone.offset.intValue + currentDSTOffset.offset.intValue);
2605+
#endif // MTR_ENABLE_PROVISIONAL
2606+
}
2607+
25412608
- (void)test900_SubscribeAllAttributes
25422609
{
25432610
MTRBaseDevice * device = GetConnectedDevice();

0 commit comments

Comments
 (0)
Please sign in to comment.