From db80c27c71bca4ef4bb78cba39b6ae44556f7ba3 Mon Sep 17 00:00:00 2001 From: yunhanw-google Date: Fri, 18 Aug 2023 16:15:37 -0700 Subject: [PATCH] [Android]add json support for invoke and write using latest json/tlv conversion (#28759) --- .../PairOnNetworkLongImInvokeCommand.kt | 66 ++++++++++--- .../PairOnNetworkLongImWriteCommand.kt | 26 +++-- src/controller/java/AndroidCallbacks.cpp | 22 +++-- .../java/CHIPDeviceController-JNI.cpp | 99 +++++++++++++++---- 4 files changed, 167 insertions(+), 46 deletions(-) diff --git a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt index 79592aeb602943..73eab9eb15537f 100644 --- a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt +++ b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt @@ -50,16 +50,28 @@ class PairOnNetworkLongImInvokeCommand( private inner class InternalInvokeCallback : InvokeCallback { override fun onError(e: Exception) { logger.log(Level.INFO, "Invoke receive onError" + e.message) - setFailure("write failure") + setFailure("invoke failure") } override fun onResponse(element: InvokeElement?, successCode: Long) { logger.log(Level.INFO, "Invoke receive OnResponse on ") if (element != null) { - logger.log(Level.INFO, element.toString()) + logger.log(Level.INFO, element.toString() + element.getJsonString()) + val clusterId = element.getClusterId().getId() + if (clusterId == CLUSTER_ID_IDENTIFY) { + logger.log(Level.INFO, "success code is $successCode") + setSuccess() + return + } else if ( + clusterId == CLUSTER_ID_TEST && element.getJsonString().equals("""{"0:UINT":2}""") + ) { + logger.log(Level.INFO, "success code is $successCode") + setSuccess() + return + } } - logger.log(Level.INFO, "success code is$successCode") - setSuccess() + + setFailure("invoke failure") } } @@ -76,20 +88,44 @@ class PairOnNetworkLongImInvokeCommand( override fun runCommand() { val number: UShort = 1u - val tlvWriter = TlvWriter() - tlvWriter.startStructure(AnonymousTag) - tlvWriter.put(ContextSpecificTag(0), number) - tlvWriter.endStructure() + val tlvWriter1 = TlvWriter() + tlvWriter1.startStructure(AnonymousTag) + tlvWriter1.put(ContextSpecificTag(0), number) + tlvWriter1.endStructure() - val element: InvokeElement = + val element1: InvokeElement = InvokeElement.newInstance( /* endpointId= */ 0, CLUSTER_ID_IDENTIFY, IDENTIFY_COMMAND, - tlvWriter.getEncoded(), + tlvWriter1.getEncoded(), + null + ) + + val tlvWriter2 = TlvWriter() + tlvWriter2.startStructure(AnonymousTag) + tlvWriter2.put(ContextSpecificTag(0), number) + tlvWriter2.put(ContextSpecificTag(1), number) + tlvWriter2.endStructure() + + val element2: InvokeElement = + InvokeElement.newInstance( + /* endpointId= */ 1, + CLUSTER_ID_TEST, + TEST_ADD_ARGUMENT_COMMAND, + tlvWriter2.getEncoded(), null ) + val element3: InvokeElement = + InvokeElement.newInstance( + /* endpointId= */ 1, + CLUSTER_ID_IDENTIFY, + IDENTIFY_COMMAND, + null, + """{"0:UINT":1}""" + ) + currentCommissioner() .pairDeviceWithAddress( getNodeId(), @@ -104,7 +140,13 @@ class PairOnNetworkLongImInvokeCommand( currentCommissioner() .getConnectedDevicePointer(getNodeId(), InternalGetConnectedDeviceCallback()) clear() - currentCommissioner().invoke(InternalInvokeCallback(), devicePointer, element, 0, 0) + currentCommissioner().invoke(InternalInvokeCallback(), devicePointer, element1, 0, 0) + waitCompleteMs(getTimeoutMillis()) + clear() + currentCommissioner().invoke(InternalInvokeCallback(), devicePointer, element2, 0, 0) + waitCompleteMs(getTimeoutMillis()) + clear() + currentCommissioner().invoke(InternalInvokeCallback(), devicePointer, element3, 0, 0) waitCompleteMs(getTimeoutMillis()) } @@ -114,5 +156,7 @@ class PairOnNetworkLongImInvokeCommand( private const val MATTER_PORT = 5540 private const val CLUSTER_ID_IDENTIFY = 0x0003L private const val IDENTIFY_COMMAND = 0L + private const val CLUSTER_ID_TEST = 0xFFF1FC05L + private const val TEST_ADD_ARGUMENT_COMMAND = 0X04L } } diff --git a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt index 1492950ed9fa1a..10945a4e67af9f 100644 --- a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt +++ b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt @@ -52,7 +52,7 @@ class PairOnNetworkLongImWriteCommand( } override fun onResponse(attributePath: ChipAttributePath?) { - logger.log(Level.INFO, "Write receve OnResponse on ") + logger.log(Level.INFO, "Write receive OnResponse on ") if (attributePath != null) { logger.log(Level.INFO, attributePath.toString()) } @@ -72,15 +72,25 @@ class PairOnNetworkLongImWriteCommand( } override fun runCommand() { - val tlvWriter = TlvWriter() - tlvWriter.put(AnonymousTag, true) - val attributeList = + val tlvWriter1 = TlvWriter() + tlvWriter1.put(AnonymousTag, true) + val attributeList1 = listOf( AttributeWriteRequest.newInstance( /* endpointId= */ 0, CLUSTER_ID_BASIC, ATTR_ID_LOCAL_CONFIG_DISABLED, - tlvWriter.getEncoded(), + tlvWriter1.getEncoded() + ) + ) + + val attributeList2 = + listOf( + AttributeWriteRequest.newInstance( + /* endpointId= */ 0, + CLUSTER_ID_BASIC, + ATTR_ID_LOCAL_CONFIG_DISABLED, + """{"40:BOOL":false}""" ) ) @@ -99,7 +109,11 @@ class PairOnNetworkLongImWriteCommand( .getConnectedDevicePointer(getNodeId(), InternalGetConnectedDeviceCallback()) clear() currentCommissioner() - .write(InternalWriteAttributesCallback(), devicePointer, attributeList, 0, 0) + .write(InternalWriteAttributesCallback(), devicePointer, attributeList1, 0, 0) + waitCompleteMs(getTimeoutMillis()) + clear() + currentCommissioner() + .write(InternalWriteAttributesCallback(), devicePointer, attributeList2, 0, 0) waitCompleteMs(getTimeoutMillis()) } diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp index f6d9499d8f8d2a..daa7f9ef8abcb0 100644 --- a/src/controller/java/AndroidCallbacks.cpp +++ b/src/controller/java/AndroidCallbacks.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2020-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. @@ -28,7 +28,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -488,15 +490,12 @@ CHIP_ERROR InvokeCallback::CreateInvokeElement(const app::ConcreteCommandPath & TLV::TLVReader readerForJavaTLV; TLV::TLVReader readerForJson; readerForJavaTLV.Init(*apData); - readerForJson.Init(*apData); // Create TLV byte array to pass to Java layer size_t bufferLen = readerForJavaTLV.GetRemainingLength() + readerForJavaTLV.GetLengthRead(); std::unique_ptr buffer = std::unique_ptr(new uint8_t[bufferLen]); uint32_t size = 0; - // The TLVReader's read head is not pointing to the first element in the container, instead of the container itself, use - // a TLVWriter to get a TLV with a normalized TLV buffer (Wrapped with an anonymous tag, no extra "end of container" tag - // at the end.) + TLV::TLVWriter writer; writer.Init(buffer.get(), bufferLen); err = writer.CopyElement(TLV::AnonymousTag(), readerForJavaTLV); @@ -505,11 +504,13 @@ CHIP_ERROR InvokeCallback::CreateInvokeElement(const app::ConcreteCommandPath & chip::ByteArray jniByteArray(env, reinterpret_cast(buffer.get()), size); // Convert TLV to JSON - Json::Value json; + std::string json; + readerForJson.Init(buffer.get(), size); + err = readerForJson.Next(); + ReturnErrorOnFailure(err); err = TlvToJson(readerForJson, json); ReturnErrorOnFailure(err); - - UtfString jsonString(env, JsonToString(json).c_str()); + UtfString jsonString(env, json.c_str()); outObj = env->CallStaticObjectMethod(invokeElementCls, invokeElementCtor, static_cast(aPath.mEndpointId), static_cast(aPath.mClusterId), static_cast(aPath.mCommandId), jniByteArray.jniValue(), jsonString.jniValue()); @@ -614,6 +615,7 @@ void ReportCallback::ReportError(jobject attributePath, jobject eventPath, const CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + ChipLogError(Controller, "ReportCallback::ReportError is called with %u", errorCode); jthrowable exception; err = AndroidControllerExceptions::GetInstance().CreateAndroidControllerException(env, message, errorCode, exception); VerifyOrReturn( @@ -722,7 +724,7 @@ void WriteAttributesCallback::ReportError(jobject attributePath, const char * me CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - ChipLogError(Controller, "WriteAttributesCallback ReportError is called"); + ChipLogError(Controller, "WriteAttributesCallback::ReportError is called with %u", errorCode); jthrowable exception; err = AndroidControllerExceptions::GetInstance().CreateAndroidControllerException(env, message, errorCode, exception); VerifyOrReturn(err == CHIP_NO_ERROR, @@ -825,7 +827,7 @@ void InvokeCallback::ReportError(const char * message, ChipError::StorageType er CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - ChipLogError(Controller, "InvokeCallback ReportError is called"); + ChipLogError(Controller, "InvokeCallback::ReportError is called with %u", errorCode); jthrowable exception; err = AndroidControllerExceptions::GetInstance().CreateAndroidControllerException(env, message, errorCode, exception); VerifyOrReturn( diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 5d93b3075a2d8b..be7bfa6b28008f 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -46,6 +46,8 @@ #include #include #include +#include +#include #include #include #include @@ -1864,6 +1866,8 @@ JNI_METHOD(void, write) auto callback = reinterpret_cast(callbackHandle); app::WriteClient * writeClient = nullptr; uint16_t convertedTimedRequestTimeoutMs = static_cast(timedRequestTimeoutMs); + bool hasValidTlv = false; + bool hasValidJson = false; ChipLogDetail(Controller, "IM write() called"); @@ -1889,15 +1893,15 @@ JNI_METHOD(void, write) jmethodID hasDataVersionMethod = nullptr; jmethodID getDataVersionMethod = nullptr; jmethodID getTlvByteArrayMethod = nullptr; + jmethodID getJsonStringMethod = nullptr; jobject endpointIdObj = nullptr; jobject clusterIdObj = nullptr; jobject attributeIdObj = nullptr; jbyteArray tlvBytesObj = nullptr; bool hasDataVersion = false; Optional dataVersion = Optional(); - ; - jbyte * tlvBytesObjBytes = nullptr; - jsize length = 0; + uint8_t * tlvBytes = nullptr; + size_t length = 0; TLV::TLVReader reader; SuccessOrExit(err = JniReferences::GetInstance().GetListItem(attributeList, i, attributeItem)); @@ -1942,14 +1946,47 @@ JNI_METHOD(void, write) tlvBytesObj = static_cast(env->CallObjectMethod(attributeItem, getTlvByteArrayMethod)); VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(tlvBytesObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - - tlvBytesObjBytes = env->GetByteArrayElements(tlvBytesObj, nullptr); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - length = env->GetArrayLength(tlvBytesObj); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + if (tlvBytesObj != nullptr) + { + jbyte * tlvBytesObjBytes = env->GetByteArrayElements(tlvBytesObj, nullptr); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + length = static_cast(env->GetArrayLength(tlvBytesObj)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + tlvBytes = reinterpret_cast(tlvBytesObjBytes); + hasValidTlv = true; + } + else + { + SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getJsonString", "()Ljava/lang/String;", + &getJsonStringMethod)); + jstring jsonJniString = static_cast(env->CallObjectMethod(attributeItem, getJsonStringMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + if (jsonJniString != nullptr) + { + JniUtfString jsonUtfJniString(env, jsonJniString); + uint8_t bufWithStruct[chip::app::kMaxSecureSduLengthBytes] = { 0 }; + uint8_t buf[chip::app::kMaxSecureSduLengthBytes] = { 0 }; + TLV::TLVReader tlvReader; + TLV::TLVWriter tlvWrite; + TLV::TLVType outerContainer = TLV::kTLVType_Structure; + MutableByteSpan dataWithStruct{ bufWithStruct }; + MutableByteSpan data{ buf }; + SuccessOrExit(err = JsonToTlv(std::string(jsonUtfJniString.c_str(), jsonUtfJniString.size()), dataWithStruct)); + tlvReader.Init(dataWithStruct); + SuccessOrExit(err = tlvReader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); + SuccessOrExit(err = tlvReader.EnterContainer(outerContainer)); + SuccessOrExit(err = tlvReader.Next()); + tlvWrite.Init(data); + SuccessOrExit(err = tlvWrite.CopyElement(TLV::AnonymousTag(), tlvReader)); + SuccessOrExit(err = tlvWrite.Finalize()); + tlvBytes = buf; + length = tlvWrite.GetLengthWritten(); + hasValidJson = true; + } + } + VerifyOrExit(hasValidTlv || hasValidJson, err = CHIP_ERROR_INVALID_ARGUMENT); - reader.Init(reinterpret_cast(tlvBytesObjBytes), static_cast(length)); + reader.Init(tlvBytes, length); reader.Next(); SuccessOrExit( err = writeClient->PutPreencodedAttribute( @@ -1964,7 +2001,6 @@ JNI_METHOD(void, write) callback->mWriteClient = writeClient; exit: - if (err != CHIP_NO_ERROR) { ChipLogError(Controller, "JNI IM Write Error: %s", err.AsString()); @@ -2000,14 +2036,17 @@ JNI_METHOD(void, invoke) jmethodID getClusterIdMethod = nullptr; jmethodID getCommandIdMethod = nullptr; jmethodID getTlvByteArrayMethod = nullptr; + jmethodID getJsonStringMethod = nullptr; jobject endpointIdObj = nullptr; jobject clusterIdObj = nullptr; jobject commandIdObj = nullptr; jbyteArray tlvBytesObj = nullptr; - jbyte * tlvBytesObjBytes = nullptr; - jsize length = 0; TLV::TLVReader reader; TLV::TLVWriter * writer = nullptr; + uint8_t * tlvBytes = nullptr; + size_t length = 0; + bool hasValidTlv = false; + bool hasValidJson = false; uint16_t convertedTimedRequestTimeoutMs = static_cast(timedRequestTimeoutMs); ChipLogDetail(Controller, "IM invoke() called"); @@ -2045,12 +2084,34 @@ JNI_METHOD(void, invoke) tlvBytesObj = static_cast(env->CallObjectMethod(invokeElement, getTlvByteArrayMethod)); VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(tlvBytesObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + if (tlvBytesObj != nullptr) + { + jbyte * tlvBytesObjBytes = env->GetByteArrayElements(tlvBytesObj, nullptr); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + length = static_cast(env->GetArrayLength(tlvBytesObj)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + tlvBytes = reinterpret_cast(tlvBytesObjBytes); + hasValidTlv = true; + } + else + { + SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getJsonString", "()Ljava/lang/String;", + &getJsonStringMethod)); + jstring jsonJniString = static_cast(env->CallObjectMethod(invokeElement, getJsonStringMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + if (jsonJniString != nullptr) + { + JniUtfString jsonUtfJniString(env, jsonJniString); + uint8_t buf[chip::app::kMaxSecureSduLengthBytes] = { 0 }; + MutableByteSpan tlvEncodingLocal{ buf }; + SuccessOrExit(err = JsonToTlv(std::string(jsonUtfJniString.c_str(), jsonUtfJniString.size()), tlvEncodingLocal)); + tlvBytes = tlvEncodingLocal.data(); + length = tlvEncodingLocal.size(); + hasValidJson = true; + } + } + VerifyOrExit(hasValidTlv || hasValidJson, err = CHIP_ERROR_INVALID_ARGUMENT); - tlvBytesObjBytes = env->GetByteArrayElements(tlvBytesObj, nullptr); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - length = env->GetArrayLength(tlvBytesObj); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); SuccessOrExit(err = commandSender->PrepareCommand(app::CommandPathParams(static_cast(endpointId), /* group id */ 0, static_cast(clusterId), static_cast(commandId), @@ -2059,7 +2120,7 @@ JNI_METHOD(void, invoke) writer = commandSender->GetCommandDataIBTLVWriter(); VerifyOrExit(writer != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - reader.Init(reinterpret_cast(tlvBytesObjBytes), static_cast(length)); + reader.Init(tlvBytes, static_cast(length)); reader.Next(); SuccessOrExit(err = writer->CopyContainer(TLV::ContextTag(app::CommandDataIB::Tag::kFields), reader)); SuccessOrExit(err = commandSender->FinishCommand(convertedTimedRequestTimeoutMs != 0