Skip to content

Commit

Permalink
Add methods for doing schema checks on response-value command payload…
Browse files Browse the repository at this point in the history
… dictionaries. (#26586)

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

This adds a way to initialize strongly typed command response structs with a
response-value dictionary representing a command response.

* Fix typo in error message.

Co-authored-by: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com>

---------

Co-authored-by: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com>
  • Loading branch information
2 people authored and pull[bot] committed Aug 24, 2023
1 parent a666a4f commit f4154bf
Show file tree
Hide file tree
Showing 14 changed files with 5,944 additions and 1,396 deletions.
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
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

0 comments on commit f4154bf

Please sign in to comment.