Skip to content

Commit

Permalink
Feature/optional qr code info kotlin (#31251)
Browse files Browse the repository at this point in the history
* add tlv parser for qr codes

* add optional extension and vendor data

* add test

* moar clean up

* update formatting

* resolve casting errors

* fix test package name

* fix formatting issues

* fix lint errors

* potential smart cast fix

* remove outer let

* fix unresolved

* clean up

* add optionalQRCodeInfo back

* add “addOptionalQRCodeInfo” back

* update equals methods

* use proper int types

* clean up

* make optional fields private

* make add optional data methods private

* clean up test imports

* update test formatting

* reduce number of nested conditions

* add parse tlv fields method

* Reduce nested conditions
  • Loading branch information
stevePalmerin authored and pull[bot] committed Feb 8, 2024
1 parent 924318f commit 1035978
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,9 @@ class OnboardingPayload(
*/
var setupPinCode: Long = 0
) {
var optionalQRCodeInfo: HashMap<Int, OptionalQRCodeInfo>
private val optionalVendorData: HashMap<Int, OptionalQRCodeInfo>
private val optionalExtensionData: HashMap<Int, OptionalQRCodeInfoExtension>

init {
optionalQRCodeInfo = HashMap()
optionalVendorData = HashMap()
optionalExtensionData = HashMap()
}
var optionalQRCodeInfo: HashMap<Int, OptionalQRCodeInfo> = HashMap()
private val optionalVendorData: HashMap<Int, OptionalQRCodeInfo> = HashMap()
private val optionalExtensionData: HashMap<Int, OptionalQRCodeInfoExtension> = HashMap()

constructor(
version: Int,
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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<Byte>,
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<Byte>, index: AtomicInteger, numberOfBitsToRead: Int): Long {
Expand Down
50 changes: 50 additions & 0 deletions src/controller/java/tests/matter/onboardingpayload/QRCodeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down

0 comments on commit 1035978

Please sign in to comment.