blob: 1758fbd868fbfb0bbf23719a90f664c991b135b6 [file] [log] [blame]
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.DataVersionFilter
import chip.devicecontroller.model.EventState
import chip.devicecontroller.model.NodeState
import chip.devicecontroller.model.Status
import com.matter.controller.commands.common.CredentialsIssuer
import java.util.logging.Level
import java.util.logging.Logger
class PairOnNetworkLongImReadCommand(
controller: ChipDeviceController,
credsIssue: CredentialsIssuer?
) :
PairingCommand(
controller,
"onnetwork-long-im-read",
credsIssue,
PairingModeType.ON_NETWORK,
PairingNetworkType.NONE,
DiscoveryFilterType.LONG_DISCRIMINATOR
) {
private var devicePointer: Long = 0
private inner class InternalReportCallback : ReportCallback {
override fun onError(
attributePath: ChipAttributePath?,
eventPath: ChipEventPath?,
e: Exception
) {
logger.log(Level.INFO, "Read receive onError")
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 checkAllAttributesJsonForFixedLabel(cluster: String): Boolean {
val expected =
"""{"65528:ARRAY-?":[],"0:ARRAY-STRUCT":[{"0:STRING":"room","1:STRING":"bedroom 2"},""" +
"""{"0:STRING":"orientation","1:STRING":"North"},{"0:STRING":"floor","1:STRING":"2"},""" +
"""{"0:STRING":"direction","1:STRING":"up"}],"65531:ARRAY-UINT":[0,65528,65529,65531,65532,65533],""" +
""""65533:UINT":1,"65529:ARRAY-?":[],"65532:UINT":0}"""
return cluster.equals(expected)
}
fun checkUnitTestClusterGeneralStatus(status: Status): Boolean =
(status.getStatus() == Status.Code.InvalidDataType) && !status.getClusterStatus().isPresent()
fun checkUnitTestClusterClusterStatus(status: Status): Boolean =
(status.getStatus() == Status.Code.Failure) &&
status.getClusterStatus().isPresent() &&
(status.getClusterStatus().get() == CLUSTER_ID_TEST_CLUSTER_ERROR_CLUSTER_STATUS)
private fun validateResponse(nodeState: NodeState) {
val endpointZero =
requireNotNull(nodeState.getEndpointState(0)) { "Endpoint zero not found." }
val endpointOne = requireNotNull(nodeState.getEndpointState(1)) { "Endpoint one not found." }
val basicCluster =
requireNotNull(endpointZero.getClusterState(CLUSTER_ID_BASIC)) {
"Basic cluster not found."
}
val fixedLabelCluster =
requireNotNull(endpointOne.getClusterState(FIXED_LABEL_CLUSTER)) {
"fixed label cluster not found."
}
val localConfigDisabledAttribute =
requireNotNull(basicCluster.getAttributeState(ATTR_ID_LOCAL_CONFIG_DISABLED)) {
"No local config disabled attribute found."
}
val unitTestCluster =
requireNotNull(endpointOne.getClusterState(UNIT_TEST_CLUSTER)) {
"Unit test cluster not found."
}
val startUpEvents =
requireNotNull(basicCluster.getEventState(EVENT_ID_START_UP)) { "No start up event found." }
val clusterAttributes =
requireNotNull(fixedLabelCluster.getAttributesJson()) {
"No fixed label cluster 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(checkAllAttributesJsonForFixedLabel(clusterAttributes)) {
"Invalid fixed label cluster attributes Json ${clusterAttributes}"
}
require(
checkUnitTestClusterGeneralStatus(
unitTestCluster.getAttributeStatuses()[CLUSTER_ID_TEST_GENERAL_ERROR_BOOLEAN]!!
)
) {
"Invalid unit test cluster generalStatus check ${unitTestCluster}"
}
require(
checkUnitTestClusterClusterStatus(
unitTestCluster.getAttributeStatuses()[CLUSTER_ID_TEST_CLUSTER_ERROR_BOOLEAN]!!
)
) {
"Invalid unit test cluster clusterStatus check ${unitTestCluster}"
}
}
override fun onReport(nodeState: NodeState) {
logger.log(Level.INFO, nodeState.toString())
try {
validateResponse(nodeState)
setSuccess()
} catch (ex: IllegalArgumentException) {
setFailure(ex.message)
}
}
}
private inner class InternalGetConnectedDeviceCallback : GetConnectedDeviceCallback {
override fun onDeviceConnected(devicePointer: Long) {
this@PairOnNetworkLongImReadCommand.devicePointer = devicePointer
logger.log(Level.INFO, "onDeviceConnected")
}
override fun onConnectionFailure(nodeId: Long, error: Exception?) {
logger.log(Level.INFO, "onConnectionFailure")
}
}
override fun runCommand() {
val attributePathList =
listOf(
ChipAttributePath.newInstance(
ChipPathId.forWildcard(),
ChipPathId.forWildcard(),
ChipPathId.forWildcard()
),
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()
)
)
val dataVersionFilterList =
listOf(
DataVersionFilter.newInstance(
ChipPathId.forId(/* endpointId= */ 0),
ChipPathId.forId(CLUSTER_ID_BASIC),
CLUSTER_ID_BASIC_VERSION,
)
)
currentCommissioner()
.pairDeviceWithAddress(
getNodeId(),
getRemoteAddr().address.hostAddress,
MATTER_PORT,
getDiscriminator(),
getSetupPINCode(),
null
)
currentCommissioner().setCompletionListener(this)
waitCompleteMs(getTimeoutMillis())
currentCommissioner()
.getConnectedDevicePointer(getNodeId(), InternalGetConnectedDeviceCallback())
clear()
currentCommissioner()
.readPath(
InternalReportCallback(),
devicePointer,
attributePathList,
eventPathList,
dataVersionFilterList,
false,
0
)
waitCompleteMs(getTimeoutMillis())
}
companion object {
private val logger = Logger.getLogger(PairOnNetworkLongImReadCommand::class.java.name)
private const val MATTER_PORT = 5540
private const val CLUSTER_ID_BASIC = 0x0028L
private const val FIXED_LABEL_CLUSTER = 0x0040L
private const val UNIT_TEST_CLUSTER = 0xfff1fc05L
private const val ATTR_ID_LOCAL_CONFIG_DISABLED = 16L
private const val EVENT_ID_START_UP = 0L
private const val GLOBAL_ATTRIBUTE_LIST = 65531L
private const val CLUSTER_ID_BASIC_VERSION = 0L
private const val CLUSTER_ID_TEST_GENERAL_ERROR_BOOLEAN = 0x0031L
private const val CLUSTER_ID_TEST_CLUSTER_ERROR_BOOLEAN = 0x0032L
private const val CLUSTER_ID_TEST_CLUSTER_ERROR_CLUSTER_STATUS = 17
}
}