Skip to content

Commit

Permalink
Add a method for validating data-value dictionaries. (#36207)
Browse files Browse the repository at this point in the history
  • Loading branch information
bzbarsky-apple authored Oct 23, 2024
1 parent 631ad43 commit a0d7b14
Show file tree
Hide file tree
Showing 4 changed files with 413 additions and 21 deletions.
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

0 comments on commit a0d7b14

Please sign in to comment.