Skip to content

Commit

Permalink
update json/tlv for report (project-chip#28639)
Browse files Browse the repository at this point in the history
  • Loading branch information
yunhanw-google authored and HunsupJung committed Oct 23, 2023
1 parent 7254d33 commit 001d0e6
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package com.matter.controller.commands.pairing
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

Expand Down Expand Up @@ -35,9 +37,83 @@ class PairOnNetworkLongImReadCommand(
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)
}
}
}

Expand All @@ -56,9 +132,23 @@ class PairOnNetworkLongImReadCommand(
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()
)
)

Expand All @@ -77,14 +167,7 @@ class PairOnNetworkLongImReadCommand(
.getConnectedDevicePointer(getNodeId(), InternalGetConnectedDeviceCallback())
clear()
currentCommissioner()
.readPath(
InternalReportCallback(),
devicePointer,
attributePathList,
Collections.emptyList(),
false,
0
)
.readPath(InternalReportCallback(), devicePointer, attributePathList, eventPathList, false, 0)
waitCompleteMs(getTimeoutMillis())
}

Expand All @@ -94,5 +177,7 @@ class PairOnNetworkLongImReadCommand(
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
}
}
53 changes: 38 additions & 15 deletions src/controller/java/AndroidCallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -40,6 +39,8 @@ namespace Controller {

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)
Expand Down Expand Up @@ -224,6 +225,32 @@ void ReportCallback::OnReportEnd()
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)
{
Expand Down Expand Up @@ -252,9 +279,7 @@ void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPat
}

TLV::TLVReader readerForJavaTLV;
TLV::TLVReader readerForJson;
readerForJavaTLV.Init(*apData);
readerForJson.Init(*apData);

jobject value = nullptr;
#if USE_JAVA_TLV_ENCODE_DECODE
Expand All @@ -276,6 +301,7 @@ void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPat
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.)
Expand All @@ -287,11 +313,10 @@ void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPat
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;
Expand Down Expand Up @@ -366,9 +391,7 @@ void ReportCallback::OnEventData(const app::EventHeader & aEventHeader, TLV::TLV
}

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);
Expand Down Expand Up @@ -420,11 +443,10 @@ void ReportCallback::OnEventData(const app::EventHeader & aEventHeader, TLV::TLV
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;
Expand Down Expand Up @@ -492,7 +514,8 @@ CHIP_ERROR InvokeCallback::CreateInvokeElement(const app::ConcreteCommandPath &
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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -51,6 +57,35 @@ public Optional<Long> getDataVersion() {
return dataVersion;
}

/**
* 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}.
*
Expand Down Expand Up @@ -80,7 +115,7 @@ public String toString() {
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(
Expand All @@ -91,7 +126,7 @@ public String toString() {
builder.append(eventId);
builder.append(": ");
builder.append(
eventState.getValue() == null ? "null" : eventState.getValue().toString());
eventState.getJson() == null ? "null" : eventState.getJson().toString());
builder.append("\n");
});
});
Expand Down

0 comments on commit 001d0e6

Please sign in to comment.