update json/tlv for report (#28639)

diff --git a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
index f432027..bdf43df 100644
--- a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
+++ b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
@@ -3,11 +3,13 @@
 import chip.devicecontroller.ChipDeviceController
 import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback
 import chip.devicecontroller.ReportCallback
+import chip.devicecontroller.model.AttributeState
 import chip.devicecontroller.model.ChipAttributePath
 import chip.devicecontroller.model.ChipEventPath
+import chip.devicecontroller.model.ChipPathId
+import chip.devicecontroller.model.EventState
 import chip.devicecontroller.model.NodeState
 import com.matter.controller.commands.common.CredentialsIssuer
-import java.util.Collections
 import java.util.logging.Level
 import java.util.logging.Logger
 
@@ -35,9 +37,83 @@
       setFailure("read failure")
     }
 
+    // kotlin-detect complains that bytearray as a magic number, but we cannot define bytearray
+    // as a well named constant and const can only support with primitive and string.
+    @Suppress("MagicNumber")
+    fun checkLocalConfigDisableAttributeTlv(attribute: AttributeState): Boolean =
+      attribute.getTlv().contentEquals(byteArrayOf(0x8))
+
+    fun checkLocalConfigDisableAttributeJson(attribute: AttributeState): Boolean =
+      attribute.getJson().toString() == """{"16:BOOL":false}"""
+
+    // kotlin-detect complains that bytearray as a magic number, but we cannot define bytearray
+    // as a well named constant and const can only support with primitive and string.
+    @Suppress("MagicNumber")
+    fun checkStartUpEventTlv(event: EventState): Boolean =
+      event.getTlv().contentEquals(byteArrayOf(0x15, 0x24, 0x0, 0x1, 0x18))
+
+    fun checkStartUpEventJson(event: EventState): Boolean =
+      event.getJson().toString() == """{"0:STRUCT":{"0:UINT":1}}"""
+
+    fun checkAllAttributesJsonForBasicCluster(cluster: String): Boolean {
+      val expected =
+        """{"16:BOOL":false,""" +
+          """"65531:ARRAY-UINT":[""" +
+          """0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,65528,65529,65531,65532,65533]}"""
+      return cluster.equals(expected)
+    }
+
+    private fun validateResponse(nodeState: NodeState) {
+      val endpointZero =
+        requireNotNull(nodeState.getEndpointState(0)) { "Endpoint zero not found." }
+
+      val basicCluster =
+        requireNotNull(endpointZero.getClusterState(CLUSTER_ID_BASIC)) {
+          "Basic cluster not found."
+        }
+
+      val localConfigDisabledAttribute =
+        requireNotNull(basicCluster.getAttributeState(ATTR_ID_LOCAL_CONFIG_DISABLED)) {
+          "No local config disabled attribute found."
+        }
+
+      val startUpEvents =
+        requireNotNull(basicCluster.getEventState(EVENT_ID_START_UP)) { "No start up event found." }
+
+      val clusterAttributes =
+        requireNotNull(basicCluster.getAttributesJson()) { "No basicCluster attribute found." }
+
+      require(checkLocalConfigDisableAttributeTlv(localConfigDisabledAttribute)) {
+        "Invalid local config disabled attribute TLV ${localConfigDisabledAttribute.getTlv().contentToString()}"
+      }
+
+      require(checkLocalConfigDisableAttributeJson(localConfigDisabledAttribute)) {
+        "Invalid local config disabled attribute Json ${localConfigDisabledAttribute.getJson().toString()}"
+      }
+
+      require(startUpEvents.isNotEmpty()) { "Unexpected: startUpEvents is empty" }
+
+      require(checkStartUpEventTlv(startUpEvents[0])) {
+        "Invalid start up event TLV ${startUpEvents[0].getTlv().contentToString()}"
+      }
+
+      require(checkStartUpEventJson(startUpEvents[0])) {
+        "Invalid start up event Json ${startUpEvents[0].getJson().toString()}"
+      }
+
+      require(checkAllAttributesJsonForBasicCluster(clusterAttributes)) {
+        "Invalid basic cluster attributes Json ${clusterAttributes}"
+      }
+    }
+
     override fun onReport(nodeState: NodeState) {
-      logger.log(Level.INFO, "Read receve onReport")
-      setSuccess()
+      logger.log(Level.INFO, nodeState.toString())
+      try {
+        validateResponse(nodeState)
+        setSuccess()
+      } catch (ex: IllegalArgumentException) {
+        setFailure(ex.message)
+      }
     }
   }
 
@@ -56,9 +132,23 @@
     val attributePathList =
       listOf(
         ChipAttributePath.newInstance(
-          /* endpointId= */ 0,
-          CLUSTER_ID_BASIC,
-          ATTR_ID_LOCAL_CONFIG_DISABLED
+          ChipPathId.forId(/* endpointId= */ 0),
+          ChipPathId.forId(CLUSTER_ID_BASIC),
+          ChipPathId.forId(ATTR_ID_LOCAL_CONFIG_DISABLED),
+        ),
+        ChipAttributePath.newInstance(
+          ChipPathId.forId(/* endpointId= */ 0),
+          ChipPathId.forId(CLUSTER_ID_BASIC),
+          ChipPathId.forId(GLOBAL_ATTRIBUTE_LIST),
+        )
+      )
+
+    val eventPathList =
+      listOf(
+        ChipEventPath.newInstance(
+          ChipPathId.forWildcard(),
+          ChipPathId.forWildcard(),
+          ChipPathId.forWildcard()
         )
       )
 
@@ -77,14 +167,7 @@
       .getConnectedDevicePointer(getNodeId(), InternalGetConnectedDeviceCallback())
     clear()
     currentCommissioner()
-      .readPath(
-        InternalReportCallback(),
-        devicePointer,
-        attributePathList,
-        Collections.emptyList(),
-        false,
-        0
-      )
+      .readPath(InternalReportCallback(), devicePointer, attributePathList, eventPathList, false, 0)
     waitCompleteMs(getTimeoutMillis())
   }
 
@@ -94,5 +177,7 @@
     private const val MATTER_PORT = 5540
     private const val CLUSTER_ID_BASIC = 0x0028L
     private const val ATTR_ID_LOCAL_CONFIG_DISABLED = 16L
+    private const val EVENT_ID_START_UP = 0L
+    private const val GLOBAL_ATTRIBUTE_LIST = 65531L
   }
 }
diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp
index daa7f9e..1598567 100644
--- a/src/controller/java/AndroidCallbacks.cpp
+++ b/src/controller/java/AndroidCallbacks.cpp
@@ -29,7 +29,6 @@
 #include <lib/support/JniReferences.h>
 #include <lib/support/JniTypeWrappers.h>
 #include <lib/support/jsontlv/JsonToTlv.h>
-#include <lib/support/jsontlv/TlvJson.h>
 #include <lib/support/jsontlv/TlvToJson.h>
 #include <lib/support/logging/CHIPLogging.h>
 #include <platform/PlatformManager.h>
@@ -40,6 +39,8 @@
 
 static const int MILLIS_SINCE_BOOT  = 0;
 static const int MILLIS_SINCE_EPOCH = 1;
+// Add the bytes for attribute tag(1:control + 8:tag + 8:length) and structure(1:struct + 1:close container)
+static const int EXTRA_SPACE_FOR_ATTRIBUTE_TAG = 19;
 
 GetConnectedDeviceCallback::GetConnectedDeviceCallback(jobject wrapperCallback, jobject javaCallback) :
     mOnSuccess(OnDeviceConnectedFn, this), mOnFailure(OnDeviceConnectionFailureFn, this)
@@ -224,6 +225,32 @@
     VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
 }
 
+// Convert TLV blob to Json with structure, the element's tag is replaced with the actual attributeId.
+CHIP_ERROR ConvertReportTlvToJson(const uint32_t id, TLV::TLVReader & data, std::string & json)
+{
+    TLV::TLVWriter writer;
+    TLV::TLVReader readerForJavaTLV;
+    uint32_t size    = 0;
+    size_t bufferLen = readerForJavaTLV.GetTotalLength() + EXTRA_SPACE_FOR_ATTRIBUTE_TAG;
+    readerForJavaTLV.Init(data);
+    std::unique_ptr<uint8_t[]> buffer = std::unique_ptr<uint8_t[]>(new uint8_t[bufferLen]);
+    writer.Init(buffer.get(), bufferLen);
+    TLV::TLVType outer;
+
+    ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer));
+    TLV::Tag tag;
+    ReturnErrorOnFailure(ConvertTlvTag(id, tag));
+    ReturnErrorOnFailure(writer.CopyElement(tag, readerForJavaTLV));
+    ReturnErrorOnFailure(writer.EndContainer(outer));
+    size = writer.GetLengthWritten();
+
+    TLV::TLVReader readerForJson;
+    readerForJson.Init(buffer.get(), size);
+    ReturnErrorOnFailure(readerForJson.Next());
+    // Convert TLV to JSON
+    return TlvToJson(readerForJson, json);
+}
+
 void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
                                      const app::StatusIB & aStatus)
 {
@@ -252,9 +279,7 @@
     }
 
     TLV::TLVReader readerForJavaTLV;
-    TLV::TLVReader readerForJson;
     readerForJavaTLV.Init(*apData);
-    readerForJson.Init(*apData);
 
     jobject value = nullptr;
 #if USE_JAVA_TLV_ENCODE_DECODE
@@ -276,6 +301,7 @@
     size_t bufferLen                  = readerForJavaTLV.GetRemainingLength() + readerForJavaTLV.GetLengthRead();
     std::unique_ptr<uint8_t[]> buffer = std::unique_ptr<uint8_t[]>(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.)
@@ -287,11 +313,10 @@
     chip::ByteArray jniByteArray(env, reinterpret_cast<jbyte *>(buffer.get()), size);
 
     // Convert TLV to JSON
-    Json::Value json;
-    err = TlvToJson(readerForJson, json);
+    std::string json;
+    err = ConvertReportTlvToJson(static_cast<uint32_t>(aPath.mAttributeId), *apData, json);
     VerifyOrReturn(err == CHIP_NO_ERROR, ReportError(attributePathObj, nullptr, err));
-
-    UtfString jsonString(env, JsonToString(json).c_str());
+    UtfString jsonString(env, json.c_str());
 
     // Create AttributeState object
     jclass attributeStateCls;
@@ -366,9 +391,7 @@
     }
 
     TLV::TLVReader readerForJavaTLV;
-    TLV::TLVReader readerForJson;
     readerForJavaTLV.Init(*apData);
-    readerForJson.Init(*apData);
 
     jlong eventNumber    = static_cast<jlong>(aEventHeader.mEventNumber);
     jint priorityLevel   = static_cast<jint>(aEventHeader.mPriorityLevel);
@@ -420,11 +443,10 @@
     chip::ByteArray jniByteArray(env, reinterpret_cast<jbyte *>(buffer.get()), size);
 
     // Convert TLV to JSON
-    Json::Value json;
-    err = TlvToJson(readerForJson, json);
-    VerifyOrReturn(err == CHIP_NO_ERROR, ReportError(nullptr, eventPathObj, err));
-
-    UtfString jsonString(env, JsonToString(json).c_str());
+    std::string json;
+    err = ConvertReportTlvToJson(static_cast<uint32_t>(aEventHeader.mPath.mEventId), *apData, json);
+    VerifyOrReturn(err == CHIP_NO_ERROR, ReportError(eventPathObj, nullptr, err));
+    UtfString jsonString(env, json.c_str());
 
     // Create EventState object
     jclass eventStateCls;
@@ -492,7 +514,8 @@
         readerForJavaTLV.Init(*apData);
 
         // Create TLV byte array to pass to Java layer
-        size_t bufferLen                  = readerForJavaTLV.GetRemainingLength() + readerForJavaTLV.GetLengthRead();
+        size_t bufferLen = readerForJavaTLV.GetRemainingLength() + readerForJavaTLV.GetLengthRead();
+        ;
         std::unique_ptr<uint8_t[]> buffer = std::unique_ptr<uint8_t[]>(new uint8_t[bufferLen]);
         uint32_t size                     = 0;
 
diff --git a/src/controller/java/src/chip/devicecontroller/model/ClusterState.java b/src/controller/java/src/chip/devicecontroller/model/ClusterState.java
index 8507682..4c575f1 100644
--- a/src/controller/java/src/chip/devicecontroller/model/ClusterState.java
+++ b/src/controller/java/src/chip/devicecontroller/model/ClusterState.java
@@ -17,13 +17,19 @@
  */
 package chip.devicecontroller.model;
 
+import android.util.Log;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Optional;
+import java.util.stream.Stream;
 import javax.annotation.Nullable;
+import org.json.JSONException;
+import org.json.JSONObject;
 
 /** Class for tracking CHIP cluster state in a hierarchical manner. */
 public final class ClusterState {
+  private static final String TAG = "ClusterState";
   private Map<Long, AttributeState> attributes;
   private Map<Long, ArrayList<EventState>> events;
   private Optional<Long> dataVersion;
@@ -52,6 +58,35 @@
   }
 
   /**
+   * Convenience utility for getting all attributes in Json string format.
+   *
+   * @return all attributes in Json string format., or empty string if not found.
+   */
+  public String getAttributesJson() {
+    JSONObject combinedObject = new JSONObject();
+    Stream<JSONObject> attributeJsons =
+        attributes.values().stream().map(it -> it.getJson()).filter(it -> it != null);
+
+    attributeJsons.forEach(
+        attributes -> {
+          for (Iterator<String> iterator = attributes.keys(); iterator.hasNext(); ) {
+            String key = iterator.next();
+            if (combinedObject.has(key)) {
+              Log.e(TAG, "Conflicting attribute tag Id is found: " + key);
+              continue;
+            }
+            try {
+              Object value = attributes.get(key);
+              combinedObject.put(key, value);
+            } catch (JSONException ex) {
+              Log.e(TAG, "receive attribute json exception: " + ex);
+            }
+          }
+        });
+    return combinedObject.toString();
+  }
+
+  /**
    * Convenience utility for getting an {@code AttributeState}.
    *
    * @return the {@code AttributeState} for the specified id, or null if not found.
@@ -80,7 +115,7 @@
           builder.append(attributeId);
           builder.append(": ");
           builder.append(
-              attributeState.getValue() == null ? "null" : attributeState.getValue().toString());
+              attributeState.getJson() == null ? "null" : attributeState.getJson().toString());
           builder.append("\n");
         });
     events.forEach(
@@ -91,7 +126,7 @@
                 builder.append(eventId);
                 builder.append(": ");
                 builder.append(
-                    eventState.getValue() == null ? "null" : eventState.getValue().toString());
+                    eventState.getJson() == null ? "null" : eventState.getJson().toString());
                 builder.append("\n");
               });
         });