| /* |
| * |
| * Copyright (c) 2023 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. |
| */ |
| |
| #include "lib/support/CHIPMemString.h" |
| #include "lib/support/ScopedBuffer.h" |
| #include <json/json.h> |
| #include <lib/core/DataModelTypes.h> |
| #include <lib/support/Base64.h> |
| #include <lib/support/SafeInt.h> |
| #include <lib/support/jsontlv/ElementTypes.h> |
| #include <lib/support/jsontlv/TlvToJson.h> |
| |
| namespace chip { |
| |
| namespace { |
| |
| // actual value of this does not actually matter, however we need |
| // a value to be able to read 32-bit implicit profile tags |
| // |
| // JSON format never has this and TLV payload contains "implicit profile" |
| // and this value is never stored. |
| constexpr uint32_t kTemporaryImplicitProfileId = 0xFF01; |
| |
| /// RAII to switch the implicit profile id for a reader |
| class ImplicitProfileIdChange |
| { |
| public: |
| ImplicitProfileIdChange(TLV::TLVReader & reader, uint32_t id) : mReader(reader), mOldImplicitProfileId(reader.ImplicitProfileId) |
| { |
| reader.ImplicitProfileId = id; |
| } |
| ~ImplicitProfileIdChange() { mReader.ImplicitProfileId = mOldImplicitProfileId; } |
| |
| private: |
| TLV::TLVReader & mReader; |
| uint32_t mOldImplicitProfileId; |
| }; |
| |
| const char * GetJsonElementStrFromType(const ElementTypeContext & ctx) |
| { |
| switch (ctx.tlvType) |
| { |
| case TLV::kTLVType_UnsignedInteger: |
| return kElementTypeUInt; |
| case TLV::kTLVType_SignedInteger: |
| return kElementTypeInt; |
| case TLV::kTLVType_Boolean: |
| return kElementTypeBool; |
| case TLV::kTLVType_FloatingPointNumber: |
| return ctx.isDouble ? kElementTypeDouble : kElementTypeFloat; |
| case TLV::kTLVType_ByteString: |
| return kElementTypeBytes; |
| case TLV::kTLVType_UTF8String: |
| return kElementTypeString; |
| case TLV::kTLVType_Null: |
| return kElementTypeNull; |
| case TLV::kTLVType_Structure: |
| return kElementTypeStruct; |
| case TLV::kTLVType_Array: |
| return kElementTypeArray; |
| default: |
| return kElementTypeEmpty; |
| } |
| }; |
| |
| /* |
| * Encapsulates the element information required to construct a JSON element name string in a JSON object. |
| * |
| * The generated JSON element name string is constructed as: |
| * 'TagNumber:ElementType-SubElementType'. |
| */ |
| struct JsonObjectElementContext |
| { |
| JsonObjectElementContext(TLV::TLVReader & reader) |
| { |
| tag = reader.GetTag(); |
| implicitProfileId = reader.ImplicitProfileId; |
| type.tlvType = reader.GetType(); |
| if (type.tlvType == TLV::kTLVType_FloatingPointNumber) |
| { |
| type.isDouble = reader.IsElementDouble(); |
| } |
| } |
| |
| std::string GenerateJsonElementName() const |
| { |
| std::string str = "???"; |
| if (TLV::IsContextTag(tag)) |
| { |
| // common case for context tags: raw value |
| str = std::to_string(TLV::TagNumFromTag(tag)); |
| } |
| else if (TLV::IsProfileTag(tag)) |
| { |
| if (TLV::ProfileIdFromTag(tag) == implicitProfileId) |
| { |
| // Explicit assume implicit tags are just things we want |
| // 32-bit numbers for |
| str = std::to_string(TLV::TagNumFromTag(tag)); |
| } |
| else |
| { |
| // UNEXPECTED, create a full 64-bit number here |
| str = std::to_string(TLV::ProfileIdFromTag(tag)) + "/" + std::to_string(TLV::TagNumFromTag(tag)); |
| } |
| } |
| str = str + ":" + GetJsonElementStrFromType(type); |
| if (type.tlvType == TLV::kTLVType_Array) |
| { |
| str = str + "-" + GetJsonElementStrFromType(subType); |
| } |
| return str; |
| } |
| |
| TLV::Tag tag; |
| uint32_t implicitProfileId; |
| ElementTypeContext type; |
| ElementTypeContext subType; |
| }; |
| |
| /* |
| * This templated function inserts a name/value pair into the Json object. |
| * The value is templated to be of type T and accepts any of the following types: |
| * |
| * bool, uint*_t, int*_t, char *, float, double, std::string, Json::Value |
| * |
| * This method uses the provided element context to generate Json name string. |
| */ |
| template <typename T> |
| void InsertJsonElement(Json::Value & json, const JsonObjectElementContext & ctx, T val) |
| { |
| if (json.isArray()) |
| { |
| json.append(val); |
| } |
| else |
| { |
| json[ctx.GenerateJsonElementName()] = val; |
| } |
| } |
| |
| static CHIP_ERROR TlvToJson(TLV::TLVReader & reader, Json::Value & jsonObj); |
| |
| /* |
| * Given a TLVReader positioned at TLV structure this function: |
| * - enters structure |
| * - converts all elements of a structure into JSON object representation |
| * - exits structure |
| */ |
| CHIP_ERROR TlvStructToJson(TLV::TLVReader & reader, Json::Value & jsonObj) |
| { |
| CHIP_ERROR err; |
| TLV::TLVType containerType; |
| |
| ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
| |
| while ((err = reader.Next()) == CHIP_NO_ERROR) |
| { |
| TLV::Tag tag = reader.GetTag(); |
| VerifyOrReturnError(TLV::IsContextTag(tag) || TLV::IsProfileTag(tag), CHIP_ERROR_INVALID_TLV_TAG); |
| |
| // Profile tags are expected to be implicit profile tags and they are |
| // used to encode > 8bit values from json |
| if (TLV::IsProfileTag(tag)) |
| { |
| VerifyOrReturnError(TLV::ProfileIdFromTag(tag) == reader.ImplicitProfileId, CHIP_ERROR_INVALID_TLV_TAG); |
| VerifyOrReturnError(TLV::TagNumFromTag(tag) > UINT8_MAX, CHIP_ERROR_INVALID_TLV_TAG); |
| } |
| |
| // Recursively convert to JSON the item within the struct. |
| ReturnErrorOnFailure(TlvToJson(reader, jsonObj)); |
| } |
| |
| VerifyOrReturnError(err == CHIP_END_OF_TLV, err); |
| return reader.ExitContainer(containerType); |
| } |
| |
| CHIP_ERROR TlvToJson(TLV::TLVReader & reader, Json::Value & jsonObj) |
| { |
| JsonObjectElementContext context(reader); |
| |
| switch (reader.GetType()) |
| { |
| case TLV::kTLVType_UnsignedInteger: { |
| uint64_t v; |
| ReturnErrorOnFailure(reader.Get(v)); |
| if (CanCastTo<uint32_t>(v)) |
| { |
| InsertJsonElement(jsonObj, context, v); |
| } |
| else |
| { |
| InsertJsonElement(jsonObj, context, std::to_string(v)); |
| } |
| break; |
| } |
| |
| case TLV::kTLVType_SignedInteger: { |
| int64_t v; |
| ReturnErrorOnFailure(reader.Get(v)); |
| if (CanCastTo<int32_t>(v)) |
| { |
| InsertJsonElement(jsonObj, context, v); |
| } |
| else |
| { |
| InsertJsonElement(jsonObj, context, std::to_string(v)); |
| } |
| break; |
| } |
| |
| case TLV::kTLVType_Boolean: { |
| bool v; |
| ReturnErrorOnFailure(reader.Get(v)); |
| InsertJsonElement(jsonObj, context, v); |
| break; |
| } |
| |
| case TLV::kTLVType_FloatingPointNumber: { |
| double v; |
| ReturnErrorOnFailure(reader.Get(v)); |
| if (v == std::numeric_limits<double>::infinity()) |
| { |
| InsertJsonElement(jsonObj, context, kFloatingPointPositiveInfinity); |
| } |
| else if (v == -std::numeric_limits<double>::infinity()) |
| { |
| InsertJsonElement(jsonObj, context, kFloatingPointNegativeInfinity); |
| } |
| else |
| { |
| InsertJsonElement(jsonObj, context, v); |
| } |
| break; |
| } |
| |
| case TLV::kTLVType_ByteString: { |
| ByteSpan span; |
| ReturnErrorOnFailure(reader.Get(span)); |
| |
| 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(), static_cast<uint16_t>(span.size()), byteString.Get()); |
| byteString.Get()[encodedLen] = '\0'; |
| |
| InsertJsonElement(jsonObj, context, byteString.Get()); |
| break; |
| } |
| |
| case TLV::kTLVType_UTF8String: { |
| CharSpan span; |
| ReturnErrorOnFailure(reader.Get(span)); |
| |
| std::string str(span.data(), span.size()); |
| InsertJsonElement(jsonObj, context, str); |
| break; |
| } |
| |
| case TLV::kTLVType_Null: { |
| InsertJsonElement(jsonObj, context, Json::Value()); |
| break; |
| } |
| |
| case TLV::kTLVType_Structure: { |
| Json::Value jsonStruct(Json::objectValue); |
| ReturnErrorOnFailure(TlvStructToJson(reader, jsonStruct)); |
| InsertJsonElement(jsonObj, context, jsonStruct); |
| break; |
| } |
| |
| case TLV::kTLVType_Array: { |
| CHIP_ERROR err; |
| Json::Value jsonArray(Json::arrayValue); |
| ElementTypeContext prevSubType; |
| ElementTypeContext nextSubType; |
| TLV::TLVType containerType; |
| |
| ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
| |
| while ((err = reader.Next()) == CHIP_NO_ERROR) |
| { |
| VerifyOrReturnError(reader.GetTag() == TLV::AnonymousTag(), CHIP_ERROR_INVALID_TLV_TAG); |
| VerifyOrReturnError(reader.GetType() != TLV::kTLVType_Array, CHIP_ERROR_INVALID_TLV_ELEMENT); |
| |
| nextSubType.tlvType = reader.GetType(); |
| if (nextSubType.tlvType == TLV::kTLVType_FloatingPointNumber) |
| { |
| nextSubType.isDouble = reader.IsElementDouble(); |
| } |
| |
| if (jsonArray.empty()) |
| { |
| prevSubType = nextSubType; |
| } |
| else |
| { |
| VerifyOrReturnError(prevSubType.tlvType == nextSubType.tlvType && prevSubType.isDouble == nextSubType.isDouble, |
| CHIP_ERROR_INVALID_TLV_ELEMENT); |
| } |
| |
| // Recursively convert to JSON the encompassing item within the array. |
| ReturnErrorOnFailure(TlvToJson(reader, jsonArray)); |
| } |
| |
| VerifyOrReturnError(err == CHIP_END_OF_TLV, err); |
| ReturnErrorOnFailure(reader.ExitContainer(containerType)); |
| |
| context.subType = prevSubType; |
| InsertJsonElement(jsonObj, context, jsonArray); |
| break; |
| } |
| |
| default: |
| return CHIP_ERROR_INVALID_TLV_ELEMENT; |
| break; |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| } // namespace |
| |
| CHIP_ERROR TlvToJson(const ByteSpan & tlv, std::string & jsonString) |
| { |
| TLV::TLVReader reader; |
| reader.Init(tlv); |
| reader.ImplicitProfileId = kTemporaryImplicitProfileId; |
| |
| ReturnErrorOnFailure(reader.Next()); |
| return TlvToJson(reader, jsonString); |
| } |
| |
| CHIP_ERROR TlvToJson(TLV::TLVReader & reader, std::string & jsonString) |
| { |
| // The top level element must be a TLV Structure of Anonymous type. |
| VerifyOrReturnError(reader.GetType() == TLV::kTLVType_Structure, CHIP_ERROR_WRONG_TLV_TYPE); |
| VerifyOrReturnError(reader.GetTag() == TLV::AnonymousTag(), CHIP_ERROR_INVALID_TLV_TAG); |
| |
| // During json conversion, a implicit profile ID is required |
| ImplicitProfileIdChange implicitProfileIdChange(reader, kTemporaryImplicitProfileId); |
| |
| Json::Value jsonObject(Json::objectValue); |
| ReturnErrorOnFailure(TlvStructToJson(reader, jsonObject)); |
| |
| Json::StyledWriter writer; |
| jsonString = writer.write(jsonObject); |
| return CHIP_NO_ERROR; |
| } |
| |
| std::string PrettyPrintJsonString(const std::string & jsonString) |
| { |
| Json::Reader reader; |
| Json::Value jsonObject; |
| reader.parse(jsonString, jsonObject); |
| Json::StyledWriter writer; |
| return writer.write(jsonObject); |
| } |
| |
| std::string MakeJsonSingleLine(const std::string & jsonString) |
| { |
| std::string str = PrettyPrintJsonString(jsonString); |
| str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end()); |
| return str; |
| } |
| |
| } // namespace chip |