Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set a timezone and DST offsets during commissioning on Darwin. #29933

Merged
merged 1 commit into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/darwin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ jobs:
# but to instrument the code in the underlying libCHIP we need to pass CHIP_IS_UBSAN=YES
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)
# And the same thing, but with MTR_PER_CONTROLLER_STORAGE_ENABLED turned on.
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)
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)
# And the same thing, but with MTR_ENABLE_PROVISIONAL also turned on.
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)
# And the same thing, but with MTR_NO_AVAILABILITY not turned on. This requires -Wno-unguarded-availability-new to avoid availability errors.
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)
# -enableThreadSanitizer instruments the code in Matter.framework,
Expand Down
2 changes: 2 additions & 0 deletions src/controller/AutoCommissioner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ CHIP_ERROR AutoCommissioner::SetCommissioningParameters(const CommissioningParam
mTimeZoneBuf[i].name.SetValue(span);
}
}
auto list = app::DataModel::List<app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type>(mTimeZoneBuf, size);
mParams.SetTimeZone(list);
}

return CHIP_NO_ERROR;
Expand Down
4 changes: 2 additions & 2 deletions src/darwin/Framework/CHIP/MTRCertificateInfo.mm
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ - (MTRDistinguishedNameInfo *)subject

- (NSDate *)notBefore
{
return ChipEpochSecondsAsDate(_data.mNotBeforeTime);
return MatterEpochSecondsAsDate(_data.mNotBeforeTime);
}

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

- (id)copyWithZone:(nullable NSZone *)zone
Expand Down
10 changes: 8 additions & 2 deletions src/darwin/Framework/CHIP/MTRConversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,17 @@ AsNumber(chip::Optional<T> optional)
return (optional.HasValue()) ? @(optional.Value()) : nil;
}

inline NSDate * ChipEpochSecondsAsDate(uint32_t chipEpochSeconds)
inline NSDate * MatterEpochSecondsAsDate(uint32_t matterEpochSeconds)
{
return [NSDate dateWithTimeIntervalSince1970:(chip::kChipEpochSecondsSinceUnixEpoch + (NSTimeInterval) chipEpochSeconds)];
return [NSDate dateWithTimeIntervalSince1970:(chip::kChipEpochSecondsSinceUnixEpoch + (NSTimeInterval) matterEpochSeconds)];
}

/**
* Returns whether the conversion could be performed. Will return false if the
* passed-in date is our of the range representable as a Matter epoch-s value.
*/
bool DateToMatterEpochSeconds(NSDate * date, uint32_t & epoch);

/**
* Utilities for converting between NSSet<NSNumber *> and chip::CATValues.
*/
Expand Down
19 changes: 19 additions & 0 deletions src/darwin/Framework/CHIP/MTRConversion.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#import "MTRLogging_Internal.h"

#include <lib/support/SafeInt.h>
#include <lib/support/TimeUtils.h>

CHIP_ERROR SetToCATValues(NSSet<NSNumber *> * catSet, chip::CATValues & values)
{
Expand Down Expand Up @@ -59,3 +60,21 @@ CHIP_ERROR SetToCATValues(NSSet<NSNumber *> * catSet, chip::CATValues & values)
}
return [NSSet setWithSet:catSet];
}

bool DateToMatterEpochSeconds(NSDate * date, uint32_t & matterEpochSeconds)
{
NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents * components = [calendar componentsInTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] fromDate:date];

if (!chip::CanCastTo<uint16_t>(components.year)) {
return false;
}

uint16_t year = static_cast<uint16_t>([components year]);
uint8_t month = static_cast<uint8_t>([components month]);
uint8_t day = static_cast<uint8_t>([components day]);
uint8_t hour = static_cast<uint8_t>([components hour]);
uint8_t minute = static_cast<uint8_t>([components minute]);
uint8_t second = static_cast<uint8_t>([components second]);
return chip::CalendarToChipEpochTime(year, month, day, hour, minute, second, matterEpochSeconds);
}
53 changes: 53 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@

#include <platform/CHIPDeviceBuildConfig.h>

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

// Set up the right timezone and DST information. For timezone, just
// use our current timezone and don't schedule any sort of timezone
// change.
auto * tz = [NSTimeZone localTimeZone];
using TimeZoneType = chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type;
TimeZoneType timeZone;
timeZone.validAt = 0;
timeZone.offset = static_cast<int32_t>(tz.secondsFromGMT - tz.daylightSavingTimeOffset);
timeZone.name.Emplace(AsCharSpan(tz.name));

params.SetTimeZone(chip::app::DataModel::List<TimeZoneType>(&timeZone, 1));

// For DST, there is no limit to the number of transitions we could try
// to add, but in practice devices likely support only 2 and
// AutoCommissioner caps the list at 10. Let's do up to 4 transitions
// for now.
using DSTOffsetType = chip::app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::Type;

DSTOffsetType dstOffsets[4];
size_t dstOffsetCount = 0;
auto nextOffset = tz.daylightSavingTimeOffset;
uint64_t nextValidStarting = 0;
auto * nextTransition = tz.nextDaylightSavingTimeTransition;
for (auto & dstOffset : dstOffsets) {
++dstOffsetCount;
dstOffset.offset = static_cast<int32_t>(nextOffset);
dstOffset.validStarting = nextValidStarting;
if (nextTransition != nil) {
uint32_t transitionEpochS;
if (DateToMatterEpochSeconds(nextTransition, transitionEpochS)) {
using Microseconds64 = chip::System::Clock::Microseconds64;
using Seconds32 = chip::System::Clock::Seconds32;
dstOffset.validUntil.SetNonNull(Microseconds64(Seconds32(transitionEpochS)).count());
} else {
// Out of range; treat as "forever".
dstOffset.validUntil.SetNull();
}
} else {
dstOffset.validUntil.SetNull();
}

if (dstOffset.validUntil.IsNull()) {
break;
}

nextOffset = [tz daylightSavingTimeOffsetForDate:nextTransition];
nextValidStarting = dstOffset.validUntil.Value();
nextTransition = [tz nextDaylightSavingTimeTransitionAfterDate:nextTransition];
}
params.SetDSTOffsets(chip::app::DataModel::List<DSTOffsetType>(dstOffsets, dstOffsetCount));

chip::NodeId deviceId = [nodeID unsignedLongLongValue];
self->_operationalCredentialsDelegate->SetDeviceID(deviceId);
auto errorCode = self.cppCommissioner->Commission(deviceId, params);
Expand Down
19 changes: 4 additions & 15 deletions src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
#include <lib/core/Optional.h>
#include <lib/core/TLV.h>
#include <lib/support/PersistentStorageMacros.h>
#include <lib/support/SafeInt.h>
#include <lib/support/TimeUtils.h>
#include <platform/LockTracker.h>

using namespace chip;
Expand Down Expand Up @@ -322,21 +320,12 @@

bool MTROperationalCredentialsDelegate::ToChipEpochTime(NSDate * date, uint32_t & epoch)
{
NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents * components = [calendar componentsInTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] fromDate:date];

if (CanCastTo<uint16_t>(components.year)) {
uint16_t year = static_cast<uint16_t>([components year]);
uint8_t month = static_cast<uint8_t>([components month]);
uint8_t day = static_cast<uint8_t>([components day]);
uint8_t hour = static_cast<uint8_t>([components hour]);
uint8_t minute = static_cast<uint8_t>([components minute]);
uint8_t second = static_cast<uint8_t>([components second]);
if (chip::CalendarToChipEpochTime(year, month, day, hour, minute, second, epoch)) {
return true;
}
if (DateToMatterEpochSeconds(date, epoch)) {
return true;
}

NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents * components = [calendar componentsInTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] fromDate:date];
MTR_LOG_ERROR(
"Year %lu is out of range for Matter epoch time. Please use [NSDate distantFuture] to represent \"never expires\".",
static_cast<unsigned long>(components.year));
Expand Down
67 changes: 67 additions & 0 deletions src/darwin/Framework/CHIPTests/MTRDeviceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -2538,6 +2538,73 @@ - (void)test027_AttestationChallenge
[self waitForExpectations:@[ attestationRequestedViaDevice ] timeout:kTimeoutInSeconds];
}

- (void)test028_TimeZoneAndDST
{
// Time synchronization is marked provisional so far, so we can only test it
// when MTR_ENABLE_PROVISIONAL is set.
#if MTR_ENABLE_PROVISIONAL
dispatch_queue_t queue = dispatch_get_main_queue();

__auto_type * device = GetConnectedDevice();
__auto_type * cluster = [[MTRBaseClusterTimeSynchronization alloc] initWithDevice:device endpointID:@(0) queue:queue];

XCTestExpectation * readTimeZoneExpectation = [self expectationWithDescription:@"Read TimeZone attribute"];
__block NSArray<MTRTimeSynchronizationClusterTimeZoneStruct *> * timeZone;
[cluster readAttributeTimeZoneWithCompletion:^(NSArray * _Nullable value, NSError * _Nullable error) {
XCTAssertNil(error);
timeZone = value;
[readTimeZoneExpectation fulfill];
}];

[self waitForExpectations:@[ readTimeZoneExpectation ] timeout:kTimeoutInSeconds];

__block NSArray<MTRTimeSynchronizationClusterDSTOffsetStruct *> * dstOffset;
XCTestExpectation * readDSTOffsetExpectation = [self expectationWithDescription:@"Read DSTOffset attribute"];
[cluster readAttributeDSTOffsetWithCompletion:^(NSArray * _Nullable value, NSError * _Nullable error) {
XCTAssertNil(error);
dstOffset = value;
[readDSTOffsetExpectation fulfill];
}];

[self waitForExpectations:@[ readDSTOffsetExpectation ] timeout:kTimeoutInSeconds];

// Check that the first DST offset entry matches what we expect. If we
// happened to cross a DST boundary during execution of this function, some
// of these checks will fail, but that seems pretty low-probability.

XCTAssertTrue(dstOffset.count > 0);
MTRTimeSynchronizationClusterDSTOffsetStruct * currentDSTOffset = dstOffset[0];

__auto_type * utcTz = [NSTimeZone timeZoneForSecondsFromGMT:0];
__auto_type * dateComponents = [[NSDateComponents alloc] init];
dateComponents.timeZone = utcTz;
dateComponents.year = 2000;
dateComponents.month = 1;
dateComponents.day = 1;
NSCalendar * gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDate * matterEpoch = [gregorianCalendar dateFromComponents:dateComponents];

NSDate * nextReportedDSTTransition;
if (currentDSTOffset.validUntil == nil) {
nextReportedDSTTransition = nil;
} else {
double validUntilMicroSeconds = currentDSTOffset.validUntil.doubleValue;
nextReportedDSTTransition = [NSDate dateWithTimeInterval:validUntilMicroSeconds / 1e6 sinceDate:matterEpoch];
}

__auto_type * tz = [NSTimeZone localTimeZone];
NSDate * nextDSTTransition = tz.nextDaylightSavingTimeTransition;
XCTAssertEqualObjects(nextReportedDSTTransition, nextDSTTransition);

XCTAssertEqual(currentDSTOffset.offset.intValue, tz.daylightSavingTimeOffset);

// Now check the timezone info we got. We always set exactly one timezone.
XCTAssertEqual(timeZone.count, 1);
MTRTimeSynchronizationClusterTimeZoneStruct * currentTimeZone = timeZone[0];
XCTAssertEqual(tz.secondsFromGMT, currentTimeZone.offset.intValue + currentDSTOffset.offset.intValue);
#endif // MTR_ENABLE_PROVISIONAL
}

- (void)test900_SubscribeAllAttributes
{
MTRBaseDevice * device = GetConnectedDevice();
Expand Down