From 2580913e02ecb6ce99b86854da9acee0d2cce8b5 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Wed, 27 Jul 2022 18:33:47 +0200 Subject: [PATCH] [Darwin] Add MTROTAHeaderParser and use it in darwin-framework-tool (#21235) * [Darwin] Add MTROTAHeaderParser * [darwin-framework-tool] Use MTROTAHeaderParser --- .../provider/OTASoftwareUpdateInteractive.mm | 58 +++++++-------- src/darwin/Framework/CHIP/MTRError.h | 1 + src/darwin/Framework/CHIP/MTRError.mm | 6 ++ .../Framework/CHIP/MTROTAHeaderParser.h | 56 +++++++++++++++ .../Framework/CHIP/MTROTAHeaderParser.mm | 72 +++++++++++++++++++ src/darwin/Framework/CHIP/Matter.h | 1 + .../Framework/CHIP/NSStringSpanConversion.h | 36 ++++++++++ .../Matter.xcodeproj/project.pbxproj | 8 +++ 8 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTROTAHeaderParser.h create mode 100644 src/darwin/Framework/CHIP/MTROTAHeaderParser.mm create mode 100644 src/darwin/Framework/CHIP/NSStringSpanConversion.h diff --git a/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.mm b/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.mm index b5e33fc2764b1b..3bc3192b743b69 100644 --- a/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.mm +++ b/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.mm @@ -19,12 +19,9 @@ #include #include -// TODO: Objective-C Matter.framework needs to expose this. -#include - constexpr size_t kOtaHeaderMaxSize = 1024; -bool ParseOTAHeader(chip::OTAImageHeaderParser & parser, const char * otaFilePath, chip::OTAImageHeader & header) +MTROTAHeader * ParseOTAHeader(const char * otaFilePath) { uint8_t otaFileContent[kOtaHeaderMaxSize]; chip::ByteSpan buffer(otaFileContent); @@ -32,27 +29,17 @@ bool ParseOTAHeader(chip::OTAImageHeaderParser & parser, const char * otaFilePat std::ifstream otaFile(otaFilePath, std::ifstream::in); if (!otaFile.is_open() || !otaFile.good()) { ChipLogError(SoftwareUpdate, "Error opening OTA image file: %s", otaFilePath); - return false; + return nil; } otaFile.read(reinterpret_cast(otaFileContent), kOtaHeaderMaxSize); if (otaFile.bad()) { ChipLogError(SoftwareUpdate, "Error reading OTA image file: %s", otaFilePath); - return false; - } - - parser.Init(); - if (!parser.IsInitialized()) { - return false; + return nil; } - CHIP_ERROR error = parser.AccumulateAndDecode(buffer, header); - if (error != CHIP_NO_ERROR) { - ChipLogError(SoftwareUpdate, "Error parsing OTA image header: %" CHIP_ERROR_FORMAT, error.Format()); - return false; - } - - return true; + NSError * error; + return [MTROTAHeaderParser headerFromData:[NSData dataWithBytes:buffer.data() length:buffer.size()] error:&error]; } // Parses the JSON filepath and extracts DeviceSoftwareVersionModel parameters @@ -138,11 +125,9 @@ static bool ParseJsonFileAndPopulateCandidates( VerifyOrReturnError(ParseJsonFileAndPopulateCandidates(filePath, &candidates), CHIP_ERROR_INTERNAL); for (DeviceSoftwareVersionModel * candidate : candidates) { - chip::OTAImageHeaderParser parser; - chip::OTAImageHeader header; - auto otaURL = [candidate.deviceModelData.otaURL UTF8String]; - VerifyOrReturnError(ParseOTAHeader(parser, otaURL, header), CHIP_ERROR_INVALID_ARGUMENT); + auto header = ParseOTAHeader(otaURL); + VerifyOrReturnError(header != nil, CHIP_ERROR_INVALID_ARGUMENT); ChipLogDetail(chipTool, "Validating image list candidate %s: ", [candidate.deviceModelData.otaURL UTF8String]); @@ -154,23 +139,28 @@ static bool ParseJsonFileAndPopulateCandidates( auto minApplicableSoftwareVersion = [candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue]; auto maxApplicableSoftwareVersion = [candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue]; - VerifyOrReturnError(vendorId == header.mVendorId, CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError(productId == header.mProductId, CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError(softwareVersion == header.mSoftwareVersion, CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError(softwareVersionStringLength == header.mSoftwareVersionString.size(), CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError( - memcmp(softwareVersionString, header.mSoftwareVersionString.data(), header.mSoftwareVersionString.size()) == 0, + auto headerVendorId = [header.vendorID unsignedIntValue]; + auto headerProductId = [header.productID unsignedIntValue]; + auto headerSoftwareVersion = [header.softwareVersion unsignedLongValue]; + auto headerSoftwareVersionString = [header.softwareVersionString UTF8String]; + auto headerSoftwareVersionStringLength = [header.softwareVersionString length]; + + VerifyOrReturnError(vendorId == headerVendorId, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(productId == headerProductId, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(softwareVersion == headerSoftwareVersion, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(softwareVersionStringLength == headerSoftwareVersionStringLength, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(memcmp(softwareVersionString, headerSoftwareVersionString, headerSoftwareVersionStringLength) == 0, CHIP_ERROR_INVALID_ARGUMENT); - if (header.mMinApplicableVersion.HasValue()) { - VerifyOrReturnError(minApplicableSoftwareVersion == header.mMinApplicableVersion.Value(), CHIP_ERROR_INVALID_ARGUMENT); + if (header.minApplicableVersion) { + auto version = [header.minApplicableVersion unsignedLongValue]; + VerifyOrReturnError(minApplicableSoftwareVersion == version, CHIP_ERROR_INVALID_ARGUMENT); } - if (header.mMaxApplicableVersion.HasValue()) { - VerifyOrReturnError(maxApplicableSoftwareVersion == header.mMaxApplicableVersion.Value(), CHIP_ERROR_INVALID_ARGUMENT); + if (header.maxApplicableVersion) { + auto version = [header.maxApplicableVersion unsignedLongValue]; + VerifyOrReturnError(maxApplicableSoftwareVersion == version, CHIP_ERROR_INVALID_ARGUMENT); } - - parser.Clear(); } mOTADelegate.candidates = candidates; diff --git a/src/darwin/Framework/CHIP/MTRError.h b/src/darwin/Framework/CHIP/MTRError.h index bb1623bf2c374f..7dc123f2792806 100644 --- a/src/darwin/Framework/CHIP/MTRError.h +++ b/src/darwin/Framework/CHIP/MTRError.h @@ -50,6 +50,7 @@ typedef NS_ERROR_ENUM(MTRErrorDomain, MTRErrorCode){ MTRErrorCodeWrongAddressType = 7, MTRErrorCodeIntegrityCheckFailed = 8, MTRErrorCodeTimeout = 9, + MTRErrorCodeBufferTooSmall = 10, }; // clang-format on diff --git a/src/darwin/Framework/CHIP/MTRError.mm b/src/darwin/Framework/CHIP/MTRError.mm index e07928fa8659a5..153b034d54035b 100644 --- a/src/darwin/Framework/CHIP/MTRError.mm +++ b/src/darwin/Framework/CHIP/MTRError.mm @@ -77,6 +77,9 @@ + (NSError *)errorForCHIPErrorCode:(CHIP_ERROR)errorCode } else if (errorCode == CHIP_ERROR_TIMEOUT) { code = MTRErrorCodeTimeout; [userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Transaction timed out.", nil) }]; + } else if (errorCode == CHIP_ERROR_BUFFER_TOO_SMALL) { + code = MTRErrorCodeBufferTooSmall; + [userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"A buffer is too small.", nil) }]; } else { code = MTRErrorCodeGeneralError; [userInfo addEntriesFromDictionary:@{ @@ -259,6 +262,9 @@ + (CHIP_ERROR)errorToCHIPErrorCode:(NSError * _Nullable)error case MTRErrorCodeTimeout: code = CHIP_ERROR_TIMEOUT.AsInteger(); break; + case MTRErrorCodeBufferTooSmall: + code = CHIP_ERROR_BUFFER_TOO_SMALL.AsInteger(); + break; case MTRErrorCodeGeneralError: { if (error.userInfo != nil && error.userInfo[@"errorCode"] != nil) { code = static_cast([error.userInfo[@"errorCode"] unsignedLongValue]); diff --git a/src/darwin/Framework/CHIP/MTROTAHeaderParser.h b/src/darwin/Framework/CHIP/MTROTAHeaderParser.h new file mode 100644 index 00000000000000..d578af8cae05b8 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAHeaderParser.h @@ -0,0 +1,56 @@ +/** + * + * Copyright (c) 2022 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, MTROTAImageDigestType) { + MTROTAImageDigestTypeSha256 = 1, + MTROTAImageDigestTypeSha256_128, + MTROTAImageDigestTypeSha256_120, + MTROTAImageDigestTypeSha256_96, + MTROTAImageDigestTypeSha256_64, + MTROTAImageDigestTypeSha256_32, + MTROTAImageDigestTypeSha384, + MTROTAImageDigestTypeSha512, + MTROTAImageDigestTypeSha3_224, + MTROTAImageDigestTypeSha3_256, + MTROTAImageDigestTypeSha3_384, + MTROTAImageDigestTypeSha3_512, +}; + +@interface MTROTAHeader : NSObject + +@property (nonatomic, strong) NSNumber * vendorID; +@property (nonatomic, strong) NSNumber * productID; +@property (nonatomic, strong) NSNumber * payloadSize; +@property (nonatomic, strong) NSNumber * softwareVersion; +@property (nonatomic, strong) NSString * softwareVersionString; +@property (nonatomic, strong) NSString * releaseNotesURL; +@property (nonatomic, strong) NSData * imageDigest; +@property (nonatomic, assign) MTROTAImageDigestType imageDigestType; +@property (nonatomic, strong) NSNumber * minApplicableVersion; +@property (nonatomic, strong) NSNumber * maxApplicableVersion; + +@end + +@interface MTROTAHeaderParser : NSObject ++ (nullable MTROTAHeader *)headerFromData:(NSData *)data error:(NSError * __autoreleasing *)error; +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTROTAHeaderParser.mm b/src/darwin/Framework/CHIP/MTROTAHeaderParser.mm new file mode 100644 index 00000000000000..558d590e98e5c1 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAHeaderParser.mm @@ -0,0 +1,72 @@ +/** + * + * Copyright (c) 2022 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. + */ + +#import "MTROTAHeaderParser.h" + +#import "MTRError.h" +#import "MTRError_Internal.h" +#import "NSDataSpanConversion.h" +#import "NSStringSpanConversion.h" + +#include + +@implementation MTROTAHeader +@end + +@implementation MTROTAHeaderParser ++ (nullable MTROTAHeader *)headerFromData:(NSData *)data error:(NSError * __autoreleasing *)error +{ + chip::OTAImageHeaderParser parser; + + parser.Init(); + + if (!parser.IsInitialized()) { + *error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]; + return nil; + } + + chip::ByteSpan buffer = AsByteSpan(data); + chip::OTAImageHeader header; + CHIP_ERROR err = parser.AccumulateAndDecode(buffer, header); + if (err != CHIP_NO_ERROR) { + *error = [MTRError errorForCHIPErrorCode:err]; + parser.Clear(); + return nil; + } + + auto headerObj = [MTROTAHeader new]; + headerObj.vendorID = @(header.mVendorId); + headerObj.productID = @(header.mProductId); + headerObj.payloadSize = @(header.mPayloadSize); + headerObj.softwareVersion = @(header.mSoftwareVersion); + headerObj.softwareVersionString = AsString(header.mSoftwareVersionString); + headerObj.releaseNotesURL = AsString(header.mReleaseNotesURL); + headerObj.imageDigest = AsData(header.mImageDigest); + headerObj.imageDigestType = static_cast(chip::to_underlying(header.mImageDigestType)); + + if (header.mMinApplicableVersion.HasValue()) { + headerObj.minApplicableVersion = @(header.mMinApplicableVersion.Value()); + } + + if (header.mMaxApplicableVersion.HasValue()) { + headerObj.maxApplicableVersion = @(header.mMaxApplicableVersion.Value()); + } + + parser.Clear(); + return headerObj; +} +@end diff --git a/src/darwin/Framework/CHIP/Matter.h b/src/darwin/Framework/CHIP/Matter.h index 3f5856e892c8e6..cabba655deaa11 100644 --- a/src/darwin/Framework/CHIP/Matter.h +++ b/src/darwin/Framework/CHIP/Matter.h @@ -34,6 +34,7 @@ #import #import #import +#import #import #import #import diff --git a/src/darwin/Framework/CHIP/NSStringSpanConversion.h b/src/darwin/Framework/CHIP/NSStringSpanConversion.h new file mode 100644 index 00000000000000..0bd7e21cbbe52d --- /dev/null +++ b/src/darwin/Framework/CHIP/NSStringSpanConversion.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2022 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. + */ + +#pragma once + +#import "Foundation/Foundation.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +/** + * Utilities for converting between NSString and chip::CharSpan. + */ + +inline chip::CharSpan AsCharSpan(NSString * str) { return chip::CharSpan([str UTF8String], [str length]); } + +inline NSString * AsString(chip::CharSpan span) +{ + return [[NSString alloc] initWithBytes:span.data() length:span.size() encoding:NSUTF8StringEncoding]; +} + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index d0464d268fc426..7e19b0328b55a0 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 1ED276E026C57CF000547A89 /* MTRCallbackBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1ED276DF26C57CF000547A89 /* MTRCallbackBridge.mm */; }; 1ED276E226C5812A00547A89 /* MTRCluster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1ED276E126C5812A00547A89 /* MTRCluster.mm */; }; 1ED276E426C5832500547A89 /* MTRCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = 1ED276E326C5832500547A89 /* MTRCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1EDCE545289049A100E41EC9 /* MTROTAHeaderParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EDCE543289049A100E41EC9 /* MTROTAHeaderParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1EDCE546289049A100E41EC9 /* MTROTAHeaderParser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1EDCE544289049A100E41EC9 /* MTROTAHeaderParser.mm */; }; 27A53C1727FBC6920053F131 /* MTRAttestationTrustStoreBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 27A53C1527FBC6920053F131 /* MTRAttestationTrustStoreBridge.h */; }; 27A53C1827FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 27A53C1627FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm */; }; 2C1B027A2641DB4E00780EF1 /* MTROperationalCredentialsDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2C1B02782641DB4E00780EF1 /* MTROperationalCredentialsDelegate.mm */; }; @@ -133,6 +135,8 @@ 1ED276DF26C57CF000547A89 /* MTRCallbackBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MTRCallbackBridge.mm; path = "zap-generated/MTRCallbackBridge.mm"; sourceTree = ""; }; 1ED276E126C5812A00547A89 /* MTRCluster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRCluster.mm; sourceTree = ""; }; 1ED276E326C5832500547A89 /* MTRCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCluster.h; sourceTree = ""; }; + 1EDCE543289049A100E41EC9 /* MTROTAHeaderParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTROTAHeaderParser.h; sourceTree = ""; }; + 1EDCE544289049A100E41EC9 /* MTROTAHeaderParser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROTAHeaderParser.mm; sourceTree = ""; }; 27A53C1527FBC6920053F131 /* MTRAttestationTrustStoreBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRAttestationTrustStoreBridge.h; sourceTree = ""; }; 27A53C1627FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRAttestationTrustStoreBridge.mm; sourceTree = ""; }; 2C1B02782641DB4E00780EF1 /* MTROperationalCredentialsDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROperationalCredentialsDelegate.mm; sourceTree = ""; }; @@ -313,6 +317,8 @@ B202528F2459E34F00F97062 /* CHIP */ = { isa = PBXGroup; children = ( + 1EDCE543289049A100E41EC9 /* MTROTAHeaderParser.h */, + 1EDCE544289049A100E41EC9 /* MTROTAHeaderParser.mm */, 27A53C1527FBC6920053F131 /* MTRAttestationTrustStoreBridge.h */, 27A53C1627FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm */, 88EBF8CB27FABDD500686BC1 /* MTRDeviceAttestationDelegate.h */, @@ -484,6 +490,7 @@ 991DC08B247704DC00C13860 /* MTRLogging.h in Headers */, 5A7947E527C0129F00434CF2 /* MTRDeviceController+XPC.h in Headers */, B2E0D7B4245B0B5C003C5B48 /* MTRError_Internal.h in Headers */, + 1EDCE545289049A100E41EC9 /* MTROTAHeaderParser.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -623,6 +630,7 @@ 1EC3238D271999E2002A8BF0 /* cluster-objects.cpp in Sources */, 991DC0892475F47D00C13860 /* MTRDeviceController.mm in Sources */, B2E0D7B7245B0B5C003C5B48 /* MTRQRCodeSetupPayloadParser.mm in Sources */, + 1EDCE546289049A100E41EC9 /* MTROTAHeaderParser.mm in Sources */, 1EC4CE5D25CC26E900D7304F /* MTRBaseClusters.mm in Sources */, 51E0310127EA20D20083DC9C /* MTRControllerAccessControl.mm in Sources */, 1ED276E226C5812A00547A89 /* MTRCluster.mm in Sources */,