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
+    )                         
+  }              
+}
+