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 a method for validating data-value dictionaries. #36207

Merged
merged 1 commit into from
Oct 23, 2024
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
55 changes: 35 additions & 20 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,9 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue
}
}

static CHIP_ERROR MTREncodeTLVFromDataValueDictionaryInternal(id object, chip::TLV::TLVWriter & writer, chip::TLV::Tag tag)
// writer is allowed to be null to just validate the incoming object without
// actually encoding.
static CHIP_ERROR MTREncodeTLVFromDataValueDictionaryInternal(id object, chip::TLV::TLVWriter * writer, chip::TLV::Tag tag)
{
if (![object isKindOfClass:[NSDictionary class]]) {
MTR_LOG_ERROR("Error: Unsupported object to encode: %@", [object class]);
Expand All @@ -631,60 +633,62 @@ static CHIP_ERROR MTREncodeTLVFromDataValueDictionaryInternal(id object, chip::T
MTR_LOG_ERROR("Error: Object to encode has corrupt signed integer type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
return writer.Put(tag, [value longLongValue]);
return writer ? writer->Put(tag, [value longLongValue]) : CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTRUnsignedIntegerValueType]) {
if (![value isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("Error: Object to encode has corrupt unsigned integer type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
return writer.Put(tag, [value unsignedLongLongValue]);
return writer ? writer->Put(tag, [value unsignedLongLongValue]) : CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTRBooleanValueType]) {
if (![value isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("Error: Object to encode has corrupt boolean type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
return writer.Put(tag, static_cast<bool>([value boolValue]));
return writer ? writer->Put(tag, static_cast<bool>([value boolValue])) : CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTRFloatValueType]) {
if (![value isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("Error: Object to encode has corrupt float type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
return writer.Put(tag, [value floatValue]);
return writer ? writer->Put(tag, [value floatValue]) : CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTRDoubleValueType]) {
if (![value isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("Error: Object to encode has corrupt double type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
return writer.Put(tag, [value doubleValue]);
return writer ? writer->Put(tag, [value doubleValue]) : CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTRNullValueType]) {
return writer.PutNull(tag);
return writer ? writer->PutNull(tag) : CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTRUTF8StringValueType]) {
if (![value isKindOfClass:[NSString class]]) {
MTR_LOG_ERROR("Error: Object to encode has corrupt UTF8 string type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
return writer.PutString(tag, AsCharSpan(value));
return writer ? writer->PutString(tag, AsCharSpan(value)) : CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTROctetStringValueType]) {
if (![value isKindOfClass:[NSData class]]) {
MTR_LOG_ERROR("Error: Object to encode has corrupt octet string type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
return writer.Put(tag, AsByteSpan(value));
return writer ? writer->Put(tag, AsByteSpan(value)) : CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTRStructureValueType]) {
if (![value isKindOfClass:[NSArray class]]) {
MTR_LOG_ERROR("Error: Object to encode has corrupt structure type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
TLV::TLVType outer;
ReturnErrorOnFailure(writer.StartContainer(tag, chip::TLV::kTLVType_Structure, outer));
TLV::TLVType outer = TLV::kTLVType_NotSpecified;
if (writer) {
ReturnErrorOnFailure(writer->StartContainer(tag, chip::TLV::kTLVType_Structure, outer));
}
for (id element in value) {
if (![element isKindOfClass:[NSDictionary class]]) {
MTR_LOG_ERROR("Error: Structure element to encode has corrupt type: %@", [element class]);
Expand Down Expand Up @@ -713,16 +717,20 @@ static CHIP_ERROR MTREncodeTLVFromDataValueDictionaryInternal(id object, chip::T
ReturnErrorOnFailure(
MTREncodeTLVFromDataValueDictionaryInternal(elementValue, writer, tag));
}
ReturnErrorOnFailure(writer.EndContainer(outer));
if (writer) {
ReturnErrorOnFailure(writer->EndContainer(outer));
}
return CHIP_NO_ERROR;
}
if ([typeName isEqualToString:MTRArrayValueType]) {
if (![value isKindOfClass:[NSArray class]]) {
MTR_LOG_ERROR("Error: Object to encode has corrupt array type: %@", [value class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
TLV::TLVType outer;
ReturnErrorOnFailure(writer.StartContainer(tag, chip::TLV::kTLVType_Array, outer));
TLV::TLVType outer = TLV::kTLVType_NotSpecified;
if (writer) {
ReturnErrorOnFailure(writer->StartContainer(tag, chip::TLV::kTLVType_Array, outer));
}
for (id element in value) {
if (![element isKindOfClass:[NSDictionary class]]) {
MTR_LOG_ERROR("Error: Array element to encode has corrupt type: %@", [element class]);
Expand All @@ -735,14 +743,16 @@ static CHIP_ERROR MTREncodeTLVFromDataValueDictionaryInternal(id object, chip::T
}
ReturnErrorOnFailure(MTREncodeTLVFromDataValueDictionaryInternal(elementValue, writer, chip::TLV::AnonymousTag()));
}
ReturnErrorOnFailure(writer.EndContainer(outer));
if (writer) {
ReturnErrorOnFailure(writer->EndContainer(outer));
}
return CHIP_NO_ERROR;
}
MTR_LOG_ERROR("Error: Unsupported type to encode: %@", typeName);
return CHIP_ERROR_INVALID_ARGUMENT;
}

static CHIP_ERROR MTREncodeTLVFromDataValueDictionary(id object, chip::TLV::TLVWriter & writer, chip::TLV::Tag tag)
static CHIP_ERROR MTREncodeTLVFromDataValueDictionary(id object, chip::TLV::TLVWriter * writer, chip::TLV::Tag tag)
{
CHIP_ERROR err = MTREncodeTLVFromDataValueDictionaryInternal(object, writer, tag);
if (err != CHIP_NO_ERROR) {
Expand All @@ -761,7 +771,7 @@ static CHIP_ERROR MTREncodeTLVFromDataValueDictionary(id object, chip::TLV::TLVW
TLV::TLVWriter writer;
writer.Init(buffer);

CHIP_ERROR err = MTREncodeTLVFromDataValueDictionary(value, writer, TLV::AnonymousTag());
CHIP_ERROR err = MTREncodeTLVFromDataValueDictionary(value, &writer, TLV::AnonymousTag());
if (err != CHIP_NO_ERROR) {
if (error) {
*error = [MTRError errorForCHIPErrorCode:err];
Expand All @@ -772,6 +782,11 @@ static CHIP_ERROR MTREncodeTLVFromDataValueDictionary(id object, chip::TLV::TLVW
return AsData(ByteSpan(buffer, writer.GetLengthWritten()));
}

BOOL MTRDataValueDictionaryIsWellFormed(MTRDeviceDataValueDictionary value)
{
return MTREncodeTLVFromDataValueDictionary(value, nullptr, TLV::AnonymousTag()) == CHIP_NO_ERROR;
}

// Callback type to pass data value as an NSObject
typedef void (*MTRDataValueDictionaryCallback)(void * context, id value);

Expand All @@ -798,7 +813,7 @@ CHIP_ERROR Decode(chip::TLV::TLVReader & data)

CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const
{
return MTREncodeTLVFromDataValueDictionary(decodedObj, writer, tag);
return MTREncodeTLVFromDataValueDictionary(decodedObj, &writer, tag);
}

static constexpr bool kIsFabricScoped = false;
Expand Down Expand Up @@ -2212,7 +2227,7 @@ + (NSDictionary *)eventReportForHeader:(const chip::app::EventHeader &)header an
// 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());
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();
Expand Down Expand Up @@ -3082,7 +3097,7 @@ static bool EncodeDataValueToTLV(System::PacketBufferHandle & buffer, Platform::
System::PacketBufferTLVWriter writer;
writer.Init(std::move(buffer), /* useChainedBuffers = */ true);

CHIP_ERROR errorCode = MTREncodeTLVFromDataValueDictionary(data, writer, TLV::AnonymousTag());
CHIP_ERROR errorCode = MTREncodeTLVFromDataValueDictionary(data, &writer, TLV::AnonymousTag());
if (errorCode != CHIP_NO_ERROR) {
LogStringAndReturnError(@"Unable to encode data-value to TLV", errorCode, error);
return false;
Expand Down
4 changes: 3 additions & 1 deletion src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#import "MTRBaseDevice.h"
#import <Foundation/Foundation.h>

#import "MTRDeviceDataValueDictionary.h"

#include <app/AttributePathParams.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteCommandPath.h>
Expand Down Expand Up @@ -257,6 +259,6 @@ NSDictionary<NSString *, id> * _Nullable MTRDecodeDataValueDictionaryFromCHIPTLV
// TLV Data with an anonymous tag. This method assumes the encoding of the
// value fits in a single UDP MTU; for lists this method might need to be used
// on each list item separately.
NSData * _Nullable MTREncodeTLVFromDataValueDictionary(NSDictionary<NSString *, id> * value, NSError * __autoreleasing * error);
NSData * _Nullable MTREncodeTLVFromDataValueDictionary(MTRDeviceDataValueDictionary value, NSError * __autoreleasing * error);

NS_ASSUME_NONNULL_END
7 changes: 7 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ MTR_DIRECT_MEMBERS
- (void)devicePrivateInternalStateChanged:(MTRDevice *)device internalState:(NSDictionary *)state;
@end

// Returns whether a data-value dictionary is well-formed (in the sense that all
// the types of the objects inside are as expected, so it's actually a valid
// representation of some TLV). Implemented in MTRBaseDevice.mm because that's
// where the pieces needed to implement it are, but declared here so our tests
// can see it.
MTR_EXTERN MTR_TESTABLE BOOL MTRDataValueDictionaryIsWellFormed(MTRDeviceDataValueDictionary value);

#pragma mark - Constants

static NSString * const kDefaultSubscriptionPoolSizeOverrideKey = @"subscriptionPoolSizeOverride";
Expand Down
Loading
Loading