blob: 50d446b780ad1bc2362c2970d00d496ca6ac9f91 [file] [log] [blame]
/*
* 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.
*
*/
#include "RemoteDataModelLogger.h"
#import "MTRError_Utils.h"
#import <objc/runtime.h>
#include <json/json.h>
#include <lib/support/SafeInt.h>
#include <protocols/interaction_model/StatusCode.h>
#include <string>
constexpr char kAttributePathKey[] = "attributePath";
constexpr char kCommandPathKey[] = "commandPath";
constexpr char kClusterIdKey[] = "clusterId";
constexpr char kEndpointIdKey[] = "endpointId";
constexpr char kAttributeIdKey[] = "attributeId";
constexpr char kCommandIdKey[] = "commandId";
constexpr char kErrorIdKey[] = "error";
constexpr char kClusterErrorIdKey[] = "clusterError";
constexpr char kValueKey[] = "value";
constexpr char kNodeIdKey[] = "nodeId";
constexpr char kLogContentIdKey[] = "logContent";
constexpr char kBase64Header[] = "base64:";
namespace {
RemoteDataModelLoggerDelegate * gDelegate;
std::string JsonToString(Json::Value & json)
{
Json::FastWriter writer;
writer.omitEndingLineFeed();
return writer.write(json);
}
CHIP_ERROR LogError(Json::Value & value, const chip::app::StatusIB & status)
{
if (status.mClusterStatus.HasValue()) {
auto statusValue = status.mClusterStatus.Value();
value[kClusterErrorIdKey] = statusValue;
}
#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
auto statusName = chip::Protocols::InteractionModel::StatusName(status.mStatus);
value[kErrorIdKey] = statusName;
#else
auto statusName = status.mStatus;
value[kErrorIdKey] = chip::to_underlying(statusName);
#endif // CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
auto valueStr = JsonToString(value);
return gDelegate->LogJSON(valueStr.c_str());
}
CHIP_ERROR AsJsonValue(id value, Json::Value & jsonValue)
{
if (value == nil) {
jsonValue = Json::nullValue;
} else if ([value isKindOfClass:[NSNumber class]]) {
if (CFNumberIsFloatType((CFNumberRef) value)) {
jsonValue = [value doubleValue];
} else if ([[value stringValue] hasPrefix:@"-"]) {
jsonValue = [value longLongValue];
} else {
jsonValue = [value unsignedLongLongValue];
}
} else if ([value isKindOfClass:[NSArray class]]) {
jsonValue = Json::arrayValue;
NSArray * array = value;
for (id element in array) {
Json::Value jsonElement;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue(element, jsonElement));
jsonValue.append(jsonElement);
}
} else if ([value isKindOfClass:[NSDictionary class]]) {
jsonValue = Json::ValueType::objectValue;
NSDictionary * dict = value;
for (id key in dict) {
Json::Value jsonElement;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue([dict objectForKey:key], jsonElement));
jsonValue[[key UTF8String]] = jsonElement;
}
} else if ([value isKindOfClass:[NSData class]]) {
NSData * data = value;
data = [data base64EncodedDataWithOptions:0];
auto base64Str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
auto prefix = [NSString stringWithUTF8String:kBase64Header];
auto base64PrefixedStr = [prefix stringByAppendingString:base64Str];
jsonValue = [base64PrefixedStr UTF8String];
} else if ([value isKindOfClass:[NSString class]]) {
jsonValue = [value UTF8String];
} else if ([value isKindOfClass:[NSObject class]]) {
jsonValue = Json::ValueType::objectValue;
unsigned int numberOfProperties;
objc_property_t * properties = class_copyPropertyList([value class], &numberOfProperties);
for (NSUInteger i = 0; i < numberOfProperties; i++) {
objc_property_t property = properties[i];
NSString * key = [[NSString alloc] initWithUTF8String:property_getName(property)];
Json::Value jsonElement;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue([value valueForKey:key], jsonElement));
jsonValue[[key UTF8String]] = jsonElement;
}
free(properties);
} else {
return CHIP_ERROR_NOT_IMPLEMENTED;
}
return CHIP_NO_ERROR;
}
} // namespace
namespace RemoteDataModelLogger {
CHIP_ERROR LogAttributeAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * attributeId, id result)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);
Json::Value value;
value[kEndpointIdKey] = [endpointId unsignedLongLongValue];
value[kClusterIdKey] = [clusterId unsignedLongLongValue];
value[kAttributeIdKey] = [attributeId unsignedLongLongValue];
Json::Value jsonValue;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue(result, jsonValue));
// When using the *-by-id commands, the return format is always encapsulated
// within an array. If the attribute itself is an empty array, the result will
// appear as follows:
//
//[
// {
// attributePath = "<MTRAttributePath endpoint 0 cluster 0x41 (65, UserLabel) 0x0 (0, LabelList)>";
// data = {
// type = Array;
// value = (
// );
// };
// }
//]
bool hasData = false;
if (jsonValue.isArray() && !jsonValue.empty()) {
for (Json::ArrayIndex i = 0; i < jsonValue.size(); i++) {
if (jsonValue[i].isObject()) {
if (jsonValue[i].isMember(kAttributePathKey)) {
jsonValue[i].removeMember(kAttributePathKey);
}
if (!jsonValue[i].empty()) {
hasData = true;
}
} else {
hasData = true;
}
}
} else {
hasData = true;
}
if (!hasData) {
return CHIP_NO_ERROR;
}
value[kValueKey] = jsonValue;
auto valueStr = JsonToString(value);
return gDelegate->LogJSON(valueStr.c_str());
}
CHIP_ERROR LogCommandAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * commandId, id result)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);
Json::Value value;
value[kEndpointIdKey] = [endpointId unsignedLongLongValue];
value[kClusterIdKey] = [clusterId unsignedLongLongValue];
value[kCommandIdKey] = [commandId unsignedLongLongValue];
Json::Value jsonValue;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue(result, jsonValue));
bool hasData = false;
if (jsonValue.isArray()) {
for (Json::ArrayIndex i = 0; i < jsonValue.size(); i++) {
if (jsonValue[i].isObject()) {
if (jsonValue[i].isMember(kCommandPathKey)) {
jsonValue[i].removeMember(kCommandPathKey);
}
if (!jsonValue[i].empty()) {
hasData = true;
}
} else {
hasData = true;
}
}
} else {
hasData = true;
}
if (!hasData) {
return CHIP_NO_ERROR;
}
value[kValueKey] = jsonValue;
auto valueStr = JsonToString(value);
return gDelegate->LogJSON(valueStr.c_str());
}
CHIP_ERROR LogAttributeErrorAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * attributeId, NSError * error)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);
Json::Value value;
value[kEndpointIdKey] = [endpointId unsignedLongLongValue];
value[kClusterIdKey] = [clusterId unsignedLongLongValue];
value[kAttributeIdKey] = [attributeId unsignedLongLongValue];
auto err = MTRErrorToCHIPErrorCode(error);
auto status = chip::app::StatusIB(err);
return LogError(value, status);
}
CHIP_ERROR LogCommandErrorAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * commandId, NSError * error)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);
Json::Value value;
value[kEndpointIdKey] = [endpointId unsignedLongLongValue];
value[kClusterIdKey] = [clusterId unsignedLongLongValue];
value[kCommandIdKey] = [commandId unsignedLongLongValue];
auto err = MTRErrorToCHIPErrorCode(error);
auto status = chip::app::StatusIB(err);
return LogError(value, status);
}
CHIP_ERROR LogGetCommissionerNodeId(NSNumber * value)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);
Json::Value rootValue;
rootValue[kValueKey] = Json::Value();
rootValue[kValueKey][kNodeIdKey] = [value unsignedLongLongValue];
auto valueStr = JsonToString(rootValue);
return gDelegate->LogJSON(valueStr.c_str());
}
CHIP_ERROR LogBdxDownload(NSString * content, NSError * error)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);
Json::Value rootValue;
rootValue[kValueKey] = Json::Value();
Json::Value jsonValue;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue(content, jsonValue));
rootValue[kValueKey][kLogContentIdKey] = jsonValue;
if (error) {
auto err = MTRErrorToCHIPErrorCode(error);
auto status = chip::app::StatusIB(err);
#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
auto statusName = chip::Protocols::InteractionModel::StatusName(status.mStatus);
rootValue[kValueKey][kErrorIdKey] = statusName;
#else
auto statusName = status.mStatus;
rootValue[kValueKey][kErrorIdKey] = chip::to_underlying(statusName);
#endif // CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
}
auto valueStr = JsonToString(rootValue);
return gDelegate->LogJSON(valueStr.c_str());
}
void SetDelegate(RemoteDataModelLoggerDelegate * delegate) { gDelegate = delegate; }
}; // namespace RemoteDataModelLogger