diff --git a/src/controller/java/src/matter/onboardingpayload/OnboardingPayload.kt b/src/controller/java/src/matter/onboardingpayload/OnboardingPayload.kt index 6eb71d166e053a..f00b2cbef8ed54 100644 --- a/src/controller/java/src/matter/onboardingpayload/OnboardingPayload.kt +++ b/src/controller/java/src/matter/onboardingpayload/OnboardingPayload.kt @@ -115,15 +115,9 @@ class OnboardingPayload( */ var setupPinCode: Long = 0 ) { - var optionalQRCodeInfo: HashMap - private val optionalVendorData: HashMap - private val optionalExtensionData: HashMap - - init { - optionalQRCodeInfo = HashMap() - optionalVendorData = HashMap() - optionalExtensionData = HashMap() - } + var optionalQRCodeInfo: HashMap = HashMap() + private val optionalVendorData: HashMap = HashMap() + private val optionalExtensionData: HashMap = HashMap() constructor( version: Int, @@ -325,7 +319,7 @@ class OnboardingPayload( val info = OptionalQRCodeInfoExtension() info.tag = kSerialNumberTag info.type = OptionalQRCodeInfoType.TYPE_UINT32 - info.uint32 = serialNumber.toLong() + info.uint32 = serialNumber.toUInt() addOptionalExtensionData(info) } diff --git a/src/controller/java/src/matter/onboardingpayload/OptionalQRCodeInfo.kt b/src/controller/java/src/matter/onboardingpayload/OptionalQRCodeInfo.kt index 4bd203f9f5d14f..742c016d9507cd 100644 --- a/src/controller/java/src/matter/onboardingpayload/OptionalQRCodeInfo.kt +++ b/src/controller/java/src/matter/onboardingpayload/OptionalQRCodeInfo.kt @@ -31,10 +31,54 @@ open class OptionalQRCodeInfo { var type: OptionalQRCodeInfoType = OptionalQRCodeInfoType.TYPE_UNKNOWN var data: String? = null var int32: Int = 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is OptionalQRCodeInfo) return false + + return tag == other.tag && type == other.type && data == other.data && int32 == other.int32 + } + + override fun hashCode(): Int { + var result = tag + result = 31 * result + type.hashCode() + result = 31 * result + (data?.hashCode() ?: 0) + result = 31 * result + int32 + return result + } } class OptionalQRCodeInfoExtension : OptionalQRCodeInfo() { var int64: Long = 0 - var uint32: Long = 0 - var uint64: Long = 0 + var uint32: UInt = 0u + var uint64: ULong = 0u + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (!super.equals(other)) return false + if (other !is OptionalQRCodeInfoExtension) return false + + return int64 == other.int64 && uint32 == other.uint32 && uint64 == other.uint64 + } + + override fun toString(): String { + return "OptionalQRCodeInfoExtension(" + + "tag=$tag, " + + "type=$type, " + + "data=$data, " + + "int32=$int32, " + + "int64=$int64, " + + "uint32=$uint32, " + + "uint64=$uint64" + + ")" + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + int64.hashCode() + result = 31 * result + uint32.hashCode() + result = 31 * result + uint64.hashCode() + return result + } } diff --git a/src/controller/java/src/matter/onboardingpayload/QRCodeOnboardingPayloadParser.kt b/src/controller/java/src/matter/onboardingpayload/QRCodeOnboardingPayloadParser.kt index 88350e343ae072..86b8040a52c33b 100644 --- a/src/controller/java/src/matter/onboardingpayload/QRCodeOnboardingPayloadParser.kt +++ b/src/controller/java/src/matter/onboardingpayload/QRCodeOnboardingPayloadParser.kt @@ -17,23 +17,30 @@ package matter.onboardingpayload +import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicInteger +import matter.tlv.ContextSpecificTag +import matter.tlv.Element +import matter.tlv.IntValue +import matter.tlv.TlvReader +import matter.tlv.Utf8StringValue /** * @class QRCodeOnboardingPayloadParser A class that can be used to convert a base38 encoded payload * to a OnboardingPayload object */ class QRCodeOnboardingPayloadParser(private val mBase38Representation: String) { + fun populatePayload(): OnboardingPayload { - var indexToReadFrom: AtomicInteger = AtomicInteger(0) - var outPayload: OnboardingPayload = OnboardingPayload() + val indexToReadFrom = AtomicInteger(0) + val outPayload = OnboardingPayload() val payload = extractPayload(mBase38Representation) - if (payload.length == 0) { + if (payload.isEmpty()) { throw UnrecognizedQrCodeException("Invalid argument") } - var buf = base38Decode(payload) + val buf = base38Decode(payload) var dest = readBits(buf, indexToReadFrom, kVersionFieldLengthInBits) outPayload.version = dest.toInt() @@ -60,11 +67,67 @@ class QRCodeOnboardingPayloadParser(private val mBase38Representation: String) { throw UnrecognizedQrCodeException("Invalid argument") } - // TODO: populate TLV optional fields + populateTLV(outPayload, buf, indexToReadFrom) return outPayload } + private fun populateTLV( + payload: OnboardingPayload, + payloadData: ArrayList, + index: AtomicInteger + ) { + val bitsLeftToRead = (payloadData.count() * 8) - index.get() + val tlvBytesLength = (bitsLeftToRead + 7) / 8 + + if (tlvBytesLength == 0) { + return + } + val byteBuffer = ByteBuffer.allocate(tlvBytesLength) + repeat(tlvBytesLength) { + val value = readBits(payloadData, index, 8) + byteBuffer.put(value.toByte()) + } + + val reader = TlvReader(byteBuffer.array()) + while (true) { + val element = reader.nextElement() + if (reader.isEndOfTlv()) { + break + } + parseTLVFields(element, payload) + } + } + + private fun parseTLVFields(element: Element, payload: OnboardingPayload) { + // update tag + val tag = element.tag + if (tag !is ContextSpecificTag) { + return + } + + if (tag.tagNumber < 0x80) { + // add serial number + if (tag.tagNumber == kSerialNumberTag) { + val value = element.value + if (value is IntValue) { + payload.addSerialNumber(value.value.toInt()) + } + if (value is Utf8StringValue) { + payload.addSerialNumber(value.value) + } + } + } else { + // add extension values + val value = element.value + if (value is IntValue) { + payload.addOptionalVendorData(tag.tagNumber, value.value.toInt()) + } else if (value is Utf8StringValue) { + payload.addOptionalVendorData(tag.tagNumber, value.value) + } + } + } + companion object { // Populate numberOfBits into dest from buf starting at startIndex fun readBits(buf: ArrayList, index: AtomicInteger, numberOfBitsToRead: Int): Long { diff --git a/src/controller/java/tests/matter/onboardingpayload/QRCodeTest.kt b/src/controller/java/tests/matter/onboardingpayload/QRCodeTest.kt index b97725c5c3946f..62dff45174b7ed 100644 --- a/src/controller/java/tests/matter/onboardingpayload/QRCodeTest.kt +++ b/src/controller/java/tests/matter/onboardingpayload/QRCodeTest.kt @@ -363,6 +363,56 @@ class QRCodeTest { .isEqualTo("MT:W0GU2OTB00KA0648G00") } + /* + * Test QRCode with optional data + * + * matches iOS test + * https://github.com/project-chip/connectedhomeip/blob/927962863180270091c1694d4b1ce2e9ea16b8b5/src/darwin/Framework/CHIPTests/MTRSetupPayloadParserTests.m#L155 + */ + @Test + fun testQRCodeWithOptionalData() { + val payload = + OnboardingPayload( + discriminator = 128, + setupPinCode = 2048, + version = 0, + vendorId = 12, + productId = 1, + commissioningFlow = CommissioningFlow.STANDARD.value, + discoveryCapabilities = mutableSetOf(DiscoveryCapability.SOFT_AP), + ) + val parsedQrCode = + OnboardingPayloadParser() + .parseQrCode("MT:M5L90MP500K64J0A33P0SET70" + ".QT52B.E23-WZE0WISA0DK5N1K8SQ1RYCU1O0") + assertThat(parsedQrCode).isEqualTo(payload) + + var optionalQRCodeInfo = OptionalQRCodeInfoExtension() + // Test 1st optional field + optionalQRCodeInfo.tag = 0 + optionalQRCodeInfo.type = OptionalQRCodeInfoType.TYPE_STRING + optionalQRCodeInfo.data = "123456789" + + assertThat(parsedQrCode.getAllOptionalExtensionData()[0]).isEqualTo(optionalQRCodeInfo) + // verify we can grab just the serial number as well + assertThat(parsedQrCode.getSerialNumber()).isEqualTo("123456789") + + // Test 2nd optional field + optionalQRCodeInfo = OptionalQRCodeInfoExtension() + optionalQRCodeInfo.tag = 130 + optionalQRCodeInfo.type = OptionalQRCodeInfoType.TYPE_STRING + optionalQRCodeInfo.data = "myData" + + assertThat(parsedQrCode.getAllOptionalVendorData()[0]).isEqualTo(optionalQRCodeInfo) + + // Test 3rd optional field + optionalQRCodeInfo = OptionalQRCodeInfoExtension() + optionalQRCodeInfo.tag = 131 + optionalQRCodeInfo.type = OptionalQRCodeInfoType.TYPE_INT32 + optionalQRCodeInfo.int32 = 12 + + assertThat(parsedQrCode.getAllOptionalVendorData()[1]).isEqualTo(optionalQRCodeInfo) + } + companion object { const val kDefaultPayloadQRCode: String = "MT:M5L90MP500K64J00000" }