From 1308279e90809a518f8bdc7b2609b2c4577c8092 Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Sat, 24 Jun 2023 10:12:08 -0700 Subject: [PATCH] Implement unit tests for ManualOnboardingPayload (#27446) * Implement unit tests for ManualOnboardingPayload * Restyled by gn --------- Co-authored-by: Restyled.io --- .github/workflows/java-tests.yaml | 3 +- src/controller/java/BUILD.gn | 15 + .../ManualOnboardingPayloadGenerator.kt | 28 +- .../ManualOnboardingPayloadParser.kt | 28 +- .../onboardingpayload/OnboardingPayload.kt | 38 +- .../chip/onboardingpayload/ManualCodeTest.kt | 687 ++++++++++++++++++ 6 files changed, 761 insertions(+), 38 deletions(-) create mode 100644 src/controller/java/tests/chip/onboardingpayload/ManualCodeTest.kt diff --git a/.github/workflows/java-tests.yaml b/.github/workflows/java-tests.yaml index a0fb06a9e839b0..f4fecddf9aa67e 100644 --- a/.github/workflows/java-tests.yaml +++ b/.github/workflows/java-tests.yaml @@ -106,7 +106,8 @@ jobs: 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 ba6b7e958c9b3f..02655bfbac1428 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -261,9 +261,24 @@ kotlin_library("onboarding_payload") { ] } +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 c71b1f8605657a..5814ca8a442aa7 100644 --- a/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadGenerator.kt +++ b/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadGenerator.kt @@ -71,30 +71,31 @@ class ManualOnboardingPayloadGenerator(private val payloadContents: OnboardingPa 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 @@ class ManualOnboardingPayloadGenerator(private val payloadContents: OnboardingPa 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 0f925a973bfde4..20e71e2106a25a 100644 --- a/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadParser.kt +++ b/src/controller/java/src/chip/onboardingpayload/ManualOnboardingPayloadParser.kt @@ -101,7 +101,19 @@ class ManualOnboardingPayloadParser(decimalRepresentation: String) { } 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 @@ class ManualOnboardingPayloadParser(decimalRepresentation: String) { 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 af46eb5c26dcf7..9005e5b85f9b22 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 kBPKFSaltTag = 0x02 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 @@ class OnboardingPayload( 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 @@ class OnboardingPayload( return false } - if (!isValidSetupPIN(setupPinCode.toInt())) { + if (!isValidSetupPIN(setupPinCode)) { return false } @@ -494,12 +512,14 @@ class OnboardingPayload( } 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 00000000000000..b7aa1bb0858bd1 --- /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 + ) + } +} +