Skip to content

Commit

Permalink
TLV to JSON converter (project-chip#16086)
Browse files Browse the repository at this point in the history
This adds a TLV to JSON converter to provide a means to deal with TLV in
a more human-readable format. This does not rely on any schema
information and instead, just dumps out raw TLV present in cluster
payloads into a JSON format.

This relies on the jsoncpp third party library.

Testing
Added a unit-test.
  • Loading branch information
mrjerryjohns authored Mar 14, 2022
1 parent 70ba6a4 commit c33b1a3
Show file tree
Hide file tree
Showing 5 changed files with 578 additions and 0 deletions.
24 changes: 24 additions & 0 deletions src/lib/support/jsontlv/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) 2020 Project CHIP Authors
#
# 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.

import("//build_overrides/chip.gni")

static_library("jsontlv") {
public_deps = [
"${chip_root}/src/lib/core",
"${chip_root}/third_party/jsoncpp",
]

sources = [ "TlvJson.cpp" ]
}
242 changes: 242 additions & 0 deletions src/lib/support/jsontlv/TlvJson.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2013-2017 Nest Labs, Inc.
*
* 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 "lib/support/CHIPMemString.h"
#include "lib/support/ScopedBuffer.h"
#include <lib/core/DataModelTypes.h>
#include <lib/support/Base64.h>
#include <lib/support/jsontlv/TlvJson.h>

namespace {
/*
* Encapsulates the different types of keys permissible.
*
* Root Key = Key with a name of 'value'. This is the top-most key in a given JSON object generated from TLV.
* Struct Field = Key containing the 32-bit field ID of an item in a struct.
* Array Item = Key containing the 16-bit list index of an item in a list.
*
* In the latter two modes, the actual field ID/list index is encapsulated within the 'key' member.
*
*/
struct KeyContext
{
enum KeyType
{
kRoot,
kStructField,
kArrayItem
};

KeyContext() = default;

KeyContext(chip::FieldId fieldId)
{
keyType = kStructField;
key = fieldId;
}

KeyContext(chip::ListIndex listIndex)
{
keyType = kArrayItem;
key = listIndex;
}

KeyType keyType = kRoot;
unsigned int key = 0;
};
} // namespace

//
// For now, let's put a bound of the maximum length of a byte/char string to be the size of an IPv6
// MTU. While this is smaller than that of the limit defined in the data model specification,
// strings by virtue of not being chunked are intrinsically limited in size to the size of the encompassing packet.
//
static constexpr uint16_t kMaxStringLen = 1280;

namespace chip {

/*
* This templated function inserts a key/value pair into the Json value object.
* The value is templated to be of type T and accepts any of the following primitive
* types:
* bool, uint*_t, int*_t, char *, float, double.
*
* This method uses the provided key context to deduce the type of element being added.
*
*/
template <typename T>
void InsertKeyValue(Json::Value & json, const KeyContext & keyContext, T val)
{
//
// This needs to accomodate either the string 'value', or a 32-bit integer.
// The size of the largest 32-bit integer key represented as a string is 11 characters long.
// Tack on 1 byte for the null character.
//
char keyBuf[12];

if (keyContext.keyType == KeyContext::kRoot)
{
Platform::CopyString(keyBuf, sizeof(keyBuf), "value");
json[keyBuf] = val;
}
else if (keyContext.keyType == KeyContext::kStructField)
{
snprintf(keyBuf, sizeof(keyBuf), "%" PRIu32, keyContext.key);
json[keyBuf] = val;
}
else
{
json[keyContext.key] = val;
}
}

std::string JsonToString(Json::Value & json)
{
Json::StyledWriter writer;
return writer.write(json);
}

CHIP_ERROR TlvToJson(TLV::TLVReader & reader, KeyContext context, Json::Value & parent)
{
bool isStruct = false;

switch (reader.GetType())
{
case TLV::kTLVType_UnsignedInteger: {
uint64_t v;
ReturnErrorOnFailure(reader.Get(v));
InsertKeyValue(parent, context, v);
break;
}

case TLV::kTLVType_SignedInteger: {
int64_t v;
ReturnErrorOnFailure(reader.Get(v));
InsertKeyValue(parent, context, v);
break;
}

case TLV::kTLVType_Boolean: {
bool v;
ReturnErrorOnFailure(reader.Get(v));
InsertKeyValue(parent, context, v);
break;
}

case TLV::kTLVType_FloatingPointNumber: {
double v;
ReturnErrorOnFailure(reader.Get(v));
InsertKeyValue(parent, context, v);
break;
}

case TLV::kTLVType_ByteString: {
ByteSpan span;

ReturnErrorOnFailure(reader.Get(span));
VerifyOrReturnError(span.size() < kMaxStringLen, CHIP_ERROR_INVALID_TLV_ELEMENT);

Platform::ScopedMemoryBuffer<char> byteString;
byteString.Alloc(BASE64_ENCODED_LEN(span.size()) + 1);
VerifyOrReturnError(byteString.Get() != nullptr, CHIP_ERROR_NO_MEMORY);

auto encodedLen = Base64Encode(span.data(), span.size(), byteString.Get());
byteString.Get()[encodedLen] = '\0';

InsertKeyValue(parent, context, byteString.Get());
break;
}

case TLV::kTLVType_UTF8String: {
CharSpan span;

ReturnErrorOnFailure(reader.Get(span));
VerifyOrReturnError(span.size() < kMaxStringLen, CHIP_ERROR_INVALID_TLV_ELEMENT);

Platform::ScopedMemoryString charString(span.data(), span.size());
InsertKeyValue(parent, context, charString.Get());
break;
}

case TLV::kTLVType_Null: {
InsertKeyValue(parent, context, Json::Value());
break;
}

case TLV::kTLVType_Structure:
isStruct = true;

//
// Fall-through to the case below since
// arrays and structs are handled similarly with
// just a small difference in terms of handling of field IDs vs.
// list indices of the elements in the respective collections.
//

case TLV::kTLVType_Array: {
TLV::TLVType containerType;

ReturnErrorOnFailure(reader.EnterContainer(containerType));

CHIP_ERROR err;
Json::Value value;
size_t listIndex = 0;

while ((err = reader.Next()) == CHIP_NO_ERROR)
{
if (isStruct)
{
VerifyOrReturnError(TLV::IsContextTag(reader.GetTag()), CHIP_ERROR_INVALID_TLV_TAG);
KeyContext context2(static_cast<chip::FieldId>(TLV::TagNumFromTag(reader.GetTag())));

//
// Recursively convert to JSON the encompassing item within the struct.
//
ReturnErrorOnFailure(TlvToJson(reader, context2, value));
}
else
{
KeyContext context2(static_cast<chip::ListIndex>(listIndex++));

//
// Recursively convert to JSON the encompassing item within the array.
//
ReturnErrorOnFailure(TlvToJson(reader, context2, value));
}
}

VerifyOrReturnError(err == CHIP_END_OF_TLV, err);
ReturnErrorOnFailure(reader.ExitContainer(containerType));
InsertKeyValue(parent, context, value);
break;
}

default:
return CHIP_ERROR_INVALID_TLV_ELEMENT;
break;
}

return CHIP_NO_ERROR;
}

CHIP_ERROR TlvToJson(TLV::TLVReader & reader, Json::Value & root)
{
KeyContext context;
return TlvToJson(reader, context, root);
}

} // namespace chip
38 changes: 38 additions & 0 deletions src/lib/support/jsontlv/TlvJson.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2013-2017 Nest Labs, Inc.
*
* 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 <json/json.h>
#include <lib/core/CHIPTLV.h>

namespace chip {

/*
* Given a TLVReader positioned at a particular cluster data payload, this function converts
* the TLV data into a JSON object representation.
*
* NOTE: This only accepts data model payloads for events/commands/attributes. It does not support
* arbitrary TLV conversion to JSON.
*/
CHIP_ERROR TlvToJson(TLV::TLVReader & reader, Json::Value & root);

/*
* Converts a JSON object into string representation
*/
std::string JsonToString(Json::Value & json);

} // namespace chip
2 changes: 2 additions & 0 deletions src/lib/support/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ chip_test_suite("tests") {
"TestStringBuilder.cpp",
"TestThreadOperationalDataset.cpp",
"TestTimeUtils.cpp",
"TestTlvToJson.cpp",
"TestVariant.cpp",
"TestZclString.cpp",
]
Expand All @@ -68,6 +69,7 @@ chip_test_suite("tests") {

public_deps = [
"${chip_root}/src/lib/core",
"${chip_root}/src/lib/support/jsontlv",
"${chip_root}/src/platform",
"${nlunit_test_root}:nlunit-test",
]
Expand Down
Loading

0 comments on commit c33b1a3

Please sign in to comment.