Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update json/tlv for report using latest json/tlv conversion #28639

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
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]}"""
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
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(
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
attributes -> {
for (Iterator<String> iterator = attributes.keys(); iterator.hasNext(); ) {
String key = iterator.next();
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
if (combinedObject.has(key)) {
Log.e(TAG, "Conflicting attribute tag Id is found: " + key);
return;
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading