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

Add methods for doing schema checks on response-value command payload dictionaries. #26586

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
61 changes: 61 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ static void CauseReadClientFailure(
}
#endif

static bool CheckMemberOfType(NSDictionary<NSString *, id> * responseValue, NSString * memberName, Class expectedClass,
NSString * errorMessage, NSError * __autoreleasing * error);
static void LogStringAndReturnError(NSString * errorStr, CHIP_ERROR errorCode, NSError * __autoreleasing * error);
static void LogStringAndReturnError(NSString * errorStr, MTRErrorCode errorCode, NSError * __autoreleasing * error);

@implementation MTRReadClientContainer
- (void)onDone
{
Expand Down Expand Up @@ -1807,6 +1812,62 @@ + (NSDictionary *)eventReportForHeader:(const chip::app::EventHeader &)header an
timestampKey : timestampValue
};
}

+ (System::PacketBufferHandle)_responseDataForCommand:(NSDictionary<NSString *, id> *)responseValue
clusterID:(chip::ClusterId)clusterID
commandID:(chip::CommandId)commandID
error:(NSError * __autoreleasing *)error
{
if (!CheckMemberOfType(responseValue, MTRCommandPathKey, [MTRCommandPath class],
@"response-value command path is not an MTRCommandPath.", error)) {
return System::PacketBufferHandle();
}

MTRCommandPath * path = responseValue[MTRCommandPathKey];

if (![path.cluster isEqualToNumber:@(clusterID)]) {
LogStringAndReturnError([NSString stringWithFormat:@"Expected cluster id %@ but got %@", path.cluster, @(clusterID)],
MTRErrorCodeSchemaMismatch, error);
return System::PacketBufferHandle();
}

if (![path.command isEqualToNumber:@(commandID)]) {
LogStringAndReturnError([NSString stringWithFormat:@"Expected command id %@ but got %@", path.command, @(commandID)],
MTRErrorCodeSchemaMismatch, error);
return System::PacketBufferHandle();
}

if (!CheckMemberOfType(
responseValue, MTRDataKey, [NSDictionary class], @"response-value data is not a data-value dictionary.", error)) {
return System::PacketBufferHandle();
}

NSDictionary * data = responseValue[MTRDataKey];

auto buffer = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSizeWithoutReserve, 0);
if (buffer.IsNull()) {
LogStringAndReturnError(@"Unable to allocate encoding buffer", CHIP_ERROR_NO_MEMORY, error);
return System::PacketBufferHandle();
}

System::PacketBufferTLVWriter writer;
// Commands never need chained buffers, since they cannot be chunked.
writer.Init(std::move(buffer), /* useChainedBuffers = */ false);

CHIP_ERROR errorCode = MTREncodeTLVFromDataValueDictionary(data, writer, TLV::AnonymousTag());
if (errorCode != CHIP_NO_ERROR) {
LogStringAndReturnError(@"Unable to encode data-value to TLV", errorCode, error);
return System::PacketBufferHandle();
}

errorCode = writer.Finalize(&buffer);
if (errorCode != CHIP_NO_ERROR) {
LogStringAndReturnError(@"Unable to encode data-value to TLV", errorCode, error);
return System::PacketBufferHandle();
}

return buffer;
}
@end

@implementation MTRBaseDevice (Deprecated)
Expand Down
14 changes: 14 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <app/EventHeader.h>
#include <app/EventLoggingTypes.h>
#include <app/EventPathParams.h>
#include <system/SystemPacketBuffer.h>

@class MTRDeviceController;

Expand Down Expand Up @@ -83,6 +84,19 @@ static inline MTRTransportType MTRMakeTransportType(chip::Transport::Type type)
* (e.g. when TLV decoding failed).
*/
+ (NSDictionary *)eventReportForHeader:(const chip::app::EventHeader &)header andData:(id _Nullable)data;

/**
* Extract a data-value for the given response command from the given response-value
* dictionary, encode it to TLV, and return a System::PacketBufferHandle with
* the encoded data.
*
* Will return a null handle and an error if the given response-value does not represent a
* data command response or is the wrong response command, or if encoding to TLV fails.
*/
+ (chip::System::PacketBufferHandle)_responseDataForCommand:(NSDictionary<NSString *, id> *)responseValue
clusterID:(chip::ClusterId)clusterID
commandID:(chip::CommandId)commandID
error:(NSError * __autoreleasing *)error;
@end

@interface MTRClusterPath ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#import "MTRCallbackBridge.h"
#import "MTRStructsObjc.h"
#import "MTRCommandPayloadsObjc.h"
#import "MTRCommandPayloads_Internal.h"

#include <lib/support/TypeTraits.h>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{{> header excludeZapComment=true}}

#import "MTRCommandPayloadsObjc.h"
#import "MTRCommandPayloads_Internal.h"
#import "MTRBaseDevice_Internal.h"
#import "MTRError_Internal.h"
#import "MTRLogging_Internal.h"

#include <lib/core/TLV.h>
#include <app/data-model/Decode.h>

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -58,6 +65,48 @@ NS_ASSUME_NONNULL_BEGIN
{{/if}}
{{/zcl_command_arguments}}

{{#if (isStrEqual source "server")}}
- (nullable instancetype)initWithResponseValue:(NSDictionary<NSString *, id> *)responseValue
error:(NSError * __autoreleasing *)error
{
if (!(self = [super init])) {
return nil;
}

using DecodableType = chip::app::Clusters::{{asUpperCamelCase parent.name}}::Commands::{{asUpperCamelCase name}}::DecodableType;
chip::System::PacketBufferHandle buffer = [MTRBaseDevice _responseDataForCommand:responseValue
bzbarsky-apple marked this conversation as resolved.
Show resolved Hide resolved
clusterID:DecodableType::GetClusterId()
commandID:DecodableType::GetCommandId()
error:error];
if (buffer.IsNull()) {
return nil;
}

chip::TLV::TLVReader reader;
reader.Init(buffer->Start(), buffer->DataLength());

CHIP_ERROR err = reader.Next(chip::TLV::AnonymousTag());
if (err == CHIP_NO_ERROR) {
DecodableType decodedStruct;
err = chip::app::DataModel::Decode(reader, decodedStruct);
if (err == CHIP_NO_ERROR) {
err = [self _setFieldsFromDecodableStruct:decodedStruct];
if (err == CHIP_NO_ERROR) {
return self;
}
}
}

NSString * errorStr = [NSString stringWithFormat:@"Command payload decoding failed: %s", err.AsString()];
MTR_LOG_ERROR("%s", errorStr.UTF8String);
if (error != nil) {
NSDictionary * userInfo = @ { NSLocalizedFailureReasonErrorKey : NSLocalizedString(errorStr, nil) };
*error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeSchemaMismatch userInfo:userInfo];
}
return nil;
}
{{/if}}

@end
{{/if}}
{{/inline}}
Expand All @@ -70,6 +119,21 @@ NS_ASSUME_NONNULL_BEGIN
{{> completeImpl cluster=(asUpperCamelCase parent.name preserveAcronyms=true)
command=(asUpperCamelCase name preserveAcronyms=true)
includeRenamedProperties=false}}
{{#if (isStrEqual source "server")}}

@implementation MTR{{asUpperCamelCase parent.name preserveAcronyms=true}}Cluster{{asUpperCamelCase name preserveAcronyms=true}}Params (InternalMethods)

- (CHIP_ERROR)_setFieldsFromDecodableStruct:(const chip::app::Clusters::{{asUpperCamelCase parent.name}}::Commands::{{asUpperCamelCase name}}::DecodableType &)decodableStruct
{
{{#zcl_command_arguments}}
{
{{>decode_value target=(concat "self." (asStructPropertyName label)) source=(concat "decodableStruct." (asLowerCamelCase label)) cluster=parent.parent.name errorCode="return err;" depth=0}}
}
{{/zcl_command_arguments}}
return CHIP_NO_ERROR;
}
@end
{{/if}}
{{#if (or (not (isStrEqual (asUpperCamelCase parent.name preserveAcronyms=true) (compatClusterNameRemapping parent.name)))
(not (isStrEqual (asUpperCamelCase name preserveAcronyms=true) (compatCommandNameRemapping parent.name name))))}}
{{> oldNameImpl cluster=(compatClusterNameRemapping parent.name)
Expand Down
15 changes: 15 additions & 0 deletions src/darwin/Framework/CHIP/templates/MTRCommandPayloadsObjc.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, copy, nullable) NSNumber * timedInvokeTimeoutMs {{availability "" api="Timed Invoke for server to client commands" deprecationMessage="Timed invoke does not make sense for server to client commands"}};
{{/if}}
{{#if (isStrEqual source "server")}}

/**
* Initialize an MTR{{cluster}}Cluster{{command}}Params with a response-value dictionary
* of the sort that MTRDeviceResponseHandler would receive.
*
* Will return nil and hand out an error if the response-value dictionary is not
* a command data response or is not the right command response.
*
* Will return nil and hand out an error if the data response does not match the known
* schema for this command.
*/
- (nullable instancetype)initWithResponseValue:(NSDictionary<NSString *, id> *)responseValue
error:(NSError * __autoreleasing *)error MTR_NEWLY_AVAILABLE;
{{/if}}
@end
{{/if}}
{{/inline}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{{> header excludeZapComment=true}}

#import <Matter/MTRDefines.h>

#include <app-common/zap-generated/cluster-objects.h>

NS_ASSUME_NONNULL_BEGIN

{{#zcl_clusters}}
{{#zcl_commands}}
{{! We only need to generate conversion functions for the server-generated commands }}
{{#if (isStrEqual source "server")}}
{{#if (isSupported (asUpperCamelCase parent.name preserveAcronyms=true) command=(asUpperCamelCase name preserveAcronyms=true) isForCommandPayload=true)}}

@interface MTR{{asUpperCamelCase parent.name preserveAcronyms=true}}Cluster{{asUpperCamelCase name preserveAcronyms=true}}Params (InternalMethods)

- (CHIP_ERROR)_setFieldsFromDecodableStruct:(const chip::app::Clusters::{{asUpperCamelCase parent.name}}::Commands::{{asUpperCamelCase name}}::DecodableType &)decodableStruct;

@end

{{/if}}
{{/if}}
{{/zcl_commands}}
{{/zcl_clusters}}

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ void MTR{{> @partial-block}}Bridge::OnSuccessFn(void * context
DispatchSuccess(context, nil);
{{else if (isStrEqual partial-type "Command")}}
auto * response = [MTR{{asUpperCamelCase parent.name preserveAcronyms=true}}Cluster{{asUpperCamelCase name preserveAcronyms=true}}Params new];
{{#zcl_command_arguments}}
{
{{>decode_value target=(concat "response." (asStructPropertyName label)) source=(concat "data." (asLowerCamelCase label)) cluster=parent.parent.name errorCode="OnFailureFn(context, err); return;" depth=0}}
CHIP_ERROR err = [response _setFieldsFromDecodableStruct:data];
if (err != CHIP_NO_ERROR) {
OnFailureFn(context, err);
return;
}
{{/zcl_command_arguments}}
DispatchSuccess(context, response);
{{else if (isStrEqual partial-type "CommandStatus")}}
DispatchSuccess(context, nil);
Expand Down
5 changes: 5 additions & 0 deletions src/darwin/Framework/CHIP/templates/templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@
"name": "Objc reflections of MTR command payloads header",
"output": "src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm"
},
{
"path": "MTRCommandPayloads_Internal.zapt",
"name": "Internal methods for objc reflections of MTR command payloads header",
"output": "src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloads_Internal.h"
},
{
"path": "MTRAttributeTLVValueDecoder-src.zapt",
"name": "Decode TLV attribute values into Objc objects",
Expand Down
Loading