Implement unit tests for ManualOnboardingPayload (#27446)
* Implement unit tests for ManualOnboardingPayload
* Restyled by gn
---------
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/.github/workflows/java-tests.yaml b/.github/workflows/java-tests.yaml
index a0fb06a..f4fecdd 100644
--- a/.github/workflows/java-tests.yaml
+++ b/.github/workflows/java-tests.yaml
@@ -106,7 +106,8 @@
chip.tlv.TlvWriterTest \
chip.tlv.TlvReadWriteTest \
chip.tlv.TlvReaderTest \
- chip.jsontlv.JsonToTlvToJsonTest
+ chip.jsontlv.JsonToTlvToJsonTest \
+ chip.onboardingpayload.ManualCodeTest
- name: Build Java Matter Controller and all clusters app
timeout-minutes: 60
run: |
diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn
index ba6b7e9..02655bf 100644
--- a/src/controller/java/BUILD.gn
+++ b/src/controller/java/BUILD.gn
@@ -261,9 +261,24 @@
]
}
+kotlin_library("onboardingpayload_manual_code_test") {
+ output_name = "OnboardingPayloadManualCodeTest.jar"
+
+ deps = [
+ ":onboarding_payload",
+ "${chip_root}/third_party/java_deps:junit-4",
+ "${chip_root}/third_party/java_deps:truth",
+ ]
+
+ sources = [ "tests/chip/onboardingpayload/ManualCodeTest.kt" ]
+
+ kotlinc_flags = [ "-Xlint:deprecation" ]
+}
+
group("unit_tests") {
deps = [
":json_to_tlv_to_json_test",
+ ":onboardingpayload_manual_code_test",
":tlv_read_write_test",
":tlv_reader_test",
":tlv_writer_test",
diff --git a/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadGenerator.kt b/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadGenerator.kt
index c71b1f8..5814ca8 100644
--- a/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadGenerator.kt
+++ b/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadGenerator.kt
@@ -71,30 +71,31 @@
var offset = 0
- decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupCodeChunk1CharLength), chunk1)
+ decimalStringWithPadding(decimalString, offset, kManualSetupCodeChunk1CharLength, chunk1)
offset += kManualSetupCodeChunk1CharLength
- decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupCodeChunk2CharLength), chunk2)
+ decimalStringWithPadding(decimalString, offset, kManualSetupCodeChunk2CharLength, chunk2)
offset += kManualSetupCodeChunk2CharLength
- decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupCodeChunk3CharLength), chunk3)
+ decimalStringWithPadding(decimalString, offset, kManualSetupCodeChunk3CharLength, chunk3)
offset += kManualSetupCodeChunk3CharLength
if (useLongCode) {
- decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupVendorIdCharLength), payloadContents.vendorId)
+ decimalStringWithPadding(decimalString, offset, kManualSetupVendorIdCharLength, payloadContents.vendorId)
offset += kManualSetupVendorIdCharLength
- decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupProductIdCharLength), payloadContents.productId)
+ decimalStringWithPadding(decimalString, offset, kManualSetupProductIdCharLength, payloadContents.productId)
offset += kManualSetupProductIdCharLength
}
- val checkDigit = Verhoeff10.charToVal(Verhoeff10.computeCheckChar(decimalString.concatToString()))
- decimalStringWithPadding(decimalString.sliceArray(offset until offset + 2), checkDigit)
+ val str = decimalString.concatToString().substring(0, offset)
+ val checkDigit = Verhoeff10.charToVal(Verhoeff10.computeCheckChar(str))
+ decimalStringWithPadding(decimalString, offset, 1, checkDigit)
offset += 1
// Reduce decimalString size to be the size of written data and to not include null-terminator. In Kotlin, there is no direct
// method to resize an array.We use copyOfRange(0, offset) to create a new CharArray that includes only the elements from index
// 0 to offset-1, effectively reducing the size of the buffer.
- decimalString.copyOfRange(0, offset)
+ val newDecimalString = decimalString.copyOfRange(0, offset)
- return decimalString.joinToString()
+ return String(newDecimalString)
}
private fun chunk1PayloadRepresentation(payload: OnboardingPayload): Int {
@@ -141,12 +142,11 @@
return ((payload.setupPinCode.toInt() shr pincodeShift) and pincodeMask) shl kManualSetupChunk3PINCodeMsbitsPos
}
- private fun decimalStringWithPadding(buffer: CharArray, number: Int): Unit {
- val len = buffer.size - 1
- val retval = String.format("%0${len}d", number).toCharArray(buffer, 0, buffer.size)
-
- if (retval.size >= buffer.size) {
+ private fun decimalStringWithPadding(buffer: CharArray, offset: Int, len: Int, number: Int): Unit {
+ if (offset + len > buffer.size) {
throw OnboardingPayloadException("The outBuffer has insufficient size")
}
+
+ String.format("%0${len}d", number).toCharArray(buffer, offset, 0, len)
}
}
diff --git a/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadParser.kt b/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadParser.kt
index 0f925a9..20e71e2 100644
--- a/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadParser.kt
+++ b/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadParser.kt
@@ -101,7 +101,19 @@
}
companion object {
- private fun checkDecimalStringValidity(decimalString: String): String {
+ fun toNumber(decimalString: String): UInt {
+ var number: UInt = 0u
+ for (c in decimalString) {
+ if (!c.isDigit()) {
+ throw InvalidManualPairingCodeFormatException("Failed decoding base10. Character was invalid $c")
+ }
+ number *= 10u
+ number += (c - '0').toUInt()
+ }
+ return number
+ }
+
+ fun checkDecimalStringValidity(decimalString: String): String {
if (decimalString.length < 2) {
throw InvalidManualPairingCodeFormatException("Failed decoding base10. Input was empty. ${decimalString.length}")
}
@@ -116,25 +128,13 @@
return repWithoutCheckChar
}
- private fun checkCodeLengthValidity(decimalString: String, isLongCode: Boolean): Unit {
+ fun checkCodeLengthValidity(decimalString: String, isLongCode: Boolean): Unit {
val expectedCharLength = if (isLongCode) kManualSetupLongCodeCharLength else kManualSetupShortCodeCharLength
if (decimalString.length != expectedCharLength) {
throw InvalidManualPairingCodeFormatException("Failed decoding base10. Input length ${decimalString.length} was not expected length $expectedCharLength")
}
}
- private fun toNumber(decimalString: String): UInt {
- var number: UInt = 0u
- for (c in decimalString) {
- if (!c.isDigit()) {
- throw InvalidManualPairingCodeFormatException("Failed decoding base10. Character was invalid $c")
- }
- number *= 10u
- number += (c - '0').toUInt()
- }
- return number
- }
-
// Populate numberOfChars into dest from decimalString starting at startIndex (least significant digit = left-most digit)
fun readDigitsFromDecimalString(decimalString: String, index: AtomicInteger, numberOfCharsToRead: Int): UInt {
val startIndex = index.get()
diff --git a/src/controller/java/src/chip/onboardingpayload/OnboardingPayload.kt b/src/controller/java/src/chip/onboardingpayload/OnboardingPayload.kt
index af46eb5..9005e5b 100644
--- a/src/controller/java/src/chip/onboardingpayload/OnboardingPayload.kt
+++ b/src/controller/java/src/chip/onboardingpayload/OnboardingPayload.kt
@@ -61,8 +61,8 @@
const val kNumberOFDevicesTag = 0x03
const val kCommissioningTimeoutTag = 0x04
-const val kSetupPINCodeMaximumValue = 99999998
-const val kSetupPINCodeUndefinedValue = 0
+const val kSetupPINCodeMaximumValue = 99999998L
+const val kSetupPINCodeUndefinedValue = 0L
const val kTotalPayloadDataSizeInBits: Int =
kVersionFieldLengthInBits +
@@ -195,6 +195,24 @@
return checkPayloadCommonConstraints()
}
+ fun setShortDiscriminatorValue(discriminator: Int) {
+ if (discriminator != (discriminator and kDiscriminatorShortMask)) {
+ throw OnboardingPayloadException("Invalid argument")
+ }
+
+ this.discriminator = (discriminator and kDiscriminatorShortMask)
+ this.hasShortDiscriminator = true
+ }
+
+ fun setLongDiscriminatorValue(discriminator: Int) {
+ if (discriminator != (discriminator and kDiscriminatorLongMask)) {
+ throw OnboardingPayloadException("Invalid argument")
+ }
+
+ this.discriminator = (discriminator and kDiscriminatorLongMask)
+ this.hasShortDiscriminator = false;
+ }
+
fun getShortDiscriminatorValue(): Int {
if (hasShortDiscriminator) {
return discriminator
@@ -474,7 +492,7 @@
return false
}
- if (!isValidSetupPIN(setupPinCode.toInt())) {
+ if (!isValidSetupPIN(setupPinCode)) {
return false
}
@@ -494,12 +512,14 @@
}
companion object {
- private fun isValidSetupPIN(setupPIN: Int): Boolean {
- return (setupPIN != kSetupPINCodeUndefinedValue && setupPIN <= kSetupPINCodeMaximumValue &&
- setupPIN != 11111111 && setupPIN != 22222222 && setupPIN != 33333333 &&
- setupPIN != 44444444 && setupPIN != 55555555 && setupPIN != 66666666 &&
- setupPIN != 77777777 && setupPIN != 88888888 && setupPIN != 12345678 &&
- setupPIN != 87654321)
+ private fun isValidSetupPIN(setupPIN: Long): Boolean {
+ // SHALL be restricted to the values 0x0000001 to 0x5F5E0FE (00000001 to 99999998 in decimal),
+ // excluding the invalid Passcode values.
+ return (setupPIN != kSetupPINCodeUndefinedValue && setupPIN <= kSetupPINCodeMaximumValue &&
+ setupPIN != 11111111L && setupPIN != 22222222L && setupPIN != 33333333L &&
+ setupPIN != 44444444L && setupPIN != 55555555L && setupPIN != 66666666L &&
+ setupPIN != 77777777L && setupPIN != 88888888L && setupPIN != 12345678L &&
+ setupPIN != 87654321L)
}
private fun longToShortValue(longValue: Int): Int {
diff --git a/src/controller/java/tests/chip/onboardingpayload/ManualCodeTest.kt b/src/controller/java/tests/chip/onboardingpayload/ManualCodeTest.kt
new file mode 100644
index 0000000..b7aa1bb
--- /dev/null
+++ b/src/controller/java/tests/chip/onboardingpayload/ManualCodeTest.kt
@@ -0,0 +1,687 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ * Copyright (c) 2019-2023 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package chip.onboardingpayload
+
+import com.google.common.truth.Truth.assertThat
+import java.math.BigInteger
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.math.ceil
+import kotlin.math.log10
+import kotlin.math.pow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertEquals
+
+@RunWith(JUnit4::class)
+class ManualCodeTest {
+ private fun checkGenerator(payload: OnboardingPayload, expectedResult: String, skipPayloadValidation: Boolean = false): Boolean {
+ val generator = ManualOnboardingPayloadGenerator(payload)
+ generator.setSkipPayloadValidation(skipPayloadValidation)
+ val result = generator.payloadDecimalStringRepresentation()
+ var expectedResultWithCheckChar = expectedResult
+
+ if (expectedResult.isNotEmpty()) {
+ val expectedCheckChar = Verhoeff10.computeCheckChar(expectedResult)
+ expectedResultWithCheckChar += expectedCheckChar
+ }
+
+ val same = result == expectedResultWithCheckChar
+ if (!same) {
+ println("Actual result: $result")
+ println("Expected result: $expectedResultWithCheckChar")
+ }
+
+ return same
+ }
+
+ private fun getDefaultPayload(): OnboardingPayload {
+ val payload = OnboardingPayload()
+ payload.setupPinCode = 12345679
+ payload.discriminator = 2560
+ return payload
+ }
+
+ private fun assertPayloadValues(payload: OnboardingPayload, pinCode: Long, discriminator: Int, vendorId: Int, productId: Int) {
+ assertEquals(payload.setupPinCode, pinCode)
+ assertEquals(payload.discriminator, discriminator)
+ assertEquals(payload.vendorId, vendorId)
+ assertEquals(payload.productId, productId)
+ }
+
+ private fun assertEmptyPayloadWithError(payload: OnboardingPayload) {
+ assertEquals(payload.setupPinCode, 0)
+ assertEquals(payload.discriminator, 0)
+ assertEquals(payload.vendorId, 0)
+ assertEquals(payload.productId, 0)
+ }
+
+ private fun computeCheckChar(str: String): Char {
+ // Strip out dashes, if any, from the string before computing the checksum.
+ val newStr = str.replace("-", "")
+ return Verhoeff10.computeCheckChar(newStr)
+ }
+
+ /*
+ * Generate Decimal Representation from Partial Payload
+ */
+ @Test
+ fun testDecimalRepresentation_partialPayload() {
+ val payload = getDefaultPayload()
+ val expectedResult = "2412950753"
+ val result = checkGenerator(payload, expectedResult)
+ assertEquals(true, result)
+ }
+
+ /*
+ * Generate Decimal Representation from Partial Payload (Custom Flow)
+ */
+ @Test
+ fun testDecimalRepresentation_partialPayload_requiresCustomFlow() {
+ val payload = getDefaultPayload()
+ payload.commissioningFlow = CommissioningFlow.CUSTOM.value
+ val expectedResult = "64129507530000000000"
+ val result = checkGenerator(payload, expectedResult)
+ assertEquals(true, result)
+ }
+
+ /*
+ * Generate Decimal Representation from Full Payload with Zeros
+ */
+ @Test
+ fun testDecimalRepresentation_fullPayloadWithZeros() {
+ val payload = getDefaultPayload()
+ payload.commissioningFlow = CommissioningFlow.CUSTOM.value
+ payload.vendorId = 1
+ payload.productId = 1
+
+ val expectedResult = "64129507530000100001"
+ val result = checkGenerator(payload, expectedResult)
+ assertEquals(true, result)
+ }
+
+ /*
+ * Decimal Representation from Full Payload without Zeros
+ */
+ @Test
+ fun testDecimalRepresentation_fullPayloadWithoutZeros_doesNotRequireCustomFlow() {
+ val payload = getDefaultPayload()
+ payload.vendorId = 45367
+ payload.productId = 14526
+
+ val expectedResult = "2412950753"
+ val result = checkGenerator(payload, expectedResult)
+ assertEquals(true, result)
+ }
+
+ /*
+ * Decimal Representation from Full Payload without Zeros (Custom Flow)
+ */
+ @Test
+ fun testDecimalRepresentation_fullPayloadWithoutZeros() {
+ val payload = getDefaultPayload()
+ payload.commissioningFlow = CommissioningFlow.CUSTOM.value
+ payload.vendorId = 45367
+ payload.productId = 14526
+
+ val expectedResult = "64129507534536714526"
+ val result = checkGenerator(payload, expectedResult)
+ assertEquals(true, result)
+ }
+
+ /*
+ * Test 12 bit discriminator for manual setup code
+ */
+ @Test
+ fun testGenerateAndParser_manualSetupCodeWithLongDiscriminator() {
+ val payload = getDefaultPayload()
+ payload.setLongDiscriminatorValue(0xa1f)
+
+ // Test short 11 digit code
+ var generator = ManualOnboardingPayloadGenerator(payload)
+ var result = generator.payloadDecimalStringRepresentation()
+ var outPayload = OnboardingPayload()
+ ManualOnboardingPayloadParser(result).populatePayload(outPayload)
+ assertPayloadValues(
+ outPayload,
+ payload.setupPinCode,
+ discriminator = 0xa,
+ payload.vendorId,
+ payload.productId
+ )
+
+ payload.vendorId = 1
+ payload.productId = 1
+ payload.commissioningFlow = CommissioningFlow.CUSTOM.value
+ payload.setLongDiscriminatorValue(0xb1f)
+
+ // Test long 21 digit code
+ generator = ManualOnboardingPayloadGenerator(payload)
+ result = generator.payloadDecimalStringRepresentation()
+ outPayload = OnboardingPayload()
+ ManualOnboardingPayloadParser(result).populatePayload(outPayload)
+ assertPayloadValues(
+ outPayload,
+ payload.setupPinCode,
+ discriminator = 0xb,
+ payload.vendorId,
+ payload.productId
+ )
+ }
+
+ /*
+ * Test Decimal Representation - All Ones
+ */
+ @Test
+ fun testDecimalRepresentation_allOnes() {
+ val payload = getDefaultPayload()
+ payload.setupPinCode = 0x7FFFFFF
+ payload.setLongDiscriminatorValue(0xFFF)
+ payload.commissioningFlow = CommissioningFlow.CUSTOM.value
+ payload.vendorId = 65535
+ payload.productId = 65535
+
+ val expectedResult = "76553581916553565535"
+ val result = checkGenerator(payload, expectedResult, true)
+ assertEquals(true, result)
+ }
+
+ /*
+ * Parse from Partial Payload
+ */
+ @Test
+ fun testPayloadParser_partialPayload() {
+ val payload = getDefaultPayload()
+ var decimalString = "2361087535"
+
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ assertEquals(11, decimalString.length)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ assertPayloadValues(
+ payload,
+ pinCode = 123456780,
+ discriminator = 0xa,
+ vendorId = 0,
+ productId = 0
+ )
+
+ // The same thing, but with dashes separating digit groups.
+ decimalString = "236-108753-5"
+ decimalString += computeCheckChar(decimalString)
+ assertEquals(13, decimalString.length)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ assertPayloadValues(
+ payload,
+ pinCode = 123456780,
+ discriminator = 0xa,
+ vendorId = 0,
+ productId = 0
+ )
+
+ decimalString = "0000010000"
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ assertEquals(11, decimalString.length)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ assertPayloadValues(
+ payload,
+ pinCode = 1,
+ discriminator = 0,
+ vendorId = 0,
+ productId = 0
+ )
+
+ decimalString = "63610875350000000000"
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ assertEquals(21, decimalString.length)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ assertPayloadValues(
+ payload,
+ pinCode = 123456780,
+ discriminator = 0xa,
+ vendorId = 0,
+ productId = 0
+ )
+
+ // no discriminator (= 0)
+ decimalString = "0033407535"
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ assertEquals(11, decimalString.length)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+
+ // no vid (= 0)
+ decimalString = "63610875350000014526"
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ assertEquals(21, decimalString.length)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+
+ // no pid (= 0)
+ decimalString = "63610875354536700000"
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ assertEquals(21, decimalString.length)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ }
+
+ /*
+ * Parse from Full Payload
+ */
+ @Test
+ fun testPayloadParser_fullPayload() {
+ val payload = getDefaultPayload()
+ var decimalString = "63610875354536714526"
+
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ assertPayloadValues(
+ payload,
+ pinCode = 123456780,
+ discriminator = 0xa,
+ vendorId = 45367,
+ productId = 14526
+ )
+
+ // The same thing, but with dashes separating digit groups.
+ decimalString = "6361-0875-3545-3671-4526"
+ decimalString += computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ assertPayloadValues(
+ payload,
+ pinCode = 123456780,
+ discriminator = 0xa,
+ vendorId = 45367,
+ productId = 14526
+ )
+
+ decimalString = "52927623630456200032"
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ assertPayloadValues(
+ payload,
+ pinCode = 38728284,
+ discriminator = 0x5,
+ vendorId = 4562,
+ productId = 32
+ )
+
+ decimalString = "40000100000000100001"
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ assertPayloadValues(
+ payload,
+ pinCode = 1,
+ discriminator = 0,
+ vendorId = 1,
+ productId = 1
+ )
+ }
+
+ /*
+ * Test Invalid Entry To QR Code Parser
+ */
+ @Test
+ fun testPayloadParser_invalidEntry() {
+ val payload = OnboardingPayload()
+
+ // Empty input
+ var decimalString = ""
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ try {
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ assertEmptyPayloadWithError(payload)
+
+ // Invalid character
+ decimalString = "24184.2196"
+ try {
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ assertEmptyPayloadWithError(payload)
+
+ // too short
+ decimalString = "2456"
+ try {
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ assertEmptyPayloadWithError(payload)
+
+ // too long for long code
+ decimalString = "123456789123456785671"
+ try {
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ assertEmptyPayloadWithError(payload)
+
+ // too long for short code
+ decimalString = "12749875380"
+ try {
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ assertEmptyPayloadWithError(payload)
+
+ // bit to indicate short code but long code length
+ decimalString = "23456789123456785610"
+ try {
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ assertEmptyPayloadWithError(payload)
+
+ // no pin code (= 0)
+ decimalString = "2327680000"
+ try {
+ decimalString += Verhoeff10.computeCheckChar(decimalString)
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ assertEmptyPayloadWithError(payload)
+
+ // wrong check digit
+ decimalString = "02684354589"
+ try {
+ ManualOnboardingPayloadParser(decimalString).populatePayload(payload)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ assertEmptyPayloadWithError(payload)
+ }
+
+ /*
+ * Test Short Read Write
+ */
+ @Test
+ fun testShortCodeReadWrite() {
+ val inPayload = getDefaultPayload()
+ val outPayload = OnboardingPayload()
+
+ var generator = ManualOnboardingPayloadGenerator(inPayload)
+ var result = generator.payloadDecimalStringRepresentation()
+ ManualOnboardingPayloadParser(result).populatePayload(outPayload)
+
+ // Override the discriminator in the input payload with the short version,
+ // since that's what we will produce.
+ inPayload.setShortDiscriminatorValue(inPayload.getShortDiscriminatorValue())
+ assertThat(inPayload == outPayload)
+ }
+
+ /*
+ * Test Long Read Write
+ */
+ @Test
+ fun testLongCodeReadWrite() {
+ val inPayload = getDefaultPayload()
+ inPayload.commissioningFlow = CommissioningFlow.CUSTOM.value
+ inPayload.vendorId = 1
+ inPayload.productId = 1
+
+ val outPayload = OnboardingPayload()
+ var generator = ManualOnboardingPayloadGenerator(inPayload)
+ var result = generator.payloadDecimalStringRepresentation()
+ ManualOnboardingPayloadParser(result).populatePayload(outPayload)
+
+ // Override the discriminator in the input payload with the short version,
+ // since that's what we will produce.
+ inPayload.setShortDiscriminatorValue(inPayload.getShortDiscriminatorValue())
+ assertThat(inPayload == outPayload)
+ }
+
+ /*
+ * Check Decimal String Validity
+ */
+ @Test
+ fun testCheckDecimalStringValidity() {
+ var outReprensation: String
+ var checkDigit: Char
+ var decimalString: String
+ var representationWithoutCheckDigit: String = ""
+
+ try {
+ ManualOnboardingPayloadParser.checkDecimalStringValidity(representationWithoutCheckDigit)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+
+ representationWithoutCheckDigit = "1"
+ try {
+ ManualOnboardingPayloadParser.checkDecimalStringValidity(representationWithoutCheckDigit)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+
+ representationWithoutCheckDigit = "10109"
+ checkDigit = Verhoeff10.computeCheckChar(representationWithoutCheckDigit)
+ decimalString = representationWithoutCheckDigit + checkDigit
+
+ outReprensation = ManualOnboardingPayloadParser.checkDecimalStringValidity(decimalString)
+ assertThat(outReprensation == representationWithoutCheckDigit)
+
+ representationWithoutCheckDigit = "0000"
+ checkDigit = Verhoeff10.computeCheckChar(representationWithoutCheckDigit)
+ decimalString = representationWithoutCheckDigit + checkDigit
+ outReprensation = ManualOnboardingPayloadParser.checkDecimalStringValidity(decimalString)
+ assertThat(outReprensation == representationWithoutCheckDigit)
+ }
+
+ /*
+ * Check QR Code Length Validity
+ */
+ @Test
+ fun testCheckCodeLengthValidity() {
+ ManualOnboardingPayloadParser.checkCodeLengthValidity("01234567890123456789", true)
+ ManualOnboardingPayloadParser.checkCodeLengthValidity("0123456789", false)
+
+ try {
+ ManualOnboardingPayloadParser.checkCodeLengthValidity("01234567891", false)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+
+ try {
+ ManualOnboardingPayloadParser.checkCodeLengthValidity("012345678", false)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+
+ try {
+ ManualOnboardingPayloadParser.checkCodeLengthValidity("012345678901234567891", true)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+
+ try {
+ ManualOnboardingPayloadParser.checkCodeLengthValidity("0123456789012345678", true)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ }
+
+ /*
+ * Test Decimal String to Number
+ */
+ @Test
+ fun testDecimalStringToNumber() {
+ var number = ManualOnboardingPayloadParser.toNumber("12345")
+ assertEquals(12345u, number)
+
+ number = ManualOnboardingPayloadParser.toNumber("01234567890")
+ assertEquals(1234567890u, number)
+
+ number = ManualOnboardingPayloadParser.toNumber("00000001")
+ assertEquals(1u, number)
+
+ number = ManualOnboardingPayloadParser.toNumber("0")
+ assertEquals(0u, number)
+
+ try {
+ ManualOnboardingPayloadParser.toNumber("012345.123456789")
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+
+ try {
+ ManualOnboardingPayloadParser.toNumber("/")
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ }
+
+ /*
+ * Test Short Code Character Lengths
+ */
+ @Test
+ fun testShortCodeCharLengths() {
+ val numBits = 1 + kSetupPINCodeFieldLengthInBits + kManualSetupDiscriminatorFieldLengthInBits
+ val manualSetupShortCodeCharLength = ceil(log10(2.0.pow(numBits.toDouble()))).toInt()
+ assertEquals(manualSetupShortCodeCharLength, kManualSetupShortCodeCharLength)
+
+ val manualSetupVendorIdCharLength = ceil(log10(2.0.pow(kVendorIDFieldLengthInBits.toDouble()))).toInt()
+ assertEquals(manualSetupVendorIdCharLength, kManualSetupVendorIdCharLength)
+
+ val manualSetupProductIdCharLength = ceil(log10(2.0.pow(kProductIDFieldLengthInBits.toDouble()))).toInt()
+ assertEquals(manualSetupProductIdCharLength, kManualSetupProductIdCharLength)
+
+ val manualSetupLongCodeCharLength =
+ kManualSetupShortCodeCharLength + kManualSetupVendorIdCharLength + kManualSetupProductIdCharLength
+ assertEquals(manualSetupLongCodeCharLength, kManualSetupLongCodeCharLength)
+ }
+
+ /*
+ * Test Read Characters from Decimal String
+ */
+ @Test
+ fun testReadCharsFromDecimalString() {
+ val index = AtomicInteger(3)
+ var number = ManualOnboardingPayloadParser.readDigitsFromDecimalString("12345", index, 2)
+ assertEquals(45u, number)
+
+ index.set(2)
+ number = ManualOnboardingPayloadParser.readDigitsFromDecimalString("6256276377282", index, 7)
+ assertEquals(5627637u, number)
+
+ index.set(0)
+ number = ManualOnboardingPayloadParser.readDigitsFromDecimalString("10", index, 2)
+ assertEquals(10u, number)
+
+ index.set(0)
+ number = ManualOnboardingPayloadParser.readDigitsFromDecimalString("10", index, 2)
+ assertEquals(10u, number)
+
+ index.set(1)
+ number = ManualOnboardingPayloadParser.readDigitsFromDecimalString("11", index, 1)
+ assertEquals(1u, number)
+
+ index.set(2)
+ number = ManualOnboardingPayloadParser.readDigitsFromDecimalString("100001", index, 3)
+ assertEquals(0u, number)
+
+ try {
+ index.set(1)
+ ManualOnboardingPayloadParser.readDigitsFromDecimalString("12345", index, 5)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+
+ try {
+ ManualOnboardingPayloadParser.readDigitsFromDecimalString("12", index, 5)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+
+ try {
+ index.set(200)
+ ManualOnboardingPayloadParser.readDigitsFromDecimalString("6256276377282", index, 1)
+ assertThat(false)
+ } catch (e: Exception) {
+ println("Expected exception occurred: ${e.message}")
+ }
+ }
+
+ /*
+ * Generate Full Payload and Parse it
+ */
+ @Test
+ fun testGenerateAndParser_fullPayload() {
+ val payload = getDefaultPayload()
+ payload.commissioningFlow = CommissioningFlow.CUSTOM.value
+ payload.vendorId = 1
+ payload.productId = 1
+
+ val generator = ManualOnboardingPayloadGenerator(payload)
+ val result = generator.payloadDecimalStringRepresentation()
+
+ val outPayload = OnboardingPayload()
+ ManualOnboardingPayloadParser(result).populatePayload(outPayload)
+
+ assertPayloadValues(
+ outPayload,
+ pinCode = payload.setupPinCode,
+ discriminator = 0xa,
+ vendorId = payload.vendorId,
+ productId = payload.productId
+ )
+ }
+
+ /*
+ * Generate Partial Payload and Parse it
+ */
+ @Test
+ fun testGenerateAndParser_partialPayload() {
+ val payload = getDefaultPayload()
+ val generator = ManualOnboardingPayloadGenerator(payload)
+ val result = generator.payloadDecimalStringRepresentation()
+
+ val outPayload = OnboardingPayload()
+ ManualOnboardingPayloadParser(result).populatePayload(outPayload)
+
+ assertPayloadValues(
+ outPayload,
+ pinCode = payload.setupPinCode,
+ discriminator = 0xa,
+ vendorId = payload.vendorId,
+ productId = payload.productId
+ )
+ }
+}
+