From 285012198368f4eb697bce07e34681c47685622d Mon Sep 17 00:00:00 2001 From: chrisdecenzo Date: Thu, 15 Jun 2023 11:34:11 -0700 Subject: [PATCH] Draft: TLV-JSON-TLV converter (for message serialization between android tv-app platform and content apps) --- examples/common/json/JsonTLV.cpp | 311 ++++++++++++ examples/common/json/JsonTLV.h | 448 ++++++++++++++++++ .../linux/CastingShellCommands.cpp | 177 +++++++ .../tv-casting-app/tv-casting-common/BUILD.gn | 3 + .../tv-casting-common/include/CastingServer.h | 2 + .../tv-casting-common/src/CastingServer.cpp | 50 ++ 6 files changed, 991 insertions(+) create mode 100644 examples/common/json/JsonTLV.cpp create mode 100644 examples/common/json/JsonTLV.h diff --git a/examples/common/json/JsonTLV.cpp b/examples/common/json/JsonTLV.cpp new file mode 100644 index 00000000000000..3392d0cb8cf72a --- /dev/null +++ b/examples/common/json/JsonTLV.cpp @@ -0,0 +1,311 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2015-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. + */ + +/** + * @file + * This file implements interfaces for debugging and logging + * CHIP TLV. + * + */ + +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include "JsonTLV.h" + +namespace chip { + +namespace TLV { + +namespace Json { + +void ENFORCE_FORMAT(1, 2) TLVDumpWriter(const char * aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + + vprintf(aFormat, args); + + va_end(args); +} + +static void ENFORCE_FORMAT(2, 3) FormattedWrite(Encoding::BufferWriter & writer, const char * aFormat, ...) +{ + char out_buf[100]; + + va_list args; + + va_start(args, aFormat); + + vsnprintf(out_buf, sizeof(out_buf), aFormat, args); + + va_end(args); + + writer.Put(out_buf); +} + +/** + * Write the TLV element referenced by @a aReader in human-readable form using + * @a aWriter. + * + * @param[in] aWriter The writer to log the TLV data. + * @param[in] aIndent The indentation for logging the current depth into + * the TLV data. + * @param[in] aReader A read-only reference to the TLV reader containing + * the TLV data to log. + * @param[in] aDepth The current depth into the TLV data. + * + */ +static void WriteHandlerJSON(Encoding::BufferWriter & aWriter, const char * aIndent, const TLVReader & aReader, size_t aDepth) +{ + const TLVType type = aReader.GetType(); + const Tag tag = aReader.GetTag(); + const uint32_t len = aReader.GetLength(); + const uint8_t * strbuf = nullptr; + CHIP_ERROR err = CHIP_NO_ERROR; + TLVReader temp; + TLVTagControl tagControl; + + temp.Init(aReader); + tagControl = static_cast(temp.GetControlByte() & kTLVTagControlMask); + + for (size_t i = 0; i < aDepth; i++) + FormattedWrite(aWriter, "%s", aIndent); + + if (TLVTypeIsContainer(type)) + { + if (tagControl == TLVTagControl::ContextSpecific) + { + FormattedWrite(aWriter, "\"0x%x\" : ", TagNumFromTag(tag)); + } + + // add any prefix + switch (type) + { + case kTLVType_Structure: + FormattedWrite(aWriter, "{\n"); + break; + + case kTLVType_Array: + FormattedWrite(aWriter, "[\n"); + break; + + case kTLVType_List: + FormattedWrite(aWriter, "[\n"); + break; + + default: + break; + } + } + else + { + if (tagControl == TLVTagControl::ContextSpecific) + { + FormattedWrite(aWriter, "\"0x%x\" : { \"tlvType\":\"0x%02x\", \"value\": ", TagNumFromTag(tag), type); + } + else + { + FormattedWrite(aWriter, "{ \"tlvType\":\"0x%02x\", \"value\": ", type); + } + + switch (type) + { + + case kTLVType_SignedInteger: + int64_t sVal; + err = temp.Get(sVal); + VerifyOrExit(err == CHIP_NO_ERROR, FormattedWrite(aWriter, "Error in kTLVType_SignedInteger")); + FormattedWrite(aWriter, "%" PRIi64, sVal); + break; + + case kTLVType_UnsignedInteger: + uint64_t uVal; + err = temp.Get(uVal); + VerifyOrExit(err == CHIP_NO_ERROR, FormattedWrite(aWriter, "Error in kTLVType_UnsignedInteger")); + FormattedWrite(aWriter, "%" PRIu64, uVal); + break; + + case kTLVType_Boolean: + bool bVal; + err = temp.Get(bVal); + VerifyOrExit(err == CHIP_NO_ERROR, FormattedWrite(aWriter, "Error in kTLVType_Boolean")); + FormattedWrite(aWriter, "%s", bVal ? "true" : "false"); + break; + + case kTLVType_FloatingPointNumber: + double fpVal; + err = temp.Get(fpVal); + VerifyOrExit(err == CHIP_NO_ERROR, FormattedWrite(aWriter, "Error in kTLVType_FloatingPointNumber")); + FormattedWrite(aWriter, "%lf", fpVal); + break; + + case kTLVType_UTF8String: + err = temp.GetDataPtr(strbuf); + VerifyOrExit(err == CHIP_NO_ERROR, FormattedWrite(aWriter, "Error in kTLVType_UTF8String")); + FormattedWrite(aWriter, "\"%-.*s\"", static_cast(len), strbuf); + break; + + case kTLVType_ByteString: + err = temp.GetDataPtr(strbuf); + VerifyOrExit(err == CHIP_NO_ERROR, FormattedWrite(aWriter, "Error in kTLVType_ByteString")); + FormattedWrite(aWriter, "hex:"); + for (uint32_t i = 0; i < len; i++) + { + FormattedWrite(aWriter, "%02X", strbuf[i]); + } + break; + + case kTLVType_Null: + FormattedWrite(aWriter, "null"); + break; + + case kTLVType_NotSpecified: + FormattedWrite(aWriter, "ns"); + break; + + default: + FormattedWrite(aWriter, "Error: Type is not primitive."); + break; + } + + FormattedWrite(aWriter, " }"); + } + +exit: + return; +} + +static void WriteHandlerJSONAfter(Encoding::BufferWriter & aWriter, const char * aIndent, const TLVType type, size_t aDepth, + bool endOfList) +{ + if (TLVTypeIsContainer(type)) + { + for (size_t i = 0; i < aDepth; i++) + FormattedWrite(aWriter, "%s", aIndent); + + // add any postfix + switch (type) + { + case kTLVType_Structure: + FormattedWrite(aWriter, "}"); + break; + + case kTLVType_Array: + FormattedWrite(aWriter, "]"); + break; + + case kTLVType_List: + FormattedWrite(aWriter, "]"); + break; + + default: + break; + } + } + + if (!endOfList) + { + FormattedWrite(aWriter, ","); + } + + FormattedWrite(aWriter, "\n"); +} + +/** + * Log the TLV data within the specified reader in human-readable form. + * + * @param[in] aReader A read-only reference to the TLV reader containing + * the TLV data to log. + * @param[in] aDepth The current depth into the TLV data. + * @param[in,out] aContext A pointer to the handler-specific context. + * + * @retval #CHIP_NO_ERROR On success. + * + * @retval #CHIP_ERROR_INVALID_ARGUMENT If aContext is NULL or if + * aContext->mWriter is NULL. + * + */ +CHIP_ERROR WriteHandlerJSON(const TLVReader & aReader, size_t aDepth, void * aContext) +{ + static const char indent[] = " "; + JsonContext * context; + + VerifyOrReturnError(aContext != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + context = static_cast(aContext); + + WriteHandlerJSON(context->mWriter, indent, aReader, aDepth); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR WriteHandlerJSONAfter(const TLVType theType, size_t aDepth, bool endOfList, void * aContext) +{ + static const char indent[] = " "; + JsonContext * context; + + VerifyOrReturnError(aContext != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + context = static_cast(aContext); + + WriteHandlerJSONAfter(context->mWriter, indent, theType, aDepth, endOfList); + + return CHIP_NO_ERROR; +} +/** + * Write the TLV data within the specified reader in human-readable form with + * the specified writer. + * + * @param[in] aReader A read-only reference to the TLV reader containing + * the TLV data to log. + * + * @param[in] aWriter A dump writer to log the TLV data of the TLV reader. + * + * @retval #CHIP_NO_ERROR On success. + * + */ +CHIP_ERROR WriteJSON(const TLVReader & aReader, Encoding::BufferWriter & aWriter) +{ + void * context = nullptr; + JsonContext dumpContext = { aWriter, context }; + CHIP_ERROR retval; + + retval = Utilities::Iterate(aReader, WriteHandlerJSON, &dumpContext, true /* recurse */, WriteHandlerJSONAfter); + + aWriter.Put('\0'); + + return retval; +} + +} // namespace Json + +} // namespace TLV + +} // namespace chip diff --git a/examples/common/json/JsonTLV.h b/examples/common/json/JsonTLV.h new file mode 100644 index 00000000000000..0f0fc1a46ed696 --- /dev/null +++ b/examples/common/json/JsonTLV.h @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2022 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. + * + */ + +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace chip { + +namespace TLV { + +/** + * @namespace chip::TLV::Json + * + * @brief + * This namespace includes types and interfaces for json conversion of CHIP TLV. + * + */ +namespace Json { + +struct JsonContext +{ + Encoding::BufferWriter & mWriter; + void * mContext; +}; + +extern CHIP_ERROR WriteJSON(const TLVReader & aReader, Encoding::BufferWriter & bWriter); + +extern void ENFORCE_FORMAT(1, 2) TLVDumpWriter(const char * aFormat, ...); + +} // namespace Json + +} // namespace TLV + +} // namespace chip + +/** + * Utility for converting a hex string to bytes, with the right error checking + * and allocation size computation. + * + * Takes a functor to allocate the buffer to use for the hex bytes. The functor + * is expected to return uint8_t *. The caller is responsible for cleaning up + * this buffer as needed. + * + * On success, *octetCount is filled with the number of octets placed in the + * buffer. On failure, the value of *octetCount is undefined. + */ +template +CHIP_ERROR ConvertHexToBytes(chip::CharSpan hex, F bufferAllocator, size_t * octetCount) +{ + *octetCount = 0; + + if (hex.size() % 2 != 0) + { + ChipLogError(chipTool, "Error while encoding '%.*s' as an octet string: Odd number of characters.", + static_cast(hex.size()), hex.data()); + return CHIP_ERROR_INVALID_STRING_LENGTH; + } + + const size_t bufferSize = hex.size() / 2; + uint8_t * buffer = bufferAllocator(bufferSize); + if (buffer == nullptr && bufferSize != 0) + { + ChipLogError(chipTool, "Failed to allocate buffer of size: %llu", static_cast(bufferSize)); + return CHIP_ERROR_NO_MEMORY; + } + + size_t byteCount = chip::Encoding::HexToBytes(hex.data(), hex.size(), buffer, bufferSize); + if (byteCount == 0 && hex.size() != 0) + { + ChipLogError(chipTool, "Error while encoding '%.*s' as an octet string.", static_cast(hex.size()), hex.data()); + return CHIP_ERROR_INTERNAL; + } + + *octetCount = byteCount; + return CHIP_NO_ERROR; +} + +class JsonParser2 +{ +public: + // Returns whether the parse succeeded. + static bool ParseComplexArgument(const char * label, const char * json, Json::Value & value) + { + return Parse(label, json, /* strictRoot = */ true, value); + } + + // Returns whether the parse succeeded. + static bool ParseCustomArgument(const char * label, const char * json, Json::Value & value) + { + return Parse(label, json, /* strictRoot = */ false, value); + } + +private: + static bool Parse(const char * label, const char * json, bool strictRoot, Json::Value & value) + { + Json::CharReaderBuilder readerBuilder; + readerBuilder.settings_["strictRoot"] = strictRoot; + readerBuilder.settings_["allowSingleQuotes"] = true; + readerBuilder.settings_["failIfExtra"] = true; + readerBuilder.settings_["rejectDupKeys"] = true; + + auto reader = std::unique_ptr(readerBuilder.newCharReader()); + std::string errors; + if (reader->parse(json, json + strlen(json), &value, &errors)) + { + return true; + } + + // The CharReader API allows us to set failIfExtra, unlike Reader, but does + // not allow us to get structured errors. We get to try to manually undo + // the work it did to create a string from the structured errors it had. + ChipLogError(chipTool, "Error parsing JSON for %s:", label); + + // For each error "errors" has the following: + // + // 1) A line starting with "* " that has line/column info + // 2) A line with the error message. + // 3) An optional line with some extra info. + // + // We keep track of the last error column, in case the error message + // reporting needs it. + std::istringstream stream(errors); + std::string error; + chip::Optional errorColumn; + while (getline(stream, error)) + { + if (error.rfind("* ", 0) == 0) + { + // Flush out any pending error location. + LogErrorLocation(errorColumn, json); + + // The format of this line is: + // + // * Line N, Column M + // + // Unfortunately it does not indicate end of error, so we can only + // show its start. + unsigned errorLine; // ignored in practice + if (sscanf(error.c_str(), "* Line %u, Column %u", &errorLine, &errorColumn.Emplace()) != 2) + { + ChipLogError(chipTool, "Unexpected location string: %s\n", error.c_str()); + // We don't know how to make sense of this thing anymore. + break; + } + if (errorColumn.Value() == 0) + { + ChipLogError(chipTool, "Expected error column to be at least 1"); + // We don't know how to make sense of this thing anymore. + break; + } + // We are using our column numbers as offsets, so want them to be + // 0-based. + --errorColumn.Value(); + } + else + { + ChipLogError(chipTool, " %s", error.c_str()); + if (error == " Missing ',' or '}' in object declaration" && errorColumn.HasValue() && errorColumn.Value() > 0 && + json[errorColumn.Value() - 1] == '0' && (json[errorColumn.Value()] == 'x' || json[errorColumn.Value()] == 'X')) + { + // Log the error location marker before showing the NOTE + // message. + LogErrorLocation(errorColumn, json); + ChipLogError(chipTool, + "NOTE: JSON does not allow hex syntax beginning with 0x for numbers. Try putting the hex number " + "in quotes (like {\"name\": \"0x100\"})."); + } + } + } + + // Write out the marker for our last error. + LogErrorLocation(errorColumn, json); + + return false; + } + +private: + static void LogErrorLocation(chip::Optional & errorColumn, const char * json) + { + if (!errorColumn.HasValue()) + { + return; + } + + const char * sourceText = json; + unsigned error_start = errorColumn.Value(); + // The whole JSON string might be too long to fit in our log + // messages. Just include 30 chars before the error. + constexpr ptrdiff_t kMaxContext = 30; + std::string errorMarker; + if (error_start > kMaxContext) + { + sourceText += (error_start - kMaxContext); + error_start = kMaxContext; + ChipLogError(chipTool, "... %s", sourceText); + // Add markers corresponding to the "... " above. + errorMarker += "----"; + } + else + { + ChipLogError(chipTool, "%s", sourceText); + } + for (unsigned i = 0; i < error_start; ++i) + { + errorMarker += "-"; + } + errorMarker += "^"; + ChipLogError(chipTool, "%s", errorMarker.c_str()); + errorColumn.ClearValue(); + } +}; + +class JsonTLVParser +{ +public: + static CHIP_ERROR Put(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + if (value.isObject()) + { + return JsonTLVParser::PutObject(writer, tag, value); + } + + if (value.isArray()) + { + return JsonTLVParser::PutArray(writer, tag, value); + } + + if (value.isString()) + { + return JsonTLVParser::PutCharString(writer, tag, value); + } + + if (value.isNull()) + { + return chip::app::DataModel::Encode(*writer, tag, chip::app::DataModel::Nullable()); + } + + if (value.isBool()) + { + return chip::app::DataModel::Encode(*writer, tag, value.asBool()); + } + + if (value.isUInt()) + { + return chip::app::DataModel::Encode(*writer, tag, value.asLargestUInt()); + } + + if (value.isInt()) + { + return chip::app::DataModel::Encode(*writer, tag, value.asLargestInt()); + } + + if (value.isNumeric()) + { + return chip::app::DataModel::Encode(*writer, tag, value.asDouble()); + } + + return CHIP_ERROR_NOT_IMPLEMENTED; + } + +private: + static CHIP_ERROR PutArray(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + chip::TLV::TLVType outer; + ReturnErrorOnFailure(writer->StartContainer(tag, chip::TLV::kTLVType_Array, outer)); + + Json::ArrayIndex size = value.size(); + + for (Json::ArrayIndex i = 0; i < size; i++) + { + ReturnErrorOnFailure(JsonTLVParser::Put(writer, chip::TLV::AnonymousTag(), value[i])); + } + + return writer->EndContainer(outer); + } + + static CHIP_ERROR PutObject(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + chip::TLV::TLVType outer; + + // need to check if its a tlvType or type + if (value.isMember("tlvType") && value.isMember("value")) + { + constexpr const char kHexNumPrefix[] = "0x"; + constexpr size_t kHexNumPrefixLen = ArraySize(kHexNumPrefix) - 1; + + Json::Value objTlvType = value.get("tlvType", "0x0c"); + if (strncmp(objTlvType.asCString(), kHexNumPrefix, kHexNumPrefixLen) != 0) + { + ChipLogError(chipTool, "bad tlvType=%s", objTlvType.asCString()); + return CHIP_ERROR_INVALID_ARGUMENT; + } + auto numberTlvType = std::stoull(objTlvType.asCString(), nullptr, 0); + // ChipLogError(chipTool, "next tlvType=%s number=%llu", objTlvType.asCString(), numberTlvType); + + Json::Value objValue = value.get("value", ""); + + chip::TLV::TLVElementType number = static_cast(numberTlvType); + if (number == chip::TLV::TLVElementType::Int8 || number == chip::TLV::TLVElementType::Int16 || + number == chip::TLV::TLVElementType::Int32 || number == chip::TLV::TLVElementType::Int64) + { + return chip::app::DataModel::Encode(*writer, tag, static_cast(objValue.asInt64())); + } + + if (number == chip::TLV::TLVElementType::UInt8 || number == chip::TLV::TLVElementType::UInt16 || + number == chip::TLV::TLVElementType::UInt32 || number == chip::TLV::TLVElementType::UInt64) + { + return chip::app::DataModel::Encode(*writer, tag, static_cast(objValue.asUInt64())); + } + + if (number == chip::TLV::TLVElementType::FloatingPointNumber32 || + number == chip::TLV::TLVElementType::FloatingPointNumber64) + { + return chip::app::DataModel::Encode(*writer, tag, objValue.asFloat()); + } + + if (number == chip::TLV::TLVElementType::ByteString_1ByteLength || + number == chip::TLV::TLVElementType::ByteString_2ByteLength || + number == chip::TLV::TLVElementType::ByteString_4ByteLength || + number == chip::TLV::TLVElementType::ByteString_8ByteLength) + { + return PutOctetString(writer, tag, objValue); + } + + // regular handling + return JsonTLVParser::Put(writer, tag, objValue); + } + + ReturnErrorOnFailure(writer->StartContainer(tag, chip::TLV::kTLVType_Structure, outer)); + + for (auto const & id : value.getMemberNames()) + { + auto index = std::stoul(id, nullptr, 0); + VerifyOrReturnError(chip::CanCastTo(index), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(JsonTLVParser::Put(writer, chip::TLV::ContextTag(static_cast(index)), value[id])); + } + + return writer->EndContainer(outer); + } + + static CHIP_ERROR PutOctetString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + const char * hexData = value.asCString(); + size_t hexDataLen = strlen(hexData); + chip::Platform::ScopedMemoryBuffer buffer; + + size_t octetCount; + ReturnErrorOnFailure(ConvertHexToBytes( + chip::CharSpan(hexData, hexDataLen), + [&buffer](size_t allocSize) { + buffer.Calloc(allocSize); + return buffer.Get(); + }, + &octetCount)); + + return chip::app::DataModel::Encode(*writer, tag, chip::ByteSpan(buffer.Get(), octetCount)); + } + + static CHIP_ERROR PutCharString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + size_t size = strlen(value.asCString()); + CHIP_ERROR err = chip::app::DataModel::Encode(*writer, tag, chip::CharSpan(value.asCString(), size)); + return err; + } +}; + +class JsonTLV +{ +public: + ~JsonTLV() + { + if (mData != nullptr) + { + chip::Platform::MemoryFree(mData); + } + } + + CHIP_ERROR Parse(const char * label, const char * json) + { + Json::Value value; + if (!JsonParser2::ParseCustomArgument(label, json, value)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + mData = static_cast(chip::Platform::MemoryCalloc(sizeof(uint8_t), mDataMaxLen)); + VerifyOrReturnError(mData != nullptr, CHIP_ERROR_NO_MEMORY); + + chip::TLV::TLVWriter writer; + writer.Init(mData, mDataMaxLen); + + ReturnErrorOnFailure(JsonTLVParser::Put(&writer, chip::TLV::AnonymousTag(), value)); + + mDataLen = writer.GetLengthWritten(); + return writer.Finalize(); + } + + CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const + { + chip::TLV::TLVReader reader; + reader.Init(mData, mDataLen); + ReturnErrorOnFailure(reader.Next()); + + return writer.CopyElement(tag, reader); + } + + // We trust our consumers to do the encoding of our data correctly, so don't + // need to know whether we are being encoded for a write. + static constexpr bool kIsFabricScoped = false; + +private: + uint8_t * mData = nullptr; + uint32_t mDataLen = 0; + static constexpr uint32_t mDataMaxLen = 4096; +}; diff --git a/examples/tv-casting-app/linux/CastingShellCommands.cpp b/examples/tv-casting-app/linux/CastingShellCommands.cpp index 136c0fef46625f..7f2a1b4ca0fdd1 100644 --- a/examples/tv-casting-app/linux/CastingShellCommands.cpp +++ b/examples/tv-casting-app/linux/CastingShellCommands.cpp @@ -23,6 +23,7 @@ #include "CastingServer.h" #include "CastingUtils.h" +#include "JsonTLV.h" #include "app/clusters/bindings/BindingManager.h" #include #include @@ -80,6 +81,149 @@ void PrintBindings() } } +class MockCommandSenderCallback : public chip::app::CommandSender::Callback +{ +public: + void OnResponse(chip::app::CommandSender * apCommandSender, const chip::app::ConcreteCommandPath & aPath, + const chip::app::StatusIB & aStatus, chip::TLV::TLVReader * aData) override + { + ChipLogDetail(Controller, "OnResponse Cluster Command: Cluster=%" PRIx32 " Command=%" PRIx32 " Endpoint=%x", + aPath.mClusterId, aPath.mCommandId, aPath.mEndpointId); + + uint8_t out_buf[1024]; + Encoding::BufferWriter buf_writer(out_buf, sizeof(out_buf)); + + TLV::Debug::Dump(*aData, TLV::Json::TLVDumpWriter); + TLV::Json::WriteJSON(*aData, buf_writer); + + ChipLogDetail(Controller, "OnResponse %s", out_buf); + + ChipLogDetail(Controller, "OnResponse DumpDone"); + } + void OnError(const chip::app::CommandSender * apCommandSender, CHIP_ERROR aError) override + { + ChipLogError(Controller, "OnError happens with %" CHIP_ERROR_FORMAT, aError.Format()); + } + void OnDone(chip::app::CommandSender * apCommandSender) override + { + ChipLogDetail(Controller, "OnDone CommandSender"); + if (apCommandSender != nullptr) + { + chip::Platform::Delete(apCommandSender); + ChipLogDetail(Controller, "Cleaned up"); + } + } + +} mockCommandSenderDelegate; + +CHIP_ERROR SendClusterCommand(chip::NodeId node, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::CommandId commandId, + uint16_t timedRequestTimeoutMs, uint16_t imTimeoutMs, char * json) +{ + chip::TLV::TLVWriter writer; + chip::TLV::TLVReader reader; + + uint8_t buf[74]; + uint8_t out_buf[1024]; + + Encoding::BufferWriter buf_writer(out_buf, sizeof(out_buf)); + + writer.Init(buf); + writer.ImplicitProfileId = chip::TLV::kCommonProfileId; + + JsonTLV customJson; + ReturnErrorOnFailure(customJson.Parse("SendClusterCommand", json)); + + ReturnErrorOnFailure(customJson.Encode(writer, chip::TLV::CommonTag(77))); + + reader.Init(buf, writer.GetLengthWritten()); + reader.ImplicitProfileId = chip::TLV::kCommonProfileId; + reader.Next(); + + CastingServer::GetInstance()->SendCommand(mockCommandSenderDelegate, endpointId, clusterId, commandId, reader, + timedRequestTimeoutMs, imTimeoutMs); + return CHIP_NO_ERROR; +} + +CHIP_ERROR RunJsonTest(const char * json, const char * testName) +{ + chip::TLV::TLVWriter writer; + chip::TLV::TLVReader reader; + + uint8_t buf[74]; + uint8_t out_buf[1024]; + Encoding::BufferWriter buf_writer(out_buf, sizeof(out_buf)); + + writer.Init(buf); + writer.ImplicitProfileId = chip::TLV::kCommonProfileId; + + JsonTLV customJson; + ReturnLogErrorOnFailure(customJson.Parse(testName, json)); + ReturnLogErrorOnFailure(customJson.Encode(writer, chip::TLV::CommonTag(77))); + + ChipLogProgress(AppServer, "--------Start Raw Bytes"); + for (uint32_t i = 0; i < writer.GetLengthWritten(); i++) + { + if (i != 0 && i % 16 == 0) + printf("\n"); + printf("0x%02X, ", buf[i]); + } + printf("\n"); + ChipLogProgress(AppServer, "--------End Raw Bytes"); + + reader.Init(buf, writer.GetLengthWritten()); + reader.ImplicitProfileId = chip::TLV::kCommonProfileId; + reader.Next(); + + TLV::Debug::Dump(reader, TLV::Json::TLVDumpWriter); + + ChipLogProgress(AppServer, "%s", json); + TLV::Json::WriteJSON(reader, buf_writer); + + ChipLogProgress(AppServer, "%s", out_buf); + + Json::Value inputValue; + VerifyOrReturnLogError(JsonParser2::ParseCustomArgument(testName, json, inputValue), CHIP_ERROR_INVALID_ARGUMENT); + + Json::Value outputValue; + VerifyOrReturnLogError(JsonParser2::ParseCustomArgument(testName, (char *) out_buf, outputValue), CHIP_ERROR_INVALID_ARGUMENT); + + VerifyOrReturnLogError(inputValue == outputValue, CHIP_ERROR_DECODE_FAILED); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR RunJsonTests() +{ + char json[] = "{" + "\"0x0\" : { \"tlvType\":\"0x00\", \"value\": 7 }," + "\"0x1\" : { \"tlvType\":\"0x0c\", \"value\": \"foo\" }," + "\"0x2\" : {" + "\"0x0\" : { \"tlvType\":\"0x0a\", \"value\": 8.000000 }" + "}," + "\"0x3\" : [" + "{ \"tlvType\":\"0x0c\", \"value\": \"bar\" }," + "{ \"tlvType\":\"0x0c\", \"value\": \"bat\" }," + "{" + "\"0x0\" : { \"tlvType\":\"0x04\", \"value\": 7 }," + "\"0x1\" : { \"tlvType\":\"0x04\", \"value\": 8 }" + "}," + "{ \"tlvType\":\"0x08\", \"value\": true }," + "{ \"tlvType\":\"0x04\", \"value\": 5463 }" + "]," + "\"0x4\" : { \"tlvType\":\"0x04\", \"value\": 3 }" + "}"; + ReturnErrorOnFailure(RunJsonTest(json, "test 1")); + + ReturnErrorOnFailure(RunJsonTest( + "{\"0x0\" : { \"tlvType\":\"0x00\", \"value\": 7 },\"0x1\" : { \"tlvType\":\"0x0c\", \"value\": \"foo\" },\"0x2\" " + ": {\"0x0\" : { \"tlvType\":\"0x0a\", \"value\": 8.000000 }}}", + "test 2")); + + ReturnErrorOnFailure(RunJsonTest("{\"0x0\" : { \"tlvType\":\"0x04\", \"value\": 1 }}", "test 3")); + + return CHIP_NO_ERROR; +} + static CHIP_ERROR CastingHandler(int argc, char ** argv) { if (argc == 0 || strcmp(argv[0], "help") == 0) @@ -137,6 +281,39 @@ static CHIP_ERROR CastingHandler(int argc, char ** argv) CastingServer::GetInstance()->ReadServerClustersForNode(node); return CHIP_NO_ERROR; } + if (strcmp(argv[0], "invoke") == 0) + { + ChipLogProgress(DeviceLayer, "invoke"); + if (argc < 8) + { + return PrintAllCommands(); + } + + // Keypad Input: send key + // cast invoke 0 1 1289 0 0 5000 {"0x0":{"tlvType":"0x04","value":7}} + + // MediaPlayback: play + // cast invoke 0 1 1286 0 0 5000 {} + + char * eptr; + chip::NodeId node = (chip::NodeId) strtoull(argv[1], &eptr, 0); + chip::EndpointId endpointId = (chip::EndpointId) strtoull(argv[2], &eptr, 0); + chip::ClusterId clusterId = (chip::ClusterId) strtoull(argv[3], &eptr, 0); + chip::CommandId commandId = (chip::CommandId) strtoull(argv[4], &eptr, 0); + uint16_t timedRequestTimeoutMs = (uint16_t) strtoull(argv[5], &eptr, 0); + uint16_t imTimeoutMs = (uint16_t) strtoull(argv[6], &eptr, 0); + char * json = argv[7]; + + ChipLogProgress(DeviceLayer, "invoke json=%s", json); + + SendClusterCommand(node, endpointId, clusterId, commandId, timedRequestTimeoutMs, imTimeoutMs, json); + return CHIP_NO_ERROR; + } + if (strcmp(argv[0], "run-json-tests") == 0) + { + ChipLogProgress(DeviceLayer, "run-json-tests"); + return RunJsonTests(); + } #if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT if (strcmp(argv[0], "sendudc") == 0) { diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index 65a4c36e90b44c..619e89b6c98418 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -21,6 +21,7 @@ config("config") { include_dirs = [ ".", "${chip_root}/examples/chip-tool", + "${chip_root}/examples/common/json", "${chip_root}/zzz_generated/chip-tool", "${chip_root}/zzz_generated/tv-casting-app", "${chip_root}/src/lib", @@ -46,6 +47,8 @@ chip_data_model("tv-casting-common") { "${chip_root}/examples/chip-tool/commands/common/HexConversion.h", "${chip_root}/examples/chip-tool/commands/common/RemoteDataModelLogger.cpp", "${chip_root}/examples/chip-tool/commands/common/RemoteDataModelLogger.h", + "${chip_root}/examples/common/json/JsonTLV.cpp", + "${chip_root}/examples/common/json/JsonTLV.h", "${chip_root}/src/controller/ExamplePersistentStorage.cpp", "${chip_root}/src/controller/ExamplePersistentStorage.h", "${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp", diff --git a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h index fd427afc712639..ab6ed1f74da49c 100644 --- a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h +++ b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h @@ -79,6 +79,8 @@ class CastingServer : public AppDelegate std::function onConnectionFailure, std::function onNewOrUpdatedEndpoint); void ReadServerClustersForNode(chip::NodeId nodeId); + void SendCommand(chip::app::CommandSender::Callback & callback, uint32_t endpointId, uint32_t clusterId, uint32_t commandId, + chip::TLV::TLVReader & reader, uint16_t timedRequestTimeoutMs, uint16_t imTimeoutMs); static void OnDescriptorReadSuccessResponse(void * context, const chip::app::DataModel::DecodableList & responseList); static void OnDescriptorReadFailureResponse(void * context, CHIP_ERROR error); diff --git a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp index 1d40cb500c287e..1e489353139d2d 100644 --- a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp +++ b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp @@ -147,6 +147,7 @@ CHIP_ERROR CastingServer::OpenBasicCommissioningWindow(CommissioningCallbacks co mOnConnectionSuccessClientCallback = onConnectionSuccess; mOnConnectionFailureClientCallback = onConnectionFailure; mOnNewOrUpdatedEndpoint = onNewOrUpdatedEndpoint; + ChipLogError(AppServer, "-----------------------CastingServer PrepareForCommissioning"); return Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow(kCommissioningWindowTimeout); } @@ -290,6 +291,55 @@ void CastingServer::ReadServerClustersForNode(NodeId nodeId) } } +void CastingServer::SendCommand(chip::app::CommandSender::Callback & callback, uint32_t endpointId, uint32_t clusterId, + uint32_t commandId, chip::TLV::TLVReader & reader, uint16_t timedRequestTimeoutMs, + uint16_t imTimeoutMs) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + app::CommandSender * commandSender; + chip::TLV::TLVWriter * writer = nullptr; + + const OperationalDeviceProxy * device = mActiveTargetVideoPlayerInfo.GetOperationalDeviceProxy(); + if (device == nullptr) + { + ChipLogError(AppServer, "Failed in getting an instance of DeviceProxy"); + return; + } + VerifyOrExit(device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); + + commandSender = Platform::New(&callback, device->GetExchangeManager(), timedRequestTimeoutMs != 0); + + SuccessOrExit(err = commandSender->PrepareCommand(app::CommandPathParams(static_cast(endpointId), /* group id */ 0, + static_cast(clusterId), + static_cast(commandId), + app::CommandPathFlags::kEndpointIdValid), + false)); + + writer = commandSender->GetCommandDataIBTLVWriter(); + VerifyOrExit(writer != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + SuccessOrExit(err = writer->CopyContainer(chip::TLV::ContextTag(app::CommandDataIB::Tag::kFields), reader)); + + SuccessOrExit(err = commandSender->FinishCommand(timedRequestTimeoutMs != 0 ? Optional(timedRequestTimeoutMs) + : Optional::Missing())); + + SuccessOrExit(err = + commandSender->SendCommandRequest(device->GetSecureSession().Value(), + imTimeoutMs != 0 ? MakeOptional(System::Clock::Milliseconds32(imTimeoutMs)) + : Optional::Missing())); + +exit: + + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "SendCommand Invoke Error: %s", err.AsString()); + if (commandSender != nullptr) + { + chip::Platform::Delete(commandSender); + } + } +} + void CastingServer::ReadServerClusters(EndpointId endpointId) { const OperationalDeviceProxy * deviceProxy = mActiveTargetVideoPlayerInfo.GetOperationalDeviceProxy();