From b7530f455ba8c9513ee0763ad7be7e1623160d12 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Thu, 21 Dec 2023 13:57:25 +0100 Subject: [PATCH 1/2] [Matter.framework] Add MTRDiagnosticLogsDelegate to the Matter.framework --- src/darwin/Framework/CHIP/MTRDevice.h | 21 + src/darwin/Framework/CHIP/MTRDevice.mm | 12 + .../Framework/CHIP/MTRDeviceController.mm | 14 + .../CHIP/MTRDeviceControllerFactory.mm | 32 + .../MTRDeviceControllerFactory_Internal.h | 11 + .../CHIP/MTRDeviceController_Internal.h | 10 + .../CHIP/MTRDiagnosticLogsDownloader.h | 42 ++ .../CHIP/MTRDiagnosticLogsDownloader.mm | 630 ++++++++++++++++++ .../Framework/CHIP/MTRDiagnosticLogsType.h | 27 + src/darwin/Framework/CHIP/Matter.h | 1 + .../Matter.xcodeproj/project.pbxproj | 12 + 11 files changed, 812 insertions(+) create mode 100644 src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.h create mode 100644 src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm create mode 100644 src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h diff --git a/src/darwin/Framework/CHIP/MTRDevice.h b/src/darwin/Framework/CHIP/MTRDevice.h index 4627b96beab62d..32a4593a2e40fc 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.h +++ b/src/darwin/Framework/CHIP/MTRDevice.h @@ -18,6 +18,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -325,6 +326,26 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) */ - (void)removeClientDataForKey:(NSString *)key endpointID:(NSNumber *)endpointID MTR_UNSTABLE_API; +/** + * Download log of the desired type from the device. + * + * Note: The consumer of this API should move the file that the url points to or open it for reading before the + * completion handler returns. Otherwise, the file will be deleted, and the data will be lost. + * + * @param type The type of log being requested. This should correspond to a value in the enum MTRDiagnosticLogType. + * @param timeout The timeout for getting the log. If the timeout expires, completion will be called with whatever + * has been retrieved by that point (which might be none or a partial log). + * If the timeout is set to 0, the request will not expire and completion will not be called until + * the log is fully retrieved or an error occurs. + * @param queue The queue on which completion will be called. + * @param completion The completion that will be called to return the URL of the requested log if successful. Otherwise + * returns an error. + */ +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion + MTR_NEWLY_AVAILABLE; @end @protocol MTRDeviceDelegate diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 909fe065ac39ac..f25ac5b9564187 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -1363,6 +1363,18 @@ - (void)openCommissioningWindowWithDiscriminator:(NSNumber *)discriminator [baseDevice openCommissioningWindowWithDiscriminator:discriminator duration:duration queue:queue completion:completion]; } +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + [_deviceController downloadLogFromNodeWithID:_nodeID + type:type + timeout:timeout + queue:queue + completion:completion]; +} + #pragma mark - Cache management // assume lock is held diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 836ea101ff8b1d..decd879a657ef3 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -1216,6 +1216,20 @@ - (void)operationalInstanceAdded:(chip::NodeId)nodeID [device nodeMayBeAdvertisingOperational]; } +- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + [_factory downloadLogFromNodeWithID:nodeID + controller:self + type:type + timeout:timeout + queue:queue + completion:completion]; +} + @end /** diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm index 717679e233a3be..319c33fdac999e 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm @@ -31,6 +31,7 @@ #import "MTRDeviceControllerStartupParams.h" #import "MTRDeviceControllerStartupParams_Internal.h" #import "MTRDeviceController_Internal.h" +#import "MTRDiagnosticLogsDownloader.h" #import "MTRError_Internal.h" #import "MTRFabricInfo_Internal.h" #import "MTRFramework.h" @@ -133,6 +134,8 @@ @interface MTRDeviceControllerFactory () @property (nonatomic, readonly, nullable) id otaProviderDelegate; @property (nonatomic, readonly, nullable) dispatch_queue_t otaProviderDelegateQueue; +@property (nonatomic, readonly) MTRDiagnosticLogsDownloader * diagnosticLogsDownloader; + - (BOOL)findMatchingFabric:(FabricTable &)fabricTable params:(MTRDeviceControllerStartupParams *)params fabric:(const FabricInfo * _Nullable * _Nonnull)fabric; @@ -332,6 +335,8 @@ - (void)cleanupStartupObjects delete _persistentStorageDelegate; _persistentStorageDelegate = nullptr; } + + _diagnosticLogsDownloader = nil; } - (CHIP_ERROR)_initFabricTable:(FabricTable &)fabricTable @@ -1066,6 +1071,33 @@ - (nullable MTRDeviceController *)runningControllerForFabricIndex:(chip::FabricI return [self runningControllerForFabricIndex:fabricIndex includeControllerStartingUp:YES includeControllerShuttingDown:YES]; } +- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + dispatch_sync(_chipWorkQueue, ^{ + if (![self isRunning]) { + return; + } + + if (_diagnosticLogsDownloader == nil) { + _diagnosticLogsDownloader = [[MTRDiagnosticLogsDownloader alloc] init]; + auto systemState = _controllerFactory->GetSystemState(); + systemState->BDXTransferServer()->SetDelegate([_diagnosticLogsDownloader getBridge]); + } + + [_diagnosticLogsDownloader downloadLogFromNodeWithID:nodeID + controller:controller + type:type + timeout:timeout + queue:queue + completion:completion]; + }); +} + - (void)operationalInstanceAdded:(chip::PeerId &)operationalID { assertChipStackLockedByCurrentThread(); diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h index 72827d10131d89..e41300127be59e 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h @@ -22,6 +22,7 @@ #import #import #import +#import #if MTR_PER_CONTROLLER_STORAGE_ENABLED #import @@ -75,6 +76,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)operationalInstanceAdded:(chip::PeerId &)operationalID; +/** + * Download log of the desired type from the device. + */ +- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion; + /** * Initialize an MTRDeviceController with the given parameters. */ diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h index 6bc64e03d8594a..92873cf4016b9e 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h @@ -33,6 +33,7 @@ #import #import +#import #if MTR_PER_CONTROLLER_STORAGE_ENABLED #import #else @@ -233,6 +234,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)operationalInstanceAdded:(chip::NodeId)nodeID; +/** + * Download log of the desired type from the device. + */ +- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion; + #pragma mark - Device-specific data and SDK access // DeviceController will act as a central repository for this opaque dictionary that MTRDevice manages - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID; diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.h b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.h new file mode 100644 index 00000000000000..f10fea520fb4fb --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.h @@ -0,0 +1,42 @@ +/** + * + * Copyright (c) 2024 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 + +#import +#import + +namespace chip { +namespace bdx { + class BDXTransferServerDelegate; +} +} + +NS_ASSUME_NONNULL_BEGIN + +@interface MTRDiagnosticLogsDownloader : NSObject +- (chip::bdx::BDXTransferServerDelegate *)getBridge; + +- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion; +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm new file mode 100644 index 00000000000000..4814b37d7a82f4 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm @@ -0,0 +1,630 @@ +/** + * + * Copyright (c) 2024 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 "MTRDiagnosticLogsDownloader.h" + +#include + +#import "MTRDeviceControllerFactory_Internal.h" +#import "MTRDeviceController_Internal.h" +#import "MTRError_Internal.h" +#import "MTRLogging_Internal.h" +#import "NSDataSpanConversion.h" +#import "NSStringSpanConversion.h" + +#import "zap-generated/MTRClusters.h" + +typedef void (^AbortHandler)(NSError * error); + +static NSString * const kErrorInitDiagnosticLogsDownloader = @"Init failure while initializing Diagnostic Logs bridge."; +static NSString * const kEndUserSupport = @"EndUserSupport"; +static NSString * const kNetworkDiagnostics = @"NetworkDiagnostics"; +static NSString * const kCrash = @"Crash"; + +constexpr uint8_t kDiagnosticLogsEndPoint = 0; + +class DiagnosticLogsDownloaderBridge; + +NS_ASSUME_NONNULL_BEGIN + +@interface Download : NSObject +@property (nonatomic) NSString * fileDesignator; +@property (nonatomic) NSNumber * fabricIndex; +@property (nonatomic) NSNumber * nodeID; +@property (nonatomic) NSURL * fileURL; +@property (nonatomic) NSFileHandle * fileHandle; +@property (nonatomic) AbortHandler abortHandler; +@property (nonatomic) MTRStatusCompletion finalize; + +- (instancetype)initWithType:(MTRDiagnosticLogType)type + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion + done:(void (^)(Download * finishedDownload))done; + +- (void)writeToFile:(NSData *)data error:(out NSError **)error; + +- (BOOL)compare:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID; + +- (void)checkInteractionModelResponse:(MTRDiagnosticLogsClusterRetrieveLogsResponseParams * _Nullable)response error:(NSError * _Nullable)error; + +- (void)success; +- (void)failure:(NSError * _Nullable)error; +@end + +@interface Downloads : NSObject +@property (nonatomic, strong) NSMutableArray * downloads; + +- (Download * _Nullable)get:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID; + +- (Download * _Nullable)add:(MTRDiagnosticLogType)type + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion + done:(void (^)(Download * finishedDownload))done; +@end + +@interface MTRDiagnosticLogsDownloader () +@property (readonly) DiagnosticLogsDownloaderBridge * bridge; +@property (nonatomic, strong) Downloads * downloads; + +/** + * Notify the delegate when a BDX Session starts for some logs. + * + * If completion is passed a non-nil error, that will be converted into + * an error response to the BDX initiatior. Otherwise a success response will be sent. + */ +- (void)handleBDXTransferSessionBeginForFileDesignator:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + completion:(MTRStatusCompletion)completion + abortHandler:(AbortHandler)abortHandler; + +/** + * Notify the delegate when some data is received on the BDX Session. + * + * If completion is passed a non-nil error, that will be converted into + * an error response to the sender. Otherwise a success response will be sent. + */ +- (void)handleBDXTransferSessionDataForFileDesignator:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + data:(NSData *)data + completion:(MTRStatusCompletion)completion; + +/** + * Notify the delegate when a BDX Session ends for some logs. + */ +- (void)handleBDXTransferSessionEndForFileDesignator:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + error:(NSError * _Nullable)error; + +@end + +NS_ASSUME_NONNULL_END + +class DiagnosticLogsDownloaderBridge : public chip::bdx::BDXTransferServerDelegate { +public: + DiagnosticLogsDownloaderBridge(MTRDiagnosticLogsDownloader * delegate); + ~DiagnosticLogsDownloaderBridge(); + + /////////// BDXTransferServerDelegate Interface ///////// + CHIP_ERROR OnTransferBegin(chip::bdx::BDXTransferProxy * transfer) override; + CHIP_ERROR OnTransferEnd(chip::bdx::BDXTransferProxy * transfer, CHIP_ERROR error) override; + CHIP_ERROR OnTransferData(chip::bdx::BDXTransferProxy * transfer, const chip::ByteSpan & data) override; + + CHIP_ERROR StartBDXTransferTimeout(Download * download, uint16_t timeoutInSeconds); + void CancelBDXTransferTimeout(Download * download); + +private: + static void OnTransferTimeout(chip::System::Layer * layer, void * context); + MTRDiagnosticLogsDownloader * mDelegate; + AbortHandler mAbortHandler; +}; + +@implementation Download +- (instancetype)initWithType:(MTRDiagnosticLogType)type + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion + done:(void (^)(Download * finishedDownload))done; +{ + self = [super init]; + if (self) { + auto * fileDesignator = [self _toFileDesignatorString:type nodeID:nodeID]; + auto * fileURL = [self _toFileURL:type nodeID:nodeID]; + + __weak typeof(self) weakSelf = self; + auto bdxTransferDone = ^(NSError * bdxError) { + dispatch_async(queue, ^{ + Download * strongSelf = weakSelf; + if (strongSelf) { + // If a fileHandle exists, it means that the BDX session has been initiated and a file has + // been created to host the data of the session. So even if there is an error it may be some + // data in the logs that the caller may find useful. For this reason, fileURL is passed in even + // when there is an error but fileHandle is not nil. + completion(strongSelf->_fileHandle ? fileURL : nil, bdxError); + [strongSelf deleteFile]; + + done(strongSelf); + } + }); + }; + + _fileDesignator = fileDesignator; + _fabricIndex = fabricIndex; + _nodeID = nodeID; + _fileURL = fileURL; + _fileHandle = nil; + _finalize = bdxTransferDone; + } + return self; +} + +- (void)checkInteractionModelResponse:(MTRDiagnosticLogsClusterRetrieveLogsResponseParams * _Nullable)response error:(NSError * _Nullable)error +{ + VerifyOrReturn(nil == error, [self failure:error]); + + auto status = response.status; + + VerifyOrReturn(![status isEqual:@(MTRDiagnosticLogsStatusBusy)], [self failure:[MTRError errorForCHIPErrorCode:CHIP_ERROR_BUSY]]); + VerifyOrReturn(![status isEqual:@(MTRDiagnosticLogsStatusDenied)], [self failure:[MTRError errorForCHIPErrorCode:CHIP_ERROR_ACCESS_DENIED]]); + + // If there is not logs for the given type, forward it to the caller with a nil url and stop here. + VerifyOrReturn(![status isEqual:@(MTRDiagnosticLogsStatusNoLogs)], [self success]); + + // If the whole log content fits into the response LogContent field, forward it to the caller + // and stop here. + if ([status isEqual:@(MTRDiagnosticLogsStatusExhausted)]) { + NSError * writeError = nil; + [self writeToFile:response.logContent error:&writeError]; + VerifyOrReturn(nil == writeError, [self failure:[MTRError errorForCHIPErrorCode:CHIP_ERROR_INTERNAL]]); + + [self success]; + return; + }; + + // The file is going to be transferred over BDX. Everything past this point will be handled in bdxTransferDone. +} + +- (void)createFile:(NSError **)error +{ + VerifyOrReturn(nil == _fileHandle); + + auto * fileManager = [NSFileManager defaultManager]; + [fileManager URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:_fileURL + create:YES + error:error]; + VerifyOrReturn(nil == *error); + + BOOL success = [fileManager createFileAtPath:[_fileURL path] contents:nil attributes:nil]; + VerifyOrReturn(success, *error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTERNAL]); + + auto * fileHandle = [NSFileHandle fileHandleForWritingToURL:_fileURL error:error]; + VerifyOrReturn(nil == *error); + + _fileHandle = fileHandle; +} + +- (void)deleteFile +{ + VerifyOrReturn(nil != _fileHandle); + + NSError * error = nil; + [[NSFileManager defaultManager] removeItemAtPath:[_fileURL path] error:&error]; + if (nil != error) { + // There is an error but there is really not much we can do at that point besides logging it. + MTR_LOG_ERROR("Error: %@", error); + } +} + +- (void)writeToFile:(NSData *)data error:(out NSError **)error +{ + [self createFile:error]; + [_fileHandle seekToEndOfFile]; + [_fileHandle writeData:data error:error]; +} + +- (BOOL)compare:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID +{ + return [_fileDesignator isEqualToString:fileDesignator] && _fabricIndex == fabricIndex && _nodeID == nodeID; +} + +- (void)failure:(NSError * _Nullable)error +{ + _finalize(error); +} + +- (void)success +{ + _finalize(nil); +} + +- (NSURL *)_toFileURL:(MTRDiagnosticLogType)type nodeID:(NSNumber *)nodeID +{ + auto * dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"yyyy-MM-dd_HH:mm:ss.SSSZZZ"; + auto * timeString = [dateFormatter stringFromDate:NSDate.now]; + auto * nodeIDString = [self _toNodeIDString:nodeID]; + auto * typeString = [self _toTypeString:type]; + auto * filename = [NSString stringWithFormat:@"%@_%@_%@", timeString, nodeIDString, typeString]; + return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:filename] isDirectory:YES]; +} + +- (NSString *)_toFileDesignatorString:(MTRDiagnosticLogType)type nodeID:(NSNumber *)nodeID +{ + auto * nodeIDString = [self _toNodeIDString:nodeID]; + auto * typeString = [self _toTypeString:type]; + auto * fileDesignator = [NSString stringWithFormat:@"bdx://%@/%@", nodeIDString, typeString]; + auto substringIndex = MIN(chip::bdx::DiagnosticLogs::kMaxFileDesignatorLen, [fileDesignator length]); + return [fileDesignator substringToIndex:substringIndex]; +} + +- (NSString *)_toNodeIDString:(NSNumber *)nodeID +{ + return [NSString stringWithFormat:@"%016llX", nodeID.unsignedLongLongValue]; +} + +- (NSString *)_toTypeString:(MTRDiagnosticLogType)type +{ + switch (type) { + case MTRDiagnosticLogTypeEndUserSupport: + return kEndUserSupport; + case MTRDiagnosticLogTypeNetworkDiagnostics: + return kNetworkDiagnostics; + case MTRDiagnosticLogTypeCrash: + return kCrash; + default: + // This should never happen. + chipDie(); + } +} + +@end + +@implementation Downloads +- (instancetype)init +{ + if (self = [super init]) { + _downloads = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dealloc +{ + auto error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTERNAL]; + for (Download * download in _downloads) { + [download failure:error]; + } + _downloads = nil; +} + +- (Download * _Nullable)get:(NSString *)fileDesignator fabricIndex:(NSNumber *)fabricIndex nodeID:(NSNumber *)nodeID +{ + for (Download * download in _downloads) { + if ([download compare:fileDesignator fabricIndex:fabricIndex nodeID:nodeID]) { + return download; + } + } + + return nil; +} + +- (Download * _Nullable)add:(MTRDiagnosticLogType)type + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion + done:(void (^)(Download * finishedDownload))done +{ + auto download = [[Download alloc] initWithType:type fabricIndex:fabricIndex nodeID:nodeID queue:queue completion:completion done:done]; + VerifyOrReturnValue(nil != download, nil); + + [_downloads addObject:download]; + return download; +} + +- (void)remove:(Download *)download +{ + [_downloads removeObject:download]; +} +@end + +@implementation MTRDiagnosticLogsDownloader +- (instancetype)init +{ + assertChipStackLockedByCurrentThread(); + + if (self = [super init]) { + _downloads = [[Downloads alloc] init]; + _bridge = new DiagnosticLogsDownloaderBridge(self); + if (_bridge == nullptr) { + MTR_LOG_ERROR("Error: %@", kErrorInitDiagnosticLogsDownloader); + return nil; + } + } + return self; +} + +- (void)dealloc +{ + if (_bridge) { + delete _bridge; + _bridge = nil; + } + _downloads = nil; +} + +- (chip::bdx::BDXTransferServerDelegate *)getBridge +{ + return _bridge; +} + +- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion; +{ + assertChipStackLockedByCurrentThread(); + + uint16_t timeoutInSeconds = 0; + if (timeout <= 0) { + timeoutInSeconds = 0; + } else if (timeout > UINT16_MAX) { + MTR_LOG_INFO("Warning: timeout is too large. It will be truncated to UINT16_MAX."); + timeoutInSeconds = UINT16_MAX; + } else { + timeoutInSeconds = static_cast(timeout); + } + + // This block is always called when a download is finished. + auto done = ^(Download * finishedDownload) { + [controller asyncDispatchToMatterQueue:^() { + [self->_downloads remove:finishedDownload]; + + if (timeoutInSeconds > 0) { + self->_bridge->CancelBDXTransferTimeout(finishedDownload); + } + } errorHandler:nil]; + }; + + auto fabricIndex = @(controller.fabricIndex); + auto download = [_downloads add:type fabricIndex:fabricIndex nodeID:nodeID queue:queue completion:completion done:done]; + VerifyOrReturn(nil != download, + dispatch_async(queue, ^{ completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTERNAL]); })); + + auto interactionModelDone = ^(MTRDiagnosticLogsClusterRetrieveLogsResponseParams * _Nullable response, NSError * _Nullable error) { + [download checkInteractionModelResponse:response error:error]; + }; + + auto * device = [controller deviceForNodeID:nodeID]; + auto * cluster = [[MTRClusterDiagnosticLogs alloc] initWithDevice:device endpointID:@(kDiagnosticLogsEndPoint) queue:queue]; + + auto * params = [[MTRDiagnosticLogsClusterRetrieveLogsRequestParams alloc] init]; + params.intent = @(type); + params.requestedProtocol = @(MTRDiagnosticLogsTransferProtocolBDX); + params.transferFileDesignator = download.fileDesignator; + + [cluster retrieveLogsRequestWithParams:params expectedValues:nil expectedValueInterval:nil completion:interactionModelDone]; + + if (timeoutInSeconds > 0) { + auto err = _bridge->StartBDXTransferTimeout(download, timeoutInSeconds); + VerifyOrReturn(CHIP_NO_ERROR == err, [download failure:[MTRError errorForCHIPErrorCode:err]]); + } +} + +- (void)handleBDXTransferSessionBeginForFileDesignator:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + completion:(MTRStatusCompletion)completion + abortHandler:(AbortHandler)abortHandler; +{ + assertChipStackLockedByCurrentThread(); + MTR_LOG_DEFAULT("BDX Transfer Session Begin: %@", fileDesignator); + + auto * download = [_downloads get:fileDesignator fabricIndex:fabricIndex nodeID:nodeID]; + VerifyOrReturn(nil != download, completion([MTRError errorForCHIPErrorCode:CHIP_ERROR_NOT_FOUND])); + + download.abortHandler = abortHandler; + completion(nil); +} + +- (void)handleBDXTransferSessionDataForFileDesignator:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + data:(NSData *)data + completion:(MTRStatusCompletion)completion +{ + assertChipStackLockedByCurrentThread(); + MTR_LOG_DEFAULT("BDX Transfer Session Data: %@: %@", fileDesignator, data); + + auto * download = [_downloads get:fileDesignator fabricIndex:fabricIndex nodeID:nodeID]; + VerifyOrReturn(nil != download, completion([MTRError errorForCHIPErrorCode:CHIP_ERROR_NOT_FOUND])); + + NSError * error = nil; + [download writeToFile:data error:&error]; + VerifyOrReturn(nil != error, completion(error)); + + completion(nil); +} + +- (void)handleBDXTransferSessionEndForFileDesignator:(NSString *)fileDesignator + fabricIndex:(NSNumber *)fabricIndex + nodeID:(NSNumber *)nodeID + error:(NSError * _Nullable)error +{ + assertChipStackLockedByCurrentThread(); + MTR_LOG_DEFAULT("BDX Transfer Session End: %@: %@", fileDesignator, error); + + auto * download = [_downloads get:fileDesignator fabricIndex:fabricIndex nodeID:nodeID]; + VerifyOrReturn(nil != download); + + VerifyOrReturn(nil == error, [download failure:error]); + [download success]; +} +@end + +DiagnosticLogsDownloaderBridge::DiagnosticLogsDownloaderBridge(MTRDiagnosticLogsDownloader * delegate) +{ + mDelegate = delegate; +} + +DiagnosticLogsDownloaderBridge::~DiagnosticLogsDownloaderBridge() +{ + mDelegate = nil; +} + +CHIP_ERROR DiagnosticLogsDownloaderBridge::OnTransferBegin(chip::bdx::BDXTransferProxy * transfer) +{ + VerifyOrReturnError(nil != mDelegate, CHIP_ERROR_INCORRECT_STATE); + + auto fileDesignatorSpan = transfer->GetFileDesignator(); + auto fileDesignator = AsString(fileDesignatorSpan); + VerifyOrReturnError(nil != fileDesignator, CHIP_ERROR_INCORRECT_STATE); + + auto * fabricIndex = @(transfer->GetFabricIndex()); + auto * nodeId = @(transfer->GetPeerNodeId()); + + auto completionHandler = ^(NSError * _Nullable error) { + assertChipStackLockedByCurrentThread(); + + if (error != nil) { + auto err = [MTRError errorToCHIPErrorCode:error]; + transfer->Reject(err); + } else { + transfer->Accept(); + } + }; + + // Ideally we would like to handle aborts a bit differently since this only works + // because our BDX stack supports one transfer at a time. + mAbortHandler = ^(NSError * error) { + assertChipStackLockedByCurrentThread(); + auto err = [MTRError errorToCHIPErrorCode:error]; + transfer->Reject(err); + }; + + [mDelegate handleBDXTransferSessionBeginForFileDesignator:fileDesignator + fabricIndex:fabricIndex + nodeID:nodeId + completion:completionHandler + abortHandler:mAbortHandler]; + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticLogsDownloaderBridge::OnTransferEnd(chip::bdx::BDXTransferProxy * transfer, CHIP_ERROR error) +{ + VerifyOrReturnError(nil != mDelegate, CHIP_ERROR_INCORRECT_STATE); + + auto fileDesignatorSpan = transfer->GetFileDesignator(); + auto fileDesignator = AsString(fileDesignatorSpan); + VerifyOrReturnError(nil != fileDesignator, CHIP_ERROR_INCORRECT_STATE); + + NSError * mtrError = nil; + if (CHIP_NO_ERROR != error) { + mtrError = [MTRError errorForCHIPErrorCode:error]; + } + + auto * fabricIndex = @(transfer->GetFabricIndex()); + auto * nodeId = @(transfer->GetPeerNodeId()); + [mDelegate handleBDXTransferSessionEndForFileDesignator:fileDesignator + fabricIndex:fabricIndex + nodeID:nodeId + error:mtrError]; + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticLogsDownloaderBridge::OnTransferData(chip::bdx::BDXTransferProxy * transfer, const chip::ByteSpan & dataSpan) +{ + VerifyOrReturnError(nil != mDelegate, CHIP_ERROR_INCORRECT_STATE); + + auto fileDesignatorSpan = transfer->GetFileDesignator(); + auto fileDesignator = AsString(fileDesignatorSpan); + VerifyOrReturnError(nil != fileDesignator, CHIP_ERROR_INCORRECT_STATE); + + auto * fabricIndex = @(transfer->GetFabricIndex()); + auto * nodeId = @(transfer->GetPeerNodeId()); + + auto data = AsData(dataSpan); + VerifyOrReturnError(nil != data, CHIP_ERROR_INCORRECT_STATE); + + auto completionHandler = ^(NSError * _Nullable error) { + assertChipStackLockedByCurrentThread(); + + if (error != nil) { + auto err = [MTRError errorToCHIPErrorCode:error]; + transfer->Reject(err); + } else { + transfer->Continue(); + } + }; + + mAbortHandler = nil; + + [mDelegate handleBDXTransferSessionDataForFileDesignator:fileDesignator + fabricIndex:fabricIndex + nodeID:nodeId + data:data + completion:completionHandler]; + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticLogsDownloaderBridge::StartBDXTransferTimeout(Download * download, uint16_t timeoutInSeconds) +{ + assertChipStackLockedByCurrentThread(); + return chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(timeoutInSeconds), OnTransferTimeout, (__bridge void *) download); +} + +void DiagnosticLogsDownloaderBridge::CancelBDXTransferTimeout(Download * download) +{ + assertChipStackLockedByCurrentThread(); + chip::DeviceLayer::SystemLayer().CancelTimer(OnTransferTimeout, (__bridge void *) download); +} + +void DiagnosticLogsDownloaderBridge::OnTransferTimeout(chip::System::Layer * layer, void * context) +{ + assertChipStackLockedByCurrentThread(); + + auto * download = (__bridge Download *) context; + VerifyOrReturn(nil != download); + + // If there is no abortHandler, it means that the BDX transfer has not started. + // When a BDX transfer has started we need to abort the transfer and we would error out + // at next poll. We would end up calling OnTransferEnd and eventually [download failure:error]. + // But if the transfer has not started we would stop right now. + auto error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_TIMEOUT]; + if (download.abortHandler == nil) { + [download failure:error]; + } else { + download.abortHandler(error); + } +} diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h b/src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h new file mode 100644 index 00000000000000..7d8675903329bb --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h @@ -0,0 +1,27 @@ +/** + * + * Copyright (c) 2024 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. + */ + +/** + * This enum is used to specify the type of log requested from this device. + * + * The log types are : End User Support, Network Diagnostics and Crash logs. + */ +typedef NS_ENUM(NSInteger, MTRDiagnosticLogType) { + MTRDiagnosticLogTypeEndUserSupport = 0, // End user support log is requested + MTRDiagnosticLogTypeNetworkDiagnostics = 1, // Network Diagnostics log is requested + MTRDiagnosticLogTypeCrash = 2 // Crash log is requested +} MTR_NEWLY_AVAILABLE; diff --git a/src/darwin/Framework/CHIP/Matter.h b/src/darwin/Framework/CHIP/Matter.h index 2cf62428bbfc2c..85f1399268f1a1 100644 --- a/src/darwin/Framework/CHIP/Matter.h +++ b/src/darwin/Framework/CHIP/Matter.h @@ -45,6 +45,7 @@ #import #import #import +#import #import #import #import diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 72565dd46f127f..b40ed8f3595e68 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -299,10 +299,13 @@ B45373FF2A9FEC4F00807602 /* unix-misc.c in Sources */ = {isa = PBXBuildFile; fileRef = B45373F82A9FEC4F00807602 /* unix-misc.c */; }; B45374002A9FEC4F00807602 /* unix-init.c in Sources */ = {isa = PBXBuildFile; fileRef = B45373F92A9FEC4F00807602 /* unix-init.c */; }; B45374012A9FEC4F00807602 /* unix-sockets.c in Sources */ = {isa = PBXBuildFile; fileRef = B45373FA2A9FEC4F00807602 /* unix-sockets.c */; }; + B4C8E6B72B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4C8E6B42B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm */; }; B4E262162AA0CF1C00DBA5BC /* RemoteDataModelLogger.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262122AA0C7A300DBA5BC /* RemoteDataModelLogger.mm */; }; B4E262172AA0CF2000DBA5BC /* RemoteDataModelLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = B4E262132AA0C7A300DBA5BC /* RemoteDataModelLogger.h */; }; B4E2621B2AA0D02000DBA5BC /* SleepCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */; }; B4E2621E2AA0D02D00DBA5BC /* WaitForCommissioneeCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */; }; + B4FCD56A2B5EDBD300832859 /* MTRDiagnosticLogsType.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FCD5692B5EDBD300832859 /* MTRDiagnosticLogsType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */; }; BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; }; D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -669,10 +672,13 @@ B45373F82A9FEC4F00807602 /* unix-misc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "unix-misc.c"; path = "repo/lib/plat/unix/unix-misc.c"; sourceTree = ""; }; B45373F92A9FEC4F00807602 /* unix-init.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "unix-init.c"; path = "repo/lib/plat/unix/unix-init.c"; sourceTree = ""; }; B45373FA2A9FEC4F00807602 /* unix-sockets.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "unix-sockets.c"; path = "repo/lib/plat/unix/unix-sockets.c"; sourceTree = ""; }; + B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDiagnosticLogsDownloader.h; sourceTree = ""; }; + B4C8E6B42B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDiagnosticLogsDownloader.mm; sourceTree = ""; }; B4E262122AA0C7A300DBA5BC /* RemoteDataModelLogger.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RemoteDataModelLogger.mm; sourceTree = ""; }; B4E262132AA0C7A300DBA5BC /* RemoteDataModelLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteDataModelLogger.h; sourceTree = ""; }; B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SleepCommand.mm; sourceTree = ""; }; B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WaitForCommissioneeCommand.mm; sourceTree = ""; }; + B4FCD5692B5EDBD300832859 /* MTRDiagnosticLogsType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDiagnosticLogsType.h; sourceTree = ""; }; BA09EB3F2474762900605257 /* libCHIP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libCHIP.a; path = lib/libCHIP.a; sourceTree = BUILT_PRODUCTS_DIR; }; BA107AEE2470CFBB004287EB /* chip_xcode_build_connector.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = chip_xcode_build_connector.sh; sourceTree = ""; }; D437613E285BDC0D0051FEA2 /* MTRErrorTestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRErrorTestUtils.h; sourceTree = ""; }; @@ -1105,6 +1111,9 @@ B202528F2459E34F00F97062 /* CHIP */ = { isa = PBXGroup; children = ( + B4FCD5692B5EDBD300832859 /* MTRDiagnosticLogsType.h */, + B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */, + B4C8E6B42B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm */, 1E4D655129C30A8700BC3478 /* MTRCommissionableBrowser.mm */, 1E4D654C29C208DD00BC3478 /* MTRCommissionableBrowser.h */, 1E4D654D29C208DD00BC3478 /* MTRCommissionableBrowserDelegate.h */, @@ -1411,6 +1420,7 @@ 5178E6812AE098520069DF72 /* MTRCommandTimedCheck.h in Headers */, 7596A84B287636C1004DAE0E /* MTRDevice_Internal.h in Headers */, 5A6FEC9927B5C88900F25F42 /* MTRDeviceOverXPC.h in Headers */, + B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */, 51B22C222740CB1D008D5055 /* MTRCommandPayloadsObjc.h in Headers */, 51B22C1E2740CB0A008D5055 /* MTRStructsObjc.h in Headers */, 2CB7163B252E8A7B0026E2BB /* MTRDeviceControllerDelegateBridge.h in Headers */, @@ -1460,6 +1470,7 @@ 998F286D26D55E10001846C6 /* MTRKeypair.h in Headers */, 1ED276E426C5832500547A89 /* MTRCluster.h in Headers */, 3D843711294977000070D20A /* NSStringSpanConversion.h in Headers */, + B4FCD56A2B5EDBD300832859 /* MTRDiagnosticLogsType.h in Headers */, 5A6FEC9A27B5C89300F25F42 /* MTRDeviceControllerXPCConnection.h in Headers */, 5129BCFD26A9EE3300122DDF /* MTRError.h in Headers */, 2C8C8FC1253E0C2100797F05 /* MTRStorage.h in Headers */, @@ -1736,6 +1747,7 @@ AF5F90FF2878D351005503FA /* MTROTAProviderDelegateBridge.mm in Sources */, 51E95DFC2A78443C00A434F0 /* MTRSessionResumptionStorageBridge.mm in Sources */, 7534F12828BFF20300390851 /* MTRDeviceAttestationDelegate.mm in Sources */, + B4C8E6B72B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm in Sources */, 2C5EEEF7268A85C400CAE3D3 /* MTRDeviceConnectionBridge.mm in Sources */, 51B22C262740CB32008D5055 /* MTRStructsObjc.mm in Sources */, 2C222AD1255C620600E446B9 /* MTRBaseDevice.mm in Sources */, From af748c7851114f7f5c5d3ba531421d3388421ca9 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Fri, 19 Jan 2024 19:10:27 +0100 Subject: [PATCH 2/2] [darwin-framework-tool] Add bdx commands to darwin-framework-tool --- examples/darwin-framework-tool/BUILD.gn | 2 + .../commands/bdx/Commands.h | 32 ++++++++++ .../commands/bdx/DownloadLogCommand.h | 45 ++++++++++++++ .../commands/bdx/DownloadLogCommand.mm | 58 +++++++++++++++++++ examples/darwin-framework-tool/main.mm | 2 + .../Matter.xcodeproj/project.pbxproj | 20 +++++++ 6 files changed, 159 insertions(+) create mode 100644 examples/darwin-framework-tool/commands/bdx/Commands.h create mode 100644 examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.h create mode 100644 examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.mm diff --git a/examples/darwin-framework-tool/BUILD.gn b/examples/darwin-framework-tool/BUILD.gn index 7ee396dd04e24a..1973c64a889e20 100644 --- a/examples/darwin-framework-tool/BUILD.gn +++ b/examples/darwin-framework-tool/BUILD.gn @@ -163,6 +163,8 @@ executable("darwin-framework-tool") { "${chip_root}/examples/chip-tool/commands/common/Commands.h", "${chip_root}/examples/chip-tool/commands/common/HexConversion.h", "${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp", + "commands/bdx/Commands.h", + "commands/bdx/DownloadLogCommand.mm", "commands/clusters/ClusterCommandBridge.h", "commands/clusters/ModelCommandBridge.mm", "commands/clusters/ReportCommandBridge.h", diff --git a/examples/darwin-framework-tool/commands/bdx/Commands.h b/examples/darwin-framework-tool/commands/bdx/Commands.h new file mode 100644 index 00000000000000..126ea055d03ba9 --- /dev/null +++ b/examples/darwin-framework-tool/commands/bdx/Commands.h @@ -0,0 +1,32 @@ +/* + * 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 "commands/bdx/DownloadLogCommand.h" +#include "commands/common/Commands.h" + +void registerCommandsBdx(Commands & commands) +{ + const char * clusterName = "Bdx"; + commands_list clusterCommands = { + make_unique(), // + }; + + commands.RegisterCommandSet(clusterName, clusterCommands, "Commands related to BDX"); +} diff --git a/examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.h b/examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.h new file mode 100644 index 00000000000000..ce48270d28b6ac --- /dev/null +++ b/examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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 "../common/CHIPCommandBridge.h" + +class DownloadLogCommand : public CHIPCommandBridge +{ +public: + DownloadLogCommand() : CHIPCommandBridge("download") + { + AddArgument("node-id", 0, UINT64_MAX, &mNodeId, "Node to download the logs from."); + AddArgument("log-type", 0, 2, &mLogType, + "The type of log being requested. This should correspond to a value in the enum MTRDiagnosticLogType."); + AddArgument("timeout", 0, UINT16_MAX, &mTimeout, + "The timeout for getting the log. If the timeout expires, completion will be called with whatever has been " + "retrieved by that point (which might be none or a partial log). If the timeout is set to 0, the request will " + "not expire and completion will not be called until the log is fully retrieved or an error occurs."); + } + + /////////// CHIPCommandBridge Interface ///////// + CHIP_ERROR RunCommand() override; + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + +private: + chip::NodeId mNodeId; + uint8_t mLogType; + uint16_t mTimeout; +}; diff --git a/examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.mm b/examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.mm new file mode 100644 index 00000000000000..494ec964f14b63 --- /dev/null +++ b/examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.mm @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 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 + +#import "MTRError_Utils.h" + +#include "DownloadLogCommand.h" + +CHIP_ERROR DownloadLogCommand::RunCommand() +{ + ChipLogProgress(chipTool, "Downloading logs from node 0x" ChipLogFormatX64, ChipLogValueX64(mNodeId)); + + MTRDeviceController * commissioner = CurrentCommissioner(); + auto * device = [MTRDevice deviceWithNodeID:@(mNodeId) controller:commissioner]; + + auto logType = static_cast(mLogType); + auto queue = dispatch_queue_create("com.chip.bdx.downloader", DISPATCH_QUEUE_SERIAL); + + auto * self = this; + auto completion = ^(NSURL * url, NSError * error) { + // A non-nil url indicates the presence of content, which can occur even in error scenarios like timeouts. + if (nil != url) { + NSError * readError = nil; + auto * data = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&readError]; + VerifyOrReturn(nil == readError, self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(readError))); + + auto * content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSLog(@"Content: %@", content); + } + + VerifyOrReturn(nil == error, self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(error))); + + // The url is nil when there are no logs on the target device. + if (nil == url) { + NSLog(@"No logs has been found onto node 0x" ChipLogFormatX64, ChipLogValueX64(mNodeId)); + } + self->SetCommandExitStatus(CHIP_NO_ERROR); + }; + + [device downloadLogOfType:logType timeout:mTimeout queue:queue completion:completion]; + return CHIP_NO_ERROR; +} diff --git a/examples/darwin-framework-tool/main.mm b/examples/darwin-framework-tool/main.mm index 8c96d490dc5ed8..070ae3188407d4 100644 --- a/examples/darwin-framework-tool/main.mm +++ b/examples/darwin-framework-tool/main.mm @@ -20,6 +20,7 @@ #import "logging/logging.h" +#include "commands/bdx/Commands.h" #include "commands/common/Commands.h" #include "commands/delay/Commands.h" #include "commands/discover/Commands.h" @@ -38,6 +39,7 @@ int main(int argc, const char * argv[]) dft::logging::Setup(); Commands commands; + registerCommandsBdx(commands); registerCommandsPairing(commands); registerCommandsDelay(commands); registerCommandsDiscover(commands); diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index b40ed8f3595e68..ea1b9b8bf338c4 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -306,6 +306,9 @@ B4E2621E2AA0D02D00DBA5BC /* WaitForCommissioneeCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */; }; B4FCD56A2B5EDBD300832859 /* MTRDiagnosticLogsType.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FCD5692B5EDBD300832859 /* MTRDiagnosticLogsType.h */; settings = {ATTRIBUTES = (Public, ); }; }; B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */; }; + B4FCD5702B603A6300832859 /* Commands.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FCD56D2B603A6300832859 /* Commands.h */; }; + B4FCD5712B603A6300832859 /* DownloadLogCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FCD56E2B603A6300832859 /* DownloadLogCommand.h */; }; + B4FCD5722B603A6300832859 /* DownloadLogCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4FCD56F2B603A6300832859 /* DownloadLogCommand.mm */; }; BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; }; D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -679,6 +682,9 @@ B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SleepCommand.mm; sourceTree = ""; }; B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WaitForCommissioneeCommand.mm; sourceTree = ""; }; B4FCD5692B5EDBD300832859 /* MTRDiagnosticLogsType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDiagnosticLogsType.h; sourceTree = ""; }; + B4FCD56D2B603A6300832859 /* Commands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Commands.h; sourceTree = ""; }; + B4FCD56E2B603A6300832859 /* DownloadLogCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DownloadLogCommand.h; sourceTree = ""; }; + B4FCD56F2B603A6300832859 /* DownloadLogCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DownloadLogCommand.mm; sourceTree = ""; }; BA09EB3F2474762900605257 /* libCHIP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libCHIP.a; path = lib/libCHIP.a; sourceTree = BUILT_PRODUCTS_DIR; }; BA107AEE2470CFBB004287EB /* chip_xcode_build_connector.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = chip_xcode_build_connector.sh; sourceTree = ""; }; D437613E285BDC0D0051FEA2 /* MTRErrorTestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRErrorTestUtils.h; sourceTree = ""; }; @@ -747,6 +753,7 @@ 037C3D7B2991BD4F00B7EEE2 /* commands */ = { isa = PBXGroup; children = ( + B4FCD56C2B603A6300832859 /* bdx */, B4E262182AA0CFFE00DBA5BC /* delay */, 03FB93DA2A46200A0048CB35 /* discover */, 037C3D7C2991BD4F00B7EEE2 /* pairing */, @@ -1348,6 +1355,16 @@ path = delay; sourceTree = ""; }; + B4FCD56C2B603A6300832859 /* bdx */ = { + isa = PBXGroup; + children = ( + B4FCD56D2B603A6300832859 /* Commands.h */, + B4FCD56E2B603A6300832859 /* DownloadLogCommand.h */, + B4FCD56F2B603A6300832859 /* DownloadLogCommand.mm */, + ); + path = bdx; + sourceTree = ""; + }; BA09EB3E2474762900605257 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -1377,6 +1394,7 @@ 037C3DCE2991BD5100B7EEE2 /* CHIPCommandBridge.h in Headers */, 037C3DD22991BD5200B7EEE2 /* InteractiveCommands.h in Headers */, 037C3DAF2991BD4F00B7EEE2 /* DeviceControllerDelegateBridge.h in Headers */, + B4FCD5712B603A6300832859 /* DownloadLogCommand.h in Headers */, 037C3DC32991BD5100B7EEE2 /* Commands.h in Headers */, 037C3DB82991BD5000B7EEE2 /* ClusterCommandBridge.h in Headers */, 037C3DC82991BD5100B7EEE2 /* CHIPToolKeypair.h in Headers */, @@ -1389,6 +1407,7 @@ 037C3DB92991BD5000B7EEE2 /* ReportCommandBridge.h in Headers */, 037C3DBE2991BD5000B7EEE2 /* OTASoftwareUpdateInteractive.h in Headers */, 037C3DBD2991BD5000B7EEE2 /* OTAProviderDelegate.h in Headers */, + B4FCD5702B603A6300832859 /* Commands.h in Headers */, 037C3DB02991BD4F00B7EEE2 /* Commands.h in Headers */, 037C3DC02991BD5100B7EEE2 /* Commands.h in Headers */, B4E262172AA0CF2000DBA5BC /* RemoteDataModelLogger.h in Headers */, @@ -1654,6 +1673,7 @@ B45373C12A9FEA9100807602 /* close.c in Sources */, 039546A62991E151006D42A8 /* InteractionModel.cpp in Sources */, B45373E72A9FEBA400807602 /* parsers.c in Sources */, + B4FCD5722B603A6300832859 /* DownloadLogCommand.mm in Sources */, B45373BF2A9FEA9100807602 /* adopt.c in Sources */, B45373F32A9FEC1A00807602 /* server-ws.c in Sources */, 03F430AA2994113500166449 /* sysunix.c in Sources */,