Initial implementation for kotlin-matter-controller (#29636)

* Initial implementaton for kotlin-matter-controller

* Address review comments

* Use lock.wait(remainingTime) to allow the thread to wait for the remaining time or until it's notified
diff --git a/docs/examples/index.md b/docs/examples/index.md
index 83a1a6f..e5144f8 100644
--- a/docs/examples/index.md
+++ b/docs/examples/index.md
@@ -87,6 +87,15 @@
 java-matter-controller/README
 ```
 
+## Kotlin matter controller example
+
+```{toctree}
+:glob:
+:maxdepth: 1
+
+kotlin-matter-controller/README
+```
+
 ## Virtual Device App example
 
 ```{toctree}
diff --git a/examples/kotlin-matter-controller/.gitignore b/examples/kotlin-matter-controller/.gitignore
new file mode 100644
index 0000000..d59e52c
--- /dev/null
+++ b/examples/kotlin-matter-controller/.gitignore
@@ -0,0 +1,20 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+# Shared libs & JAR libs (those libs are copied into source tree for easy Android build).
+*.so
+*.jar
+*.map
diff --git a/examples/kotlin-matter-controller/.gn b/examples/kotlin-matter-controller/.gn
new file mode 100644
index 0000000..5cd171a
--- /dev/null
+++ b/examples/kotlin-matter-controller/.gn
@@ -0,0 +1,25 @@
+# Copyright (c) 2020-2022 Project CHIP Authors
+#
+# 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.
+
+import("//build_overrides/build.gni")
+
+# The location of the build configuration file.
+buildconfig = "${build_root}/config/BUILDCONFIG.gn"
+
+# CHIP uses angle bracket includes.
+check_system_includes = true
+
+default_args = {
+  import("//args.gni")
+}
diff --git a/examples/kotlin-matter-controller/BUILD.gn b/examples/kotlin-matter-controller/BUILD.gn
new file mode 100644
index 0000000..b13208e
--- /dev/null
+++ b/examples/kotlin-matter-controller/BUILD.gn
@@ -0,0 +1,56 @@
+# Copyright (c) 2022-2023 Project CHIP Authors
+#
+# 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.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+
+import("${chip_root}/build/chip/java/rules.gni")
+import("${chip_root}/build/chip/tools.gni")
+
+kotlin_binary("kotlin-matter-controller") {
+  output_name = "kotlin-matter-controller"
+  deps = [
+    "${chip_root}/src/controller/java:kotlin_matter_controller",
+    "${chip_root}/src/controller/java:onboarding_payload",
+    "${chip_root}/third_party/java_deps:annotation",
+  ]
+
+  sources = [
+    "java/src/com/matter/controller/Main.kt",
+    "java/src/com/matter/controller/commands/common/Argument.kt",
+    "java/src/com/matter/controller/commands/common/ArgumentType.kt",
+    "java/src/com/matter/controller/commands/common/Command.kt",
+    "java/src/com/matter/controller/commands/common/CommandManager.kt",
+    "java/src/com/matter/controller/commands/common/CredentialsIssuer.kt",
+    "java/src/com/matter/controller/commands/common/FutureResult.kt",
+    "java/src/com/matter/controller/commands/common/IPAddress.kt",
+    "java/src/com/matter/controller/commands/common/MatterCommand.kt",
+    "java/src/com/matter/controller/commands/common/RealResult.kt",
+    "java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt",
+    "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.kt",
+    "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt",
+    "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt",
+    "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImSubscribeCommand.kt",
+    "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt",
+    "java/src/com/matter/controller/commands/pairing/PairingCommand.kt",
+    "java/src/com/matter/controller/commands/pairing/PairingModeType.kt",
+    "java/src/com/matter/controller/commands/pairing/PairingNetworkType.kt",
+  ]
+
+  kotlinc_flags = [ "-Xlint:deprecation" ]
+}
+
+group("default") {
+  deps = [ ":kotlin-matter-controller" ]
+}
diff --git a/examples/kotlin-matter-controller/Manifest.txt b/examples/kotlin-matter-controller/Manifest.txt
new file mode 100644
index 0000000..2ef5017
--- /dev/null
+++ b/examples/kotlin-matter-controller/Manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: com.matter.controller.MainKt
+Class-Path: ../lib/third_party/connectedhomeip/src/controller/java/*.jar ../lib/third_party/connectedhomeip/third_party/java_deps/stub_src/Android.jar ../lib/third_party/connectedhomeip/third_party/java_deps/json-20220924.jar ../lib/third_party/connectedhomeip/third_party/java_deps/jsr305-3.0.2.jar ../lib/third_party/connectedhomeip/third_party/java_deps/kotlin-stdlib-1.8.20.jar ../lib/third_party/connectedhomeip/third_party/java_deps/kotlinx-coroutines-core-jvm-1.7.3.jar
diff --git a/examples/kotlin-matter-controller/README.md b/examples/kotlin-matter-controller/README.md
new file mode 100644
index 0000000..c4e84cb
--- /dev/null
+++ b/examples/kotlin-matter-controller/README.md
@@ -0,0 +1,118 @@
+# Matter Controller Kotlin App Example
+
+This is a Matter Controller Kotlin app that can be used to commission and
+control Matter accessory devices.
+
+<hr>
+
+-   [Matter Controller Kotlin App Example](#matter-controller-kotlin-app-example)
+    -   [Requirements for building](#requirements-for-building)
+    -   [Preparing for build](#preparing-for-build)
+    -   [Building & Running the app](#building--running-the-app)
+
+<hr>
+
+## Requirements for building
+
+You need to have the following two software installed on your Ubuntu system:
+
+1. Java Runtime Environment (JRE)
+2. Java Development Kit (JDK)
+
+openjdk version "11.0.20.1" or above is needed
+
+First, check if we have the Java Runtime Environment and Java Development Kit
+installed on our system or not.
+
+```shell
+java -version
+```
+
+If not, you can install the Java Runtime Environment and Java Development Kit on
+your system through the following command as root:
+
+```
+sudo apt install default-jre
+sudo apt install default-jdk
+```
+
+You also need to install kotlin compiler on your Ubuntu system:
+
+kotlin compiler version 1.8.10 or above is needed to compile
+kotlin-matter-controller, if you already have lower version kotlin compiler
+installed on your Ubuntu from apt,  
+you need to remove the Kotlin compiler package, run the following command:
+
+```shell
+sudo apt-get remove kotlin
+```
+
+Wait for the removal process to complete. Once it's done, the Kotlin compiler
+will be removed from your system.
+
+(Optional) If you want to remove any configuration files associated with Kotlin,
+run the following command:
+
+```shell
+sudo apt-get purge kotlin
+```
+
+Install kotlin compiler 1.8.10 or above, such as
+[kotlin-compiler-1.8.10-url](https://github.com/JetBrains/kotlin/releases/download/v1.8.10/kotlin-compiler-1.8.10.zip)
+
+```shell
+cd /usr/lib \
+&& sudo wget -q [kotlin-compiler-1.8.10-url] \
+&& sudo unzip kotlin-compiler-*.zip \
+&& sudo rm kotlin-compiler-*.zip \
+&& sudo rm -f kotlinc/bin/*.bat
+```
+
+Add a directory to PATH permanently by editing the `.bashrc` file located in the
+Home directory. Follow these steps:
+
+1. Open the `.bashrc` file using a text editor.
+2. Go to the end of the file.
+3. Paste the export syntax at the end of the file.
+
+```shell
+export PATH="/usr/lib/kotlinc/bin:$PATH"
+```
+
+### Linux
+
+```shell
+export JAVA_PATH=[JDK path]
+```
+
+<hr>
+
+## Preparing for build
+
+Complete the following steps to prepare the Matter build:
+
+1. Check out the Matter repository.
+
+2. Run bootstrap (**only required first time**)
+
+    ```shell
+    source scripts/bootstrap.sh
+    ```
+
+## Building & Running the app
+
+This is the simplest option. In the command line, run the following command from
+the top Matter directory:
+
+```shell
+./scripts/build/build_examples.py --target linux-x64-kotlin-matter-controller build
+```
+
+The Java executable file `kotlin-matter-controller` will be generated at
+`out/android-x86-kotlin-matter-controller/bin/`
+
+Run the kotlin-matter-controller
+
+```shell
+java -Djava.library.path=../lib/jni -jar kotlin-matter-controller
+```
diff --git a/examples/kotlin-matter-controller/args.gni b/examples/kotlin-matter-controller/args.gni
new file mode 100644
index 0000000..9b0c312
--- /dev/null
+++ b/examples/kotlin-matter-controller/args.gni
@@ -0,0 +1,26 @@
+# Copyright (c) 2020-2022 Project CHIP Authors
+#
+# 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.
+
+import("//build_overrides/chip.gni")
+
+import("${chip_root}/config/standalone/args.gni")
+
+chip_device_project_config_include = "<CHIPProjectAppConfig.h>"
+chip_project_config_include = "<CHIPProjectAppConfig.h>"
+chip_system_project_config_include = "<SystemProjectConfig.h>"
+
+chip_project_config_include_dirs =
+    [ "${chip_root}/examples/kotlin-matter-controller/include" ]
+chip_project_config_include_dirs += [ "${chip_root}/config/standalone" ]
+chip_stack_lock_tracking = "fatal"
diff --git a/examples/kotlin-matter-controller/build_overrides b/examples/kotlin-matter-controller/build_overrides
new file mode 120000
index 0000000..b430cf6
--- /dev/null
+++ b/examples/kotlin-matter-controller/build_overrides
@@ -0,0 +1 @@
+../build_overrides
\ No newline at end of file
diff --git a/examples/kotlin-matter-controller/include/CHIPProjectAppConfig.h b/examples/kotlin-matter-controller/include/CHIPProjectAppConfig.h
new file mode 100644
index 0000000..09183cc
--- /dev/null
+++ b/examples/kotlin-matter-controller/include/CHIPProjectAppConfig.h
@@ -0,0 +1,56 @@
+/*
+ *
+ *    Copyright (c) 2020-2022 Project CHIP Authors
+ *
+ *    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.
+ */
+
+#ifndef CHIPPROJECTCONFIG_H
+#define CHIPPROJECTCONFIG_H
+
+#define CHIP_CONFIG_MAX_FABRICS 17
+
+#define CHIP_CONFIG_EVENT_LOGGING_NUM_EXTERNAL_CALLBACKS 2
+
+// Enable support functions for parsing command-line arguments
+#define CHIP_CONFIG_ENABLE_ARG_PARSER 1
+
+// Use a default pairing code if one hasn't been provisioned in flash.
+#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021
+#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00
+
+// Enable reading DRBG seed data from /dev/(u)random.
+// This is needed for test applications and the CHIP device manager to function
+// properly when CHIP_CONFIG_RNG_IMPLEMENTATION_CHIPDRBG is enabled.
+#define CHIP_CONFIG_DEV_RANDOM_DRBG_SEED 1
+
+// For convenience, Chip Security Test Mode can be enabled and the
+// requirement for authentication in various protocols can be disabled.
+//
+//    WARNING: These options make it possible to circumvent basic Chip security functionality,
+//    including message encryption. Because of this they MUST NEVER BE ENABLED IN PRODUCTION BUILDS.
+//
+#define CHIP_CONFIG_SECURITY_TEST_MODE 0
+
+#define CHIP_CONFIG_ENABLE_UPDATE 1
+
+#define CHIP_SYSTEM_CONFIG_PACKETBUFFER_POOL_SIZE 0
+
+#define CHIP_CONFIG_DATA_MANAGEMENT_CLIENT_EXPERIMENTAL 1
+
+#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 1
+
+// Enable some test-only interaction model APIs.
+#define CONFIG_BUILD_FOR_HOST_UNIT_TEST 1
+
+#endif /* CHIPPROJECTCONFIG_H */
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/Main.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/Main.kt
new file mode 100644
index 0000000..997c1b9
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/Main.kt
@@ -0,0 +1,70 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller
+
+import com.matter.controller.commands.common.Command
+import com.matter.controller.commands.common.CommandManager
+import com.matter.controller.commands.common.CredentialsIssuer
+import com.matter.controller.commands.pairing.PairOnNetworkLongCommand
+import com.matter.controller.commands.pairing.PairOnNetworkLongImInvokeCommand
+import com.matter.controller.commands.pairing.PairOnNetworkLongImReadCommand
+import com.matter.controller.commands.pairing.PairOnNetworkLongImSubscribeCommand
+import com.matter.controller.commands.pairing.PairOnNetworkLongImWriteCommand
+import matter.controller.ControllerParams
+import matter.controller.MatterController
+import matter.controller.MatterControllerImpl
+
+private fun getPairingCommands(
+  controller: MatterController,
+  credentialsIssuer: CredentialsIssuer
+): List<Command> {
+  return listOf(
+    PairOnNetworkLongCommand(controller, credentialsIssuer),
+  )
+}
+
+private fun getImCommands(
+  controller: MatterController,
+  credentialsIssuer: CredentialsIssuer
+): List<Command> {
+  return listOf(
+    PairOnNetworkLongImReadCommand(controller, credentialsIssuer),
+    PairOnNetworkLongImSubscribeCommand(controller, credentialsIssuer),
+    PairOnNetworkLongImWriteCommand(controller, credentialsIssuer),
+    PairOnNetworkLongImInvokeCommand(controller, credentialsIssuer),
+  )
+}
+
+fun main(args: Array<String>) {
+  val controller: MatterController = MatterControllerImpl(ControllerParams(countryCode = "US"))
+  val credentialsIssuer = CredentialsIssuer()
+  val commandManager = CommandManager()
+
+  commandManager.register("pairing", getPairingCommands(controller, credentialsIssuer))
+  commandManager.register("im", getImCommands(controller, credentialsIssuer))
+
+  try {
+    commandManager.run(args)
+  } catch (e: Exception) {
+    println("Run command failed with exception: " + e.message)
+    controller.close()
+    System.exit(1)
+  }
+
+  controller.close()
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Argument.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Argument.kt
new file mode 100644
index 0000000..7f9f11d
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Argument.kt
@@ -0,0 +1,184 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+import java.net.InetAddress
+import java.net.UnknownHostException
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+
+// TODO Issue #29632:Refactor class Argument to have specializations for various types.
+class Argument {
+  val name: String
+  val type: ArgumentType
+  private val minValue: Long
+  private val maxValue: Long
+  val value: Any
+  val desc: String?
+  val isOptional: Boolean
+
+  constructor(name: String, value: IPAddress, optional: Boolean) {
+    this.name = name
+    type = ArgumentType.ADDRESS
+    minValue = 0
+    maxValue = 0
+    this.value = value
+    desc = null
+    isOptional = optional
+  }
+
+  constructor(name: String, value: StringBuffer, desc: String?, optional: Boolean) {
+    this.name = name
+    type = ArgumentType.STRING
+    minValue = 0
+    maxValue = 0
+    this.value = value
+    this.desc = desc
+    isOptional = optional
+  }
+
+  constructor(name: String, value: AtomicBoolean, desc: String?, optional: Boolean) {
+    this.name = name
+    type = ArgumentType.BOOL
+    minValue = 0
+    maxValue = 0
+    this.value = value
+    this.desc = desc
+    isOptional = optional
+  }
+
+  constructor(
+    name: String,
+    min: Short,
+    max: Short,
+    value: AtomicInteger,
+    desc: String?,
+    optional: Boolean
+  ) {
+    this.name = name
+    type = ArgumentType.NUMBER_INT16
+    minValue = min.toLong()
+    maxValue = max.toLong()
+    this.value = value
+    this.desc = desc
+    isOptional = optional
+  }
+
+  constructor(
+    name: String,
+    min: Int,
+    max: Int,
+    value: AtomicInteger,
+    desc: String?,
+    optional: Boolean
+  ) {
+    this.name = name
+    type = ArgumentType.NUMBER_INT32
+    minValue = min.toLong()
+    maxValue = max.toLong()
+    this.value = value
+    this.desc = desc
+    isOptional = optional
+  }
+
+  constructor(
+    name: String,
+    min: Short,
+    max: Short,
+    value: AtomicLong,
+    desc: String?,
+    optional: Boolean
+  ) {
+    this.name = name
+    type = ArgumentType.NUMBER_INT32
+    minValue = min.toLong()
+    maxValue = max.toLong()
+    this.value = value
+    this.desc = desc
+    isOptional = optional
+  }
+
+  constructor(
+    name: String,
+    min: Long,
+    max: Long,
+    value: AtomicLong,
+    desc: String?,
+    optional: Boolean
+  ) {
+    this.name = name
+    type = ArgumentType.NUMBER_INT64
+    minValue = min
+    maxValue = max
+    this.value = value
+    this.desc = desc
+    isOptional = optional
+  }
+
+  fun setValue(value: String) {
+    var isValidArgument = false
+    when (type) {
+      ArgumentType.ATTRIBUTE -> {
+        val str = this.value as String
+        isValidArgument = value == str
+      }
+      ArgumentType.NUMBER_INT16 -> {
+        val numShort = this.value as AtomicInteger
+        numShort.set(value.toInt())
+        isValidArgument = numShort.toInt() >= minValue && numShort.toInt() <= maxValue
+      }
+      ArgumentType.NUMBER_INT32 -> {
+        val num = this.value as AtomicInteger
+        num.set(value.toInt())
+        isValidArgument = num.toInt() >= minValue && num.toInt() <= maxValue
+      }
+      ArgumentType.NUMBER_INT64 -> {
+        val numLong = this.value as AtomicLong
+        numLong.set(value.toLong())
+        isValidArgument = numLong.toInt() >= minValue && numLong.toInt() <= maxValue
+      }
+      ArgumentType.STRING -> {
+        val stringBuffer = this.value as StringBuffer
+        stringBuffer.append(value)
+        val str = stringBuffer.toString()
+        isValidArgument = value == str
+      }
+      ArgumentType.BOOL -> {
+        val atomicBoolean = this.value as AtomicBoolean
+        try {
+          atomicBoolean.set(value.toBoolean())
+          isValidArgument = true
+        } catch (e: Exception) {
+          isValidArgument = false
+        }
+      }
+      ArgumentType.ADDRESS ->
+        isValidArgument =
+          try {
+            val ipAddress = this.value as IPAddress
+            ipAddress.address = InetAddress.getByName(value)
+            true
+          } catch (e: UnknownHostException) {
+            false
+          }
+      else -> {}
+    }
+    require(isValidArgument) { "Invalid argument " + name + ": " + value }
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/ArgumentType.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/ArgumentType.kt
new file mode 100644
index 0000000..acd31fb
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/ArgumentType.kt
@@ -0,0 +1,39 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+enum class ArgumentType {
+  NUMBER_INT8,
+  NUMBER_INT16,
+  NUMBER_INT32,
+  NUMBER_INT64,
+  FLOAT,
+  DOUBLE,
+  BOOL,
+  STRING,
+  CHARSTRING,
+  OCTETSTRING,
+  ATTRIBUTE,
+  ADDRESS,
+  COMPLEX,
+  CUSTOM,
+  VECTOR_BOOL,
+  VECTOR16,
+  VECTOR32,
+  VECTOR_CUSTOM
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Command.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Command.kt
new file mode 100644
index 0000000..1b05e02
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Command.kt
@@ -0,0 +1,259 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+import java.util.ArrayList
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+
+/**
+ * @brief Matter Controller command
+ * @details The base class of all the commands the Matter Controller supports, which are actions
+ *   that may be performed. Commands are verb-like, such as pair a Matter device or discover Matter
+ *   devices from the environment.
+ */
+abstract class Command(val name: String, val helpText: String? = null) {
+  private val arguments = ArrayList<Argument>()
+
+  fun getArgumentName(index: Int): String {
+    return arguments[index].name
+  }
+
+  fun getArgumentsCount(): Int {
+    return arguments.size
+  }
+
+  fun getArgumentIsOptional(index: Int): Boolean {
+    return arguments[index].isOptional
+  }
+
+  /**
+   * @return A pointer to an Optional where the Attribute argument will be stored
+   * @brief Get attribute argument if it exists, there is at most one Attribute argument per command
+   */
+  fun getAttribute(): String? {
+    return arguments.find { arg -> arg.type === ArgumentType.ATTRIBUTE }?.value as? String
+  }
+
+  /**
+   * @return A pointer to an Optional where the argument description will be stored
+   * @brief Get argument description if it exists
+   */
+  fun getArgumentDescription(index: Int): String? {
+    return arguments[index].desc
+  }
+
+  fun addArgumentToList(arg: Argument) {
+    if (arg.isOptional || arguments.isEmpty()) {
+      // Safe to just append to the end of a list.
+      arguments.add(arg)
+      return
+    }
+
+    // mandatory arg needs to be inserted before the optional arguments.
+    var index = 0
+    while (index < arguments.size && !arguments[index].isOptional) {
+      index++
+    }
+
+    // Insert before the first optional arg.
+    arguments.add(index, arg)
+  }
+
+  /**
+   * @param name The name that will be displayed in the command help
+   * @param out A pointer to a AtomicBoolean where the argv value will be stored
+   * @param desc The description of the argument that will be displayed in the command help
+   * @param optional Indicate if an optional argument
+   * @return The number of arguments currently added to the command
+   * @brief Add a bool command argument
+   */
+  fun addArgument(name: String, out: AtomicBoolean, desc: String?, optional: Boolean) {
+    val arg = Argument(name, out, desc, optional)
+    addArgumentToList(arg)
+  }
+
+  /**
+   * @param name The name that will be displayed in the command help
+   * @param min The minimum value of the argv value
+   * @param max The minimum value of the argv value
+   * @param out A pointer to a AtomicInteger where the argv value will be stored
+   * @param desc The description of the argument that will be displayed in the command help
+   * @param optional Indicate if an optional argument
+   * @return The number of arguments currently added to the command
+   * @brief Add a short command argument
+   */
+  fun addArgument(
+    name: String,
+    min: Short,
+    max: Short,
+    out: AtomicInteger,
+    desc: String?,
+    optional: Boolean
+  ) {
+    val arg = Argument(name, min, max, out, desc, optional)
+    addArgumentToList(arg)
+  }
+
+  /**
+   * @param name The name that will be displayed in the command help
+   * @param min The minimum value of the argv value
+   * @param max The minimum value of the argv value
+   * @param out A pointer to a AtomicInteger where the argv value will be stored
+   * @param desc The description of the argument that will be displayed in the command help
+   * @param optional Indicate if an optional argument
+   * @return The number of arguments currently added to the command
+   * @brief Add an int command argument
+   */
+  fun addArgument(
+    name: String,
+    min: Int,
+    max: Int,
+    out: AtomicInteger,
+    desc: String?,
+    optional: Boolean
+  ) {
+    val arg = Argument(name, min, max, out, desc, optional)
+    addArgumentToList(arg)
+  }
+
+  /**
+   * @param name The name that will be displayed in the command help
+   * @param min The minimum value of the argv value
+   * @param max The minimum value of the argv value
+   * @param out A pointer to a MutableInteger where the argv value will be stored
+   * @param desc The description of the argument that will be displayed in the command help
+   * @param optional Indicate if an optional argument
+   * @return The number of arguments currently added to the command
+   * @brief Add a long Integer command argument
+   */
+  fun addArgument(
+    name: String,
+    min: Short,
+    max: Short,
+    out: AtomicLong,
+    desc: String?,
+    optional: Boolean
+  ) {
+    val arg = Argument(name, min, max, out, desc, optional)
+    addArgumentToList(arg)
+  }
+
+  /**
+   * @param name The name that will be displayed in the command help
+   * @param min The minimum value of the argv value
+   * @param max The minimum value of the argv value
+   * @param out A pointer to a AtomicLong where the argv value will be stored
+   * @param desc The description of the argument that will be displayed in the command help
+   * @param optional Indicate if an optional argument
+   * @return The number of arguments currently added to the command
+   * @brief Add a long Integer command argument
+   */
+  fun addArgument(
+    name: String,
+    min: Long,
+    max: Long,
+    out: AtomicLong,
+    desc: String?,
+    optional: Boolean
+  ) {
+    val arg = Argument(name, min, max, out, desc, optional)
+    addArgumentToList(arg)
+  }
+
+  /**
+   * @param name The name that will be displayed in the command help
+   * @param out A pointer to a IPAddress where the argv value will be stored
+   * @param optional Indicate if an optional argument
+   * @return The number of arguments currently added to the command
+   * @brief Add an IP address command argument
+   */
+  fun addArgument(name: String, out: IPAddress, optional: Boolean) {
+    val arg = Argument(name, out, optional)
+    addArgumentToList(arg)
+  }
+
+  /**
+   * @param name The name that will be displayed in the command help
+   * @param out A pointer to a StringBuffer where the argv value will be stored
+   * @param desc The description of the argument that will be displayed in the command help
+   * @param optional Indicate if an optional argument
+   * @return The number of arguments currently added to the command
+   * @brief Add a String command argument
+   */
+  fun addArgument(name: String, out: StringBuffer, desc: String?, optional: Boolean) {
+    val arg = Argument(name, out, desc, optional)
+    addArgumentToList(arg)
+  }
+
+  /**
+   * @param args Supplied command-line arguments as an array of String objects.
+   * @brief Initialize command arguments
+   */
+  fun setArgumentValues(args: Array<String>) {
+    val argc = args.size
+    var mandatoryArgsCount = 0
+    var currentIndex = 0
+    for (arg in arguments) {
+      if (!arg.isOptional) {
+        mandatoryArgsCount++
+      }
+    }
+    require(argc >= mandatoryArgsCount) {
+      "setArgumentValues: Wrong arguments number: $argc instead of $mandatoryArgsCount"
+    }
+
+    // Initialize mandatory arguments
+    for (i in 0 until mandatoryArgsCount) {
+      setArgumentValue(currentIndex++, args[i])
+    }
+
+    // Initialize optional arguments
+    // Optional arguments expect a name and a value, so it is increased by 2 on every step.
+    var i = mandatoryArgsCount
+    while (i < argc) {
+
+      // optional arguments starts with OPTIONAL_ARGUMENT_PREFIX
+      require(
+        !(args[i].length <= OPTIONAL_ARGUMENT_PREFIX_LENGTH &&
+          !args[i].startsWith(OPTIONAL_ARGUMENT_PREFIX))
+      ) {
+        "setArgumentValues: Invalid optional argument: " + args[i]
+      }
+      if (args[i].substring(OPTIONAL_ARGUMENT_PREFIX_LENGTH) == arguments[currentIndex].name) {
+        require(i + 1 < argc) {
+          "setArgumentValues: Optional argument " + args[i] + " missing value"
+        }
+        setArgumentValue(currentIndex++, args[i + 1])
+      }
+      i += 2
+    }
+  }
+
+  private fun setArgumentValue(argIndex: Int, argValue: String) {
+    arguments[argIndex].setValue(argValue)
+  }
+
+  @Throws(Exception::class) abstract fun run()
+
+  companion object {
+    private const val OPTIONAL_ARGUMENT_PREFIX = "--"
+    private const val OPTIONAL_ARGUMENT_PREFIX_LENGTH = 2
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CommandManager.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CommandManager.kt
new file mode 100644
index 0000000..36389a7
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CommandManager.kt
@@ -0,0 +1,313 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+import java.util.Arrays
+import java.util.HashMap
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlin.collections.MutableMap
+
+class CommandManager {
+  private val clusters: MutableMap<String, List<Command>> = HashMap()
+
+  fun register(clusterName: String, commandsList: List<Command>) {
+    clusters[clusterName] = commandsList
+  }
+
+  @Throws(Exception::class)
+  fun run(args: Array<String>) {
+    val command: Command?
+    if (args.size < 1) {
+      logger.log(Level.INFO, "Missing cluster name")
+      showHelpInfo()
+      return
+    }
+    val commands = clusters[args[0]]
+    if (commands == null) {
+      logger.log(Level.INFO, "Unknown cluster: " + args[0])
+      showHelpInfo()
+      return
+    }
+    if (args.size < 2) {
+      logger.log(Level.INFO, "Missing command name")
+      showCluster(args[0], commands)
+      return
+    }
+    if (!isGlobalCommand(args[0])) {
+      command = getCommand(commands, args[1])
+      if (command == null) {
+        System.out.printf("Unknown command: %s", args[1])
+        showCluster(args[0], commands)
+        throw IllegalArgumentException()
+      }
+    } else if (isEventCommand(args[1])) {
+      if (args.size < 3) {
+        logger.log(Level.INFO, "Missing event name")
+        showClusterEvents(args[0], args[1], commands)
+        throw IllegalArgumentException()
+      }
+      command = getGlobalCommand(commands, args[1], args[2])
+      if (command == null) {
+        logger.log(Level.INFO, "Unknown event: " + args[2])
+        showClusterEvents(args[0], args[1], commands)
+        throw IllegalArgumentException()
+      }
+    } else {
+      if (args.size < 3) {
+        logger.log(Level.INFO, "Missing attribute name")
+        showClusterAttributes(args[0], args[1], commands)
+        throw IllegalArgumentException()
+      }
+      command = getGlobalCommand(commands, args[1], args[2])
+      if (command == null) {
+        logger.log(Level.INFO, "Unknown attribute: " + args[2])
+        showClusterAttributes(args[0], args[1], commands)
+        throw IllegalArgumentException()
+      }
+    }
+
+    // need skip over binary and command name and only get arguments
+    val temp = Arrays.copyOfRange(args, 2, args.size)
+    try {
+      command.setArgumentValues(temp)
+    } catch (e: IllegalArgumentException) {
+      logger.log(Level.INFO, "Arguments init failed with exception: " + e.message)
+      showCommand(args[0], command)
+      System.exit(1)
+    }
+    command.run()
+  }
+
+  private fun isAttributeCommand(commandName: String): Boolean {
+    return commandName == "read" || commandName == "write" || commandName == "subscribe"
+  }
+
+  private fun isEventCommand(commandName: String): Boolean {
+    return commandName == "read-event" || commandName == "subscribe-event"
+  }
+
+  private fun isGlobalCommand(commandName: String): Boolean {
+    return isAttributeCommand(commandName) || isEventCommand(commandName)
+  }
+
+  private fun getCommand(commands: List<Command>, commandName: String): Command? {
+    for (command in commands) {
+      if (commandName == command.name) {
+        return command
+      }
+    }
+    return null
+  }
+
+  private fun getGlobalCommand(
+    commands: List<Command>,
+    commandName: String,
+    attributeName: String
+  ): Command? {
+    for (command in commands) {
+      if (commandName == command.name && attributeName == command.getAttribute()) {
+        return command
+      }
+    }
+    return null
+  }
+
+  private fun showHelpInfo() {
+    logger.log(Level.INFO, "Usage:")
+    logger.log(Level.INFO, "  cluster_name command_name [param1 param2 ...]")
+    logger.log(Level.INFO, "\n")
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+    logger.log(
+      Level.INFO,
+      "  | Clusters:                                                                           |"
+    )
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+    for (key in clusters.keys) {
+      System.out.printf("  | * %-82s|\n", key)
+    }
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+  }
+
+  private fun showCluster(clusterName: String, commands: List<Command>) {
+    logger.log(Level.INFO, "Usage:")
+    logger.log(
+      Level.INFO,
+      "  kotlin-matter-controller $clusterName command_name [param1 param2 ...]"
+    )
+    logger.log(Level.INFO, "\n")
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+    logger.log(
+      Level.INFO,
+      "  | Commands:                                                                           |"
+    )
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+    var readCommand = false
+    var writeCommand = false
+    var subscribeCommand = false
+    var readEventCommand = false
+    var subscribeEventCommand = false
+    for (command in commands) {
+      var shouldPrint = true
+      val cmdName = command.name
+      if (isGlobalCommand(cmdName)) {
+        if (cmdName == "read" && !readCommand) {
+          readCommand = true
+        } else if (cmdName == "write" && !writeCommand) {
+          writeCommand = true
+        } else if (cmdName == "subscribe" && !subscribeCommand) {
+          subscribeCommand = true
+        } else if (cmdName == "read-event" && !readEventCommand) {
+          readEventCommand = true
+        } else if (cmdName == "subscribe-event" && !subscribeEventCommand) {
+          subscribeEventCommand = true
+        } else {
+          shouldPrint = false
+        }
+      }
+      if (shouldPrint) {
+        System.out.printf("  | * %-82s|\n", cmdName)
+      }
+    }
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+\n"
+    )
+  }
+
+  private fun showClusterAttributes(
+    clusterName: String,
+    commandName: String,
+    commands: List<Command>
+  ) {
+    logger.log(Level.INFO, "Usage:")
+    System.out.printf(
+      "  kotlin-matter-controller %s %s attribute-name [param1 param2 ...]\n",
+      clusterName,
+      commandName
+    )
+    logger.log(Level.INFO, "\n")
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+    logger.log(
+      Level.INFO,
+      "  | Attributes:                                                                         |"
+    )
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+    for (command in commands) {
+      if (commandName == command.name) {
+        System.out.printf("  | * %-82s|\n", command.getAttribute())
+      }
+    }
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+  }
+
+  private fun showClusterEvents(clusterName: String, commandName: String, commands: List<Command>) {
+    logger.log(Level.INFO, "Usage:")
+    System.out.printf(
+      "  kotlin-matter-controller %s %s event-name [param1 param2 ...]\n",
+      clusterName,
+      commandName
+    )
+    logger.log(Level.INFO, "\n")
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+    logger.log(
+      Level.INFO,
+      "  | Events:                                                                             |"
+    )
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+    for (command in commands) {
+      if (commandName == command.name) {
+        System.out.printf("  | * %-82s|\n", command.getAttribute())
+      }
+    }
+    logger.log(
+      Level.INFO,
+      "  +-------------------------------------------------------------------------------------+"
+    )
+  }
+
+  private fun showCommand(clusterName: String, command: Command) {
+    logger.log(Level.INFO, "Usage:")
+    var arguments: String? = command.name
+    var description = ""
+    val argumentsCount = command.getArgumentsCount()
+    for (i in 0 until argumentsCount) {
+      var arg = ""
+      val isOptional = command.getArgumentIsOptional(i)
+      if (isOptional) {
+        arg += "[--"
+      }
+      arg += command.getArgumentName(i)
+      if (isOptional) {
+        arg += "]"
+      }
+      arguments += " "
+      arguments += arg
+      val argDescription = command.getArgumentDescription(i)
+      if (argDescription != null) {
+        description += "\n"
+        description += arg
+        description += ":\n  "
+        description += argDescription
+        description += "\n"
+      }
+    }
+    System.out.format("  %s %s\n", clusterName, arguments)
+    val helpText = command.helpText
+    if (helpText != null) {
+      System.out.format("\n%s\n", helpText)
+    }
+    if (!description.isEmpty()) {
+      logger.log(Level.INFO, description)
+    }
+  }
+
+  companion object {
+    private val logger = Logger.getLogger(CommandManager::class.java.name)
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CredentialsIssuer.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CredentialsIssuer.kt
new file mode 100644
index 0000000..a0a3848
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CredentialsIssuer.kt
@@ -0,0 +1,24 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+/**
+ * Credentials Issuer which contains all credential information of the issuer of the command, such
+ * as operational credentials for a given fabric, the DAC verifier of the commissioner, etc ..
+ */
+class CredentialsIssuer
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.kt
new file mode 100644
index 0000000..5fe4b52
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.kt
@@ -0,0 +1,77 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+import java.util.concurrent.TimeoutException
+import java.util.logging.Level
+import java.util.logging.Logger
+
+/**
+ * Implements the future result that encapculates the optional realResult, application would wait
+ * for realResult set by other thread wben receiving data from the other end. If the expected
+ * duration elapsed without receiving the expected realResult, the runtime exception would be
+ * raised.
+ */
+class FutureResult {
+  private var realResult: RealResult? = null
+  private val lock = Object()
+  var timeoutMs: Long = 0
+
+  fun setRealResult(realResult: RealResult) {
+    synchronized(lock) {
+      if (this.realResult != null) {
+        throw TimeoutException("Error, real result has been set!")
+      }
+      this.realResult = realResult
+      lock.notifyAll()
+    }
+  }
+
+  fun waitResult() {
+    val start = System.currentTimeMillis()
+    synchronized(lock) {
+      while (realResult == null) {
+        val remainingTime = timeoutMs - (System.currentTimeMillis() - start)
+        if (remainingTime <= 0) {
+          throw TimeoutException("Timeout!")
+        }
+
+        try {
+          lock.wait(remainingTime)
+        } catch (e: InterruptedException) {
+          logger.log(Level.INFO, "Wait Result failed with exception: " + e.message)
+        }
+      }
+
+      val errorResult = realResult as? RealResult.Error
+      if (errorResult != null) {
+        logger.log(Level.INFO, "Error: ${errorResult.error}")
+        throw TimeoutException("Received failure test result")
+      }
+    }
+  }
+
+  fun clear() {
+    synchronized(lock) { realResult = null }
+  }
+
+  companion object {
+    private val logger = Logger.getLogger(FutureResult::class.java.name)
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/IPAddress.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/IPAddress.kt
new file mode 100644
index 0000000..c28ce9c
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/IPAddress.kt
@@ -0,0 +1,26 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+import java.net.InetAddress
+
+class IPAddress(var address: InetAddress) {
+  override fun toString(): String {
+    return address.toString()
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.kt
new file mode 100644
index 0000000..6ca0e22
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.kt
@@ -0,0 +1,113 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicLong
+import matter.controller.MatterController
+
+abstract class MatterCommand(
+  private val matterController: MatterController,
+  private val credIssuerCmds: CredentialsIssuer?,
+  commandName: String,
+  helpText: String? = null
+) : Command(commandName, helpText) {
+  private val commissionerName = StringBuffer()
+  private val paaTrustStorePath = StringBuffer()
+  private val cdTrustStorePath = StringBuffer()
+  private val commissionerNodeId: AtomicLong = AtomicLong()
+  private val useMaxSizedCerts: AtomicBoolean = AtomicBoolean()
+  private val onlyAllowTrustedCdKeys: AtomicBoolean = AtomicBoolean()
+  private val futureResult = FutureResult()
+
+  init {
+    addArgument(
+      "paa-trust-store-path",
+      paaTrustStorePath,
+      "Path to directory holding PAA certificate information.  Can be absolute or relative to the current working " +
+        "directory.",
+      true
+    )
+    addArgument(
+      "cd-trust-store-path",
+      cdTrustStorePath,
+      "Path to directory holding CD certificate information.  Can be absolute or relative to the current working " +
+        "directory.",
+      true
+    )
+    addArgument(
+      "commissioner-name",
+      commissionerName,
+      "Name of fabric to use. Valid values are \"alpha\", \"beta\", \"gamma\", and integers greater than or equal to " +
+        "4.  The default if not specified is \"alpha\".",
+      true
+    )
+    addArgument(
+      "commissioner-nodeid",
+      0,
+      Long.MAX_VALUE,
+      commissionerNodeId,
+      "The node id to use for kotlin-matter-controller.  If not provided, kTestControllerNodeId (112233, 0x1B669) will be used.",
+      true
+    )
+    addArgument(
+      "use-max-sized-certs",
+      useMaxSizedCerts,
+      "Maximize the size of operational certificates. If not provided or 0 (\"false\"), normally sized operational " +
+        "certificates are generated.",
+      true
+    )
+    addArgument(
+      "only-allow-trusted-cd-keys",
+      onlyAllowTrustedCdKeys,
+      "Only allow trusted CD verifying keys (disallow test keys). If not provided or 0 (\"false\"), untrusted CD " +
+        "verifying keys are allowed. If 1 (\"true\"), test keys are disallowed.",
+      true
+    )
+  }
+
+  // This method returns the commissioner instance to be used for running the command.
+  fun currentCommissioner(): MatterController {
+    return matterController
+  }
+
+  /////////// Command Interface /////////
+  @Throws(Exception::class)
+  override fun run() {
+    runCommand()
+  }
+
+  protected abstract fun runCommand()
+
+  fun setSuccess() {
+    futureResult.setRealResult(RealResult.success())
+  }
+
+  fun setFailure(error: String?) {
+    futureResult.setRealResult(RealResult.error(error))
+  }
+
+  fun waitCompleteMs(timeoutMs: Long) {
+    futureResult.timeoutMs = timeoutMs
+    futureResult.waitResult()
+  }
+
+  fun clear() {
+    futureResult.clear()
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/RealResult.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/RealResult.kt
new file mode 100644
index 0000000..50debc3
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/RealResult.kt
@@ -0,0 +1,41 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.common
+
+/**
+ * Represents a result that can either indicate success or failure with an associated error message.
+ *
+ * In the context of RealResult, success is represented by [Success] and failure by [Error]. When
+ * there is an error, an error message explains the reason for the failure.
+ */
+sealed class RealResult {
+  data class Error(val error: String) : RealResult()
+
+  object Success : RealResult()
+
+  companion object {
+    fun success(): RealResult {
+      return Success
+    }
+
+    fun error(error: String?): RealResult {
+      return error?.let { Error(it) } ?: Success
+    }
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt
new file mode 100644
index 0000000..eb254ae
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt
@@ -0,0 +1,30 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.pairing
+
+enum class DiscoveryFilterType {
+  NONE,
+  SHORT_DISCRIMINATOR,
+  LONG_DISCRIMINATOR,
+  VENDOR_ID,
+  DEVICE_TYPE,
+  COMMISSIONING_MODE,
+  INSTANCE_NAME,
+  COMMISSIONER,
+  COMPRESSED_FABRIC_ID
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.kt
new file mode 100644
index 0000000..594d30d
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.kt
@@ -0,0 +1,46 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.pairing
+
+import com.matter.controller.commands.common.CredentialsIssuer
+import matter.controller.MatterController
+
+private const val MATTER_PORT = 5540
+
+class PairOnNetworkLongCommand(controller: MatterController, credsIssue: CredentialsIssuer?) :
+  PairingCommand(
+    controller,
+    "onnetwork-long",
+    credsIssue,
+    PairingModeType.ON_NETWORK,
+    PairingNetworkType.NONE,
+    DiscoveryFilterType.LONG_DISCRIMINATOR
+  ) {
+  override fun runCommand() {
+    currentCommissioner()
+      .pairDevice(
+        getNodeId(),
+        getRemoteAddr().address.hostAddress,
+        MATTER_PORT,
+        getDiscriminator(),
+        getSetupPINCode(),
+      )
+    currentCommissioner().setCompletionListener(this)
+    waitCompleteMs(getTimeoutMillis())
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt
new file mode 100644
index 0000000..313e674
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt
@@ -0,0 +1,96 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.pairing
+
+import chip.tlv.AnonymousTag
+import chip.tlv.ContextSpecificTag
+import chip.tlv.TlvWriter
+import com.matter.controller.commands.common.CredentialsIssuer
+import java.time.Duration
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.runBlocking
+import matter.controller.InvokeRequest
+import matter.controller.InvokeResponse
+import matter.controller.MatterController
+import matter.controller.model.CommandPath
+
+class PairOnNetworkLongImInvokeCommand(
+  controller: MatterController,
+  credsIssue: CredentialsIssuer?
+) :
+  PairingCommand(
+    controller,
+    "onnetwork-long-im-invoke",
+    credsIssue,
+    PairingModeType.ON_NETWORK,
+    PairingNetworkType.NONE,
+    DiscoveryFilterType.LONG_DISCRIMINATOR
+  ) {
+  override fun runCommand() {
+    val IdentifyTime: UShort = 1u
+    val tlvWriter1 = TlvWriter()
+    tlvWriter1.startStructure(AnonymousTag)
+    tlvWriter1.put(ContextSpecificTag(0), IdentifyTime)
+    tlvWriter1.endStructure()
+
+    val element1: InvokeRequest =
+      InvokeRequest(
+        CommandPath(endpointId = 0u, clusterId = CLUSTER_ID_IDENTIFY, commandId = IDENTIFY_COMMAND),
+        tlvPayload = tlvWriter1.getEncoded(),
+        timedRequest = Duration.ZERO
+      )
+
+    currentCommissioner()
+      .pairDevice(
+        getNodeId(),
+        getRemoteAddr().address.hostAddress,
+        MATTER_PORT,
+        getDiscriminator(),
+        getSetupPINCode(),
+      )
+    currentCommissioner().setCompletionListener(this)
+    waitCompleteMs(getTimeoutMillis())
+
+    runBlocking {
+      try {
+        val response: InvokeResponse = currentCommissioner().invoke(element1)
+        logger.log(Level.INFO, "Invoke command succeeded")
+        if (response.payload.isNotEmpty()) {
+          // TODO:Handle TLV data response
+        }
+      } catch (ex: Exception) {
+        setFailure("invoke failure: ${ex.message}")
+      } finally {
+        clear()
+      }
+    }
+
+    setSuccess()
+  }
+
+  companion object {
+    private val logger = Logger.getLogger(PairOnNetworkLongImInvokeCommand::class.java.name)
+
+    private const val MATTER_PORT = 5540
+    private const val CLUSTER_ID_IDENTIFY = 0x0003u
+    private const val IDENTIFY_COMMAND = 0u
+    private const val CLUSTER_ID_TEST = 0xFFF1FC05u
+    private const val TEST_ADD_ARGUMENT_COMMAND = 0X04u
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
new file mode 100644
index 0000000..15cf62e
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
@@ -0,0 +1,109 @@
+package com.matter.controller.commands.pairing
+
+import com.matter.controller.commands.common.CredentialsIssuer
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.runBlocking
+import matter.controller.MatterController
+import matter.controller.ReadData
+import matter.controller.ReadRequest
+import matter.controller.ReadResponse
+import matter.controller.model.AttributePath
+import matter.controller.model.EventPath
+
+class PairOnNetworkLongImReadCommand(controller: MatterController, credsIssue: CredentialsIssuer?) :
+  PairingCommand(
+    controller,
+    "onnetwork-long-im-read",
+    credsIssue,
+    PairingModeType.ON_NETWORK,
+    PairingNetworkType.NONE,
+    DiscoveryFilterType.LONG_DISCRIMINATOR
+  ) {
+  override fun runCommand() {
+    val attributePaths =
+      listOf(
+        AttributePath(
+          endpointId = DEFAULT_ENDPOINT,
+          clusterId = CLUSTER_ID_BASIC,
+          attributeId = ATTR_ID_LOCAL_CONFIG_DISABLED,
+        ),
+        AttributePath(
+          endpointId = DEFAULT_ENDPOINT,
+          clusterId = CLUSTER_ID_BASIC,
+          attributeId = GLOBAL_ATTRIBUTE_LIST,
+        )
+      )
+
+    val eventPaths =
+      listOf(
+        EventPath(
+          endpointId = WILDCARD_ENDPOINT_ID,
+          clusterId = WILDCARD_CLUSTER_ID,
+          eventId = WILDCARD_EVENT_ID
+        )
+      )
+
+    val readRequest: ReadRequest = ReadRequest(eventPaths, attributePaths)
+
+    currentCommissioner()
+      .pairDevice(
+        getNodeId(),
+        getRemoteAddr().address.hostAddress,
+        MATTER_PORT,
+        getDiscriminator(),
+        getSetupPINCode(),
+      )
+    currentCommissioner().setCompletionListener(this)
+    waitCompleteMs(getTimeoutMillis())
+
+    runBlocking {
+      try {
+        val response: ReadResponse = currentCommissioner().read(readRequest)
+        logger.log(Level.INFO, "Read command succeeded")
+        validateResponse(response)
+      } catch (ex: Exception) {
+        logger.log(Level.WARNING, "General read failure occurred with error ${ex.message}")
+        setFailure("read failure")
+      } finally {
+        clear()
+      }
+    }
+
+    setSuccess()
+  }
+
+  private fun findAttributeById(successes: List<ReadData>, id: UInt): ReadData.Attribute? {
+    return successes.filterIsInstance<ReadData.Attribute>().find { it.path.attributeId == id }
+  }
+
+  private fun findEventById(successes: List<ReadData>, id: UInt): ReadData.Event? {
+    return successes.filterIsInstance<ReadData.Event>().find { it.path.eventId == id }
+  }
+
+  private fun validateResponse(response: ReadResponse) {
+    require(response.successes.isNotEmpty()) { "Unexpected: response.successes is empty" }
+    require(response.failures.isEmpty()) { "Unexpected: response.failures is not empty" }
+
+    val localConfigDisabledAttribute =
+      findAttributeById(response.successes, ATTR_ID_LOCAL_CONFIG_DISABLED)
+    requireNotNull(localConfigDisabledAttribute) { "No local config disabled attribute found." }
+
+    // TODO: Add more validation rules as needed
+  }
+
+  companion object {
+    private val logger = Logger.getLogger(PairOnNetworkLongImReadCommand::class.java.name)
+
+    private const val MATTER_PORT = 5540
+    private const val DEFAULT_ENDPOINT: UShort = 0u
+    private const val WILDCARD_ENDPOINT_ID: UShort = 0xffffu
+    private const val WILDCARD_CLUSTER_ID: UInt = 0xffffffffu
+    private const val WILDCARD_ATTRIBUTE_ID: UInt = 0xffffffffu
+    private const val WILDCARD_EVENT_ID: UInt = 0xffffffffu
+    private const val CLUSTER_ID_BASIC: UInt = 0x0028u
+    private const val ATTR_ID_LOCAL_CONFIG_DISABLED: UInt = 16u
+    private const val EVENT_ID_START_UP: UInt = 0u
+    private const val GLOBAL_ATTRIBUTE_LIST: UInt = 65531u
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImSubscribeCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImSubscribeCommand.kt
new file mode 100644
index 0000000..c2ea796
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImSubscribeCommand.kt
@@ -0,0 +1,113 @@
+package com.matter.controller.commands.pairing
+
+import com.matter.controller.commands.common.CredentialsIssuer
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.runBlocking
+import matter.controller.MatterController
+import matter.controller.SubscribeRequest
+import matter.controller.SubscriptionState
+import matter.controller.model.AttributePath
+import matter.controller.model.EventPath
+
+class PairOnNetworkLongImSubscribeCommand(
+  controller: MatterController,
+  credsIssue: CredentialsIssuer?
+) :
+  PairingCommand(
+    controller,
+    "onnetwork-long-im-subscribe",
+    credsIssue,
+    PairingModeType.ON_NETWORK,
+    PairingNetworkType.NONE,
+    DiscoveryFilterType.LONG_DISCRIMINATOR
+  ) {
+  override fun runCommand() {
+    val attributePaths =
+      listOf(
+        AttributePath(
+          endpointId = WILDCARD_ENDPOINT_ID,
+          clusterId = WILDCARD_CLUSTER_ID,
+          attributeId = WILDCARD_EVENT_ID,
+        )
+      )
+
+    val eventPaths =
+      listOf(
+        EventPath(
+          endpointId = WILDCARD_ENDPOINT_ID,
+          clusterId = WILDCARD_CLUSTER_ID,
+          eventId = WILDCARD_EVENT_ID
+        )
+      )
+
+    val subscribeRequest: SubscribeRequest = SubscribeRequest(eventPaths, attributePaths)
+
+    currentCommissioner()
+      .pairDevice(
+        getNodeId(),
+        getRemoteAddr().address.hostAddress,
+        MATTER_PORT,
+        getDiscriminator(),
+        getSetupPINCode(),
+      )
+    currentCommissioner().setCompletionListener(this)
+    waitCompleteMs(getTimeoutMillis())
+
+    runBlocking {
+      try {
+        startSubscription(subscribeRequest)
+      } catch (ex: Exception) {
+        logger.log(Level.WARNING, "General subscribe failure occurred with error ${ex.message}")
+        setFailure("subscribe failure")
+      } finally {
+        clear()
+      }
+    }
+
+    setSuccess()
+  }
+
+  private suspend fun startSubscription(request: SubscribeRequest) {
+    logger.log(Level.INFO, "Starting subscription")
+
+    currentCommissioner()
+      .subscribe(request)
+      .takeWhile { subscriptionState ->
+        // Keep collecting as long as it's not SubscriptionEstablished
+        subscriptionState !is SubscriptionState.SubscriptionEstablished
+      }
+      .collect { subscriptionState ->
+        when (subscriptionState) {
+          is SubscriptionState.NodeStateUpdate -> {
+            logger.log(Level.INFO, "Received NodeStateUpdate: ${subscriptionState.updateState}")
+
+            // TODO: Add more validation rules as needed
+          }
+          is SubscriptionState.SubscriptionErrorNotification -> {
+            logger.log(
+              Level.WARNING,
+              "Received SubscriptionErrorNotification with terminationCause: ${subscriptionState.terminationCause}"
+            )
+          }
+          is SubscriptionState.SubscriptionEstablished -> {
+            logger.log(Level.INFO, "Subscription is established")
+          }
+          else -> {
+            logger.log(Level.SEVERE, "Unexpected subscription state: $subscriptionState")
+          }
+        }
+      }
+  }
+
+  companion object {
+    private val logger = Logger.getLogger(PairOnNetworkLongImSubscribeCommand::class.java.name)
+
+    private const val MATTER_PORT = 5540
+    private const val WILDCARD_ENDPOINT_ID: UShort = 0xffffu
+    private const val WILDCARD_CLUSTER_ID: UInt = 0xffffffffu
+    private const val WILDCARD_ATTRIBUTE_ID: UInt = 0xffffffffu
+    private const val WILDCARD_EVENT_ID: UInt = 0xffffffffu
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt
new file mode 100644
index 0000000..376e132
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt
@@ -0,0 +1,116 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.pairing
+
+import chip.tlv.AnonymousTag
+import chip.tlv.TlvWriter
+import com.matter.controller.commands.common.CredentialsIssuer
+import java.time.Duration
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.runBlocking
+import matter.controller.MatterController
+import matter.controller.WriteRequest
+import matter.controller.WriteRequests
+import matter.controller.WriteResponse
+import matter.controller.model.AttributePath
+
+class PairOnNetworkLongImWriteCommand(
+  controller: MatterController,
+  credsIssue: CredentialsIssuer?
+) :
+  PairingCommand(
+    controller,
+    "onnetwork-long-im-write",
+    credsIssue,
+    PairingModeType.ON_NETWORK,
+    PairingNetworkType.NONE,
+    DiscoveryFilterType.LONG_DISCRIMINATOR
+  ) {
+  override fun runCommand() {
+    val tlvWriter1 = TlvWriter()
+    tlvWriter1.put(AnonymousTag, true)
+    val writeRequests: WriteRequests =
+      WriteRequests(
+        requests =
+          listOf(
+            WriteRequest(
+              attributePath =
+                AttributePath(
+                  endpointId = 0u,
+                  clusterId = CLUSTER_ID_BASIC,
+                  attributeId = ATTR_ID_LOCAL_CONFIG_DISABLED
+                ),
+              tlvPayload = tlvWriter1.getEncoded()
+            )
+          ),
+        timedRequest = Duration.ZERO
+      )
+
+    currentCommissioner()
+      .pairDevice(
+        getNodeId(),
+        getRemoteAddr().address.hostAddress,
+        MATTER_PORT,
+        getDiscriminator(),
+        getSetupPINCode(),
+      )
+    currentCommissioner().setCompletionListener(this)
+    waitCompleteMs(getTimeoutMillis())
+
+    runBlocking {
+      try {
+        val response: WriteResponse = currentCommissioner().write(writeRequests)
+
+        if (response is WriteResponse.Success) {
+          logger.log(Level.INFO, "Write command succeeded")
+        } else if (response is WriteResponse.PartialWriteFailure) {
+          logger.log(
+            Level.WARNING,
+            "Partial write failure occurred with ${response.failures.size} errors"
+          )
+
+          for ((index, error) in response.failures.withIndex()) {
+            logger.log(Level.WARNING, "Error ${index + 1}:")
+            logger.log(Level.WARNING, "Attribute Path: ${error.attributePath}")
+            logger.log(Level.WARNING, "Exception Message: ${error.ex.message}")
+          }
+
+          setFailure("invoke failure")
+        }
+      } catch (ex: Exception) {
+        setFailure("invoke failure: ${ex.message}")
+      } catch (ex: Exception) {
+        logger.log(Level.WARNING, "General write failure occurred with error ${ex.message}")
+        setFailure("invoke failure")
+      } finally {
+        clear()
+      }
+    }
+
+    setSuccess()
+  }
+
+  companion object {
+    private val logger = Logger.getLogger(PairOnNetworkLongImWriteCommand::class.java.name)
+
+    private const val MATTER_PORT = 5540
+    private const val CLUSTER_ID_BASIC = 0x0028u
+    private const val ATTR_ID_LOCAL_CONFIG_DISABLED = 16u
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingCommand.kt
new file mode 100644
index 0000000..96b8565
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingCommand.kt
@@ -0,0 +1,219 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.pairing
+
+import com.matter.controller.commands.common.CredentialsIssuer
+import com.matter.controller.commands.common.IPAddress
+import com.matter.controller.commands.common.MatterCommand
+import java.net.InetAddress
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+import java.util.logging.Level
+import java.util.logging.Logger
+import matter.controller.MatterController
+
+abstract class PairingCommand(
+  controller: MatterController,
+  commandName: String,
+  credsIssuer: CredentialsIssuer?,
+  private val pairingMode: PairingModeType = PairingModeType.NONE,
+  private val networkType: PairingNetworkType = PairingNetworkType.NONE,
+  private val filterType: DiscoveryFilterType = DiscoveryFilterType.NONE
+) : MatterCommand(controller, credsIssuer, commandName), MatterController.CompletionListener {
+  private val remoteAddr: IPAddress = IPAddress(InetAddress.getByName("::1"))
+  private val nodeId = AtomicLong()
+  private val discoveryFilterCode = AtomicLong()
+  private val timeoutMillis = AtomicLong()
+  private val discoverOnce = AtomicBoolean()
+  private val useOnlyOnNetworkDiscovery = AtomicBoolean()
+  private val remotePort = AtomicInteger()
+  private val discriminator = AtomicInteger()
+  private val setupPINCode = AtomicLong()
+  private val operationalDataset = StringBuffer()
+  private val ssid = StringBuffer()
+  private val password = StringBuffer()
+  private val onboardingPayload = StringBuffer()
+  private val discoveryFilterInstanceName = StringBuffer()
+
+  init {
+    addArgument("node-id", 0, Long.MAX_VALUE, nodeId, null, false)
+
+    when (networkType) {
+      PairingNetworkType.NONE -> {}
+      PairingNetworkType.WIFI -> {
+        addArgument("ssid", ssid, null, false)
+        addArgument("password", password, null, false)
+      }
+      PairingNetworkType.THREAD ->
+        addArgument("operationalDataset", operationalDataset, null, false)
+    }
+
+    when (pairingMode) {
+      PairingModeType.NONE -> {}
+      PairingModeType.CODE,
+      PairingModeType.CODE_PASE_ONLY -> {
+        addArgument("payload", onboardingPayload, null, false)
+        addArgument("discover-once", discoverOnce, null, true)
+        addArgument("use-only-onnetwork-discovery", useOnlyOnNetworkDiscovery, null, true)
+      }
+      PairingModeType.ADDRESS_PASE_ONLY -> {
+        addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+        addArgument("device-remote-ip", remoteAddr, false)
+        addArgument("device-remote-port", 0.toShort(), Short.MAX_VALUE, remotePort, null, false)
+      }
+      PairingModeType.BLE -> {
+        addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+        addArgument("discriminator", 0.toShort(), 4096.toShort(), discriminator, null, false)
+      }
+      PairingModeType.ON_NETWORK ->
+        addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+      PairingModeType.SOFT_AP -> {
+        addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+        addArgument("discriminator", 0.toShort(), 4096.toShort(), discriminator, null, false)
+        addArgument("device-remote-ip", remoteAddr, false)
+        addArgument("device-remote-port", 0.toShort(), Short.MAX_VALUE, remotePort, null, false)
+      }
+      PairingModeType.ALREADY_DISCOVERED -> {
+        addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+        addArgument("device-remote-ip", remoteAddr, false)
+        addArgument("device-remote-port", 0.toShort(), Short.MAX_VALUE, remotePort, null, false)
+      }
+    }
+
+    when (filterType) {
+      DiscoveryFilterType.NONE -> {}
+      DiscoveryFilterType.SHORT_DISCRIMINATOR,
+      DiscoveryFilterType.LONG_DISCRIMINATOR ->
+        addArgument("discriminator", 0.toShort(), 4096.toShort(), discriminator, null, false)
+      DiscoveryFilterType.VENDOR_ID ->
+        addArgument("vendor-id", 1.toShort(), Short.MAX_VALUE, discoveryFilterCode, null, false)
+      DiscoveryFilterType.COMPRESSED_FABRIC_ID ->
+        addArgument("fabric-id", 0L, Long.MAX_VALUE, discoveryFilterCode, null, false)
+      DiscoveryFilterType.COMMISSIONING_MODE,
+      DiscoveryFilterType.COMMISSIONER -> {}
+      DiscoveryFilterType.DEVICE_TYPE ->
+        addArgument("device-type", 0.toShort(), Short.MAX_VALUE, discoveryFilterCode, null, false)
+      DiscoveryFilterType.INSTANCE_NAME ->
+        addArgument("name", discoveryFilterInstanceName, null, false)
+    }
+
+    addArgument("timeout", 0L, Long.MAX_VALUE, timeoutMillis, null, false)
+  }
+
+  override fun onConnectDeviceComplete() {
+    logger.log(Level.INFO, "onConnectDeviceComplete")
+  }
+
+  override fun onStatusUpdate(status: Int) {
+    logger.log(Level.INFO, "onStatusUpdate with status: $status")
+  }
+
+  override fun onPairingComplete(errorCode: Int) {
+    logger.log(Level.INFO, "onPairingComplete with error code: $errorCode")
+    if (errorCode != 0) {
+      setFailure("onPairingComplete failure")
+    }
+  }
+
+  override fun onPairingDeleted(errorCode: Int) {
+    logger.log(Level.INFO, "onPairingDeleted with error code: $errorCode")
+  }
+
+  override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
+    logger.log(Level.INFO, "onCommissioningComplete with error code: $errorCode")
+    if (errorCode == 0) {
+      setSuccess()
+    } else {
+      setFailure("onCommissioningComplete failure")
+    }
+  }
+
+  override fun onReadCommissioningInfo(
+    vendorId: Int,
+    productId: Int,
+    wifiEndpointId: Int,
+    threadEndpointId: Int
+  ) {
+    logger.log(Level.INFO, "onReadCommissioningInfo")
+  }
+
+  override fun onCommissioningStatusUpdate(nodeId: Long, stage: String?, errorCode: Int) {
+    logger.log(Level.INFO, "onCommissioningStatusUpdate")
+  }
+
+  override fun onNotifyChipConnectionClosed() {
+    logger.log(Level.INFO, "onNotifyChipConnectionClosed")
+  }
+
+  override fun onError(error: Throwable) {
+    setFailure(error.toString())
+    logger.log(Level.INFO, "onError with error: $error")
+  }
+
+  override fun onOpCSRGenerationComplete(csr: ByteArray) {
+    logger.log(Level.INFO, "onOpCSRGenerationComplete")
+    for (i in csr.indices) {
+      print(csr[i].toString() + " ")
+    }
+  }
+
+  fun getNodeId(): Long {
+    return nodeId.get()
+  }
+
+  fun getRemoteAddr(): IPAddress {
+    return remoteAddr
+  }
+
+  fun getRemotePort(): Int {
+    return remotePort.get()
+  }
+
+  fun getSetupPINCode(): Long {
+    return setupPINCode.get()
+  }
+
+  fun getDiscriminator(): Int {
+    return discriminator.get()
+  }
+
+  fun getTimeoutMillis(): Long {
+    return timeoutMillis.get()
+  }
+
+  fun getOnboardingPayload(): String {
+    return onboardingPayload.toString()
+  }
+
+  private fun String.hexToByteArray(): ByteArray {
+    return chunked(2).map { byteStr -> byteStr.toUByte(16).toByte() }.toByteArray()
+  }
+
+  fun getDiscoverOnce(): Boolean {
+    return discoverOnce.get()
+  }
+
+  fun getUseOnlyOnNetworkDiscovery(): Boolean {
+    return useOnlyOnNetworkDiscovery.get()
+  }
+
+  companion object {
+    private val logger = Logger.getLogger(PairingCommand::class.java.name)
+  }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingModeType.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingModeType.kt
new file mode 100644
index 0000000..590688d
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingModeType.kt
@@ -0,0 +1,29 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.pairing
+
+enum class PairingModeType {
+  NONE,
+  CODE,
+  CODE_PASE_ONLY,
+  ADDRESS_PASE_ONLY,
+  BLE,
+  SOFT_AP,
+  ALREADY_DISCOVERED,
+  ON_NETWORK
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingNetworkType.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingNetworkType.kt
new file mode 100644
index 0000000..4cde1fc
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingNetworkType.kt
@@ -0,0 +1,24 @@
+/*
+ *   Copyright (c) 2023 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 com.matter.controller.commands.pairing
+
+enum class PairingNetworkType {
+  NONE,
+  WIFI,
+  THREAD
+}
diff --git a/examples/kotlin-matter-controller/third_party/connectedhomeip b/examples/kotlin-matter-controller/third_party/connectedhomeip
new file mode 120000
index 0000000..1b20c9f
--- /dev/null
+++ b/examples/kotlin-matter-controller/third_party/connectedhomeip
@@ -0,0 +1 @@
+../../../
\ No newline at end of file
diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py
index c1b0fad..1780737 100755
--- a/scripts/build/build/targets.py
+++ b/scripts/build/build/targets.py
@@ -111,6 +111,8 @@
         TargetPart('thermostat', app=HostApp.THERMOSTAT),
         TargetPart('java-matter-controller',
                    app=HostApp.JAVA_MATTER_CONTROLLER),
+        TargetPart('kotlin-matter-controller',
+                   app=HostApp.KOTLIN_MATTER_CONTROLLER),
         TargetPart('minmdns', app=HostApp.MIN_MDNS),
         TargetPart('light', app=HostApp.LIGHT),
         TargetPart('lock', app=HostApp.LOCK),
@@ -332,6 +334,8 @@
         TargetPart('tv-casting-app', app=AndroidApp.TV_CASTING_APP),
         TargetPart('java-matter-controller',
                    app=AndroidApp.JAVA_MATTER_CONTROLLER),
+        TargetPart('kotlin-matter-controller',
+                   app=AndroidApp.KOTLIN_MATTER_CONTROLLER),
         TargetPart('virtual-device-app',
                    app=AndroidApp.VIRTUAL_DEVICE_APP),
     ])
diff --git a/scripts/build/builders/android.py b/scripts/build/builders/android.py
index a5b86e7..14802fe 100644
--- a/scripts/build/builders/android.py
+++ b/scripts/build/builders/android.py
@@ -71,6 +71,7 @@
     TV_SERVER = auto()
     TV_CASTING_APP = auto()
     JAVA_MATTER_CONTROLLER = auto()
+    KOTLIN_MATTER_CONTROLLER = auto()
     VIRTUAL_DEVICE_APP = auto()
 
     def AppName(self):
diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py
index 01a9e77..fcc1598 100644
--- a/scripts/build/builders/host.py
+++ b/scripts/build/builders/host.py
@@ -67,6 +67,7 @@
     TV_CASTING = auto()
     BRIDGE = auto()
     JAVA_MATTER_CONTROLLER = auto()
+    KOTLIN_MATTER_CONTROLLER = auto()
     CONTACT_SENSOR = auto()
     DISHWASHER = auto()
     REFRIGERATOR = auto()
@@ -113,6 +114,8 @@
             return 'bridge-app/linux'
         elif self == HostApp.JAVA_MATTER_CONTROLLER:
             return 'java-matter-controller'
+        elif self == HostApp.KOTLIN_MATTER_CONTROLLER:
+            return 'kotlin-matter-controller'
         elif self == HostApp.CONTACT_SENSOR:
             return 'contact-sensor-app/linux'
         elif self == HostApp.DISHWASHER:
@@ -197,6 +200,9 @@
         elif self == HostApp.JAVA_MATTER_CONTROLLER:
             yield 'java-matter-controller'
             yield 'java-matter-controller.map'
+        elif self == HostApp.KOTLIN_MATTER_CONTROLLER:
+            yield 'kotlin-matter-controller'
+            yield 'kotlin-matter-controller.map'
         elif self == HostApp.CONTACT_SENSOR:
             yield 'contact-sensor-app'
             yield 'contact-sensor-app.map'
@@ -451,6 +457,15 @@
                     ],
                     title="Copying Manifest.txt to " + self.output_dir,
                 )
+            if exampleName == "kotlin-matter-controller":
+                self._Execute(
+                    [
+                        "cp",
+                        os.path.join(self.root, "Manifest.txt"),
+                        self.output_dir,
+                    ],
+                    title="Copying Manifest.txt to " + self.output_dir,
+                )
 
         if self.app == HostApp.TESTS and self.use_coverage:
             self.coverage_dir = os.path.join(self.output_dir, 'coverage')
@@ -484,6 +499,9 @@
         if self.app == HostApp.JAVA_MATTER_CONTROLLER:
             self.createJavaExecutable("java-matter-controller")
 
+        if self.app == HostApp.KOTLIN_MATTER_CONTROLLER:
+            self.createJavaExecutable("kotlin-matter-controller")
+
     def build_outputs(self):
         outputs = {}
 
diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt
index dd29cce..4c65d6c 100644
--- a/scripts/build/testdata/all_targets_linux_x64.txt
+++ b/scripts/build/testdata/all_targets_linux_x64.txt
@@ -1,6 +1,6 @@
 ameba-amebad-{all-clusters,all-clusters-minimal,light,light-switch,pigweed}
 asr-{asr582x,asr595x,asr550x}-{all-clusters,all-clusters-minimal,lighting,light-switch,lock,bridge,temperature-measurement,thermostat,ota-requestor,dishwasher,refrigerator}[-ota][-shell][-no_logging][-factory][-rotating_id][-rio]
-android-{arm,arm64,x86,x64,androidstudio-arm,androidstudio-arm64,androidstudio-x86,androidstudio-x64}-{chip-tool,chip-test,tv-server,tv-casting-app,java-matter-controller,virtual-device-app}[-no-debug]
+android-{arm,arm64,x86,x64,androidstudio-arm,androidstudio-arm64,androidstudio-x86,androidstudio-x64}-{chip-tool,chip-test,tv-server,tv-casting-app,java-matter-controller,kotlin-matter-controller,virtual-device-app}[-no-debug]
 bouffalolab-{bl602-iot-matter-v1,bl602-night-light,xt-zb6-devkit,bl706-night-light,bl706dk,bl704ldk}-light[-shell][-115200][-rpc][-cdc][-resetcnt][-rotating_device_id][-mfd][-mfdtest][-ethernet][-wifi][-thread][-fp][-memmonitor]
 cc32xx-lock
 ti-cc13x2x7_26x2x7-{lighting,lock,pump,pump-controller}[-mtd]
@@ -10,7 +10,7 @@
 esp32-{m5stack,c3devkit,devkitc,qemu}-{all-clusters,all-clusters-minimal,ota-provider,ota-requestor,shell,light,lock,bridge,temperature-measurement,ota-requestor,tests}[-rpc][-ipv6only][-tracing]
 genio-lighting-app
 linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang]
-linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,minmdns,light,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,refrigerator,rvc}[-nodeps][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui]
+linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,refrigerator,rvc}[-nodeps][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui]
 linux-x64-efr32-test-runner[-clang]
 imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release]
 infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage]