From 736d3584622b464ac406ce3b3d3ef9d6ac608a55 Mon Sep 17 00:00:00 2001
From: Yufeng Wang <yufengwang@google.com>
Date: Mon, 13 Mar 2023 10:55:15 -0700
Subject: [PATCH] [Java] Add unit tests for TLV kotlin lib (#25639)

---
 examples/java-matter-controller/BUILD.gn      |    3 +
 src/controller/java/BUILD.gn                  |   43 +
 .../java/tests/chip/tlv/TlvReadWriteTest.kt   | 1035 +++++++++++++++++
 .../java/tests/chip/tlv/TlvReaderTest.kt      |  271 +++++
 .../java/tests/chip/tlv/TlvWriterTest.kt      |   69 ++
 third_party/java_deps/BUILD.gn                |   12 +
 third_party/java_deps/set_up_java_deps.sh     |    3 +
 7 files changed, 1436 insertions(+)
 create mode 100644 src/controller/java/tests/chip/tlv/TlvReadWriteTest.kt
 create mode 100644 src/controller/java/tests/chip/tlv/TlvReaderTest.kt
 create mode 100644 src/controller/java/tests/chip/tlv/TlvWriterTest.kt

diff --git a/examples/java-matter-controller/BUILD.gn b/examples/java-matter-controller/BUILD.gn
index d26b0340ab15b0..cdb67f1b8ed41d 100644
--- a/examples/java-matter-controller/BUILD.gn
+++ b/examples/java-matter-controller/BUILD.gn
@@ -38,6 +38,9 @@ kotlin_binary("java-matter-controller") {
   output_name = "java-matter-controller"
   deps = [
     ":java",
+    "${chip_root}/src/controller/java:tlv_read_write_test",
+    "${chip_root}/src/controller/java:tlv_reader_test",
+    "${chip_root}/src/controller/java:tlv_writer_test",
     "${chip_root}/third_party/java_deps:kotlin-stdlib",
   ]
 
diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn
index 29226c87ba38d5..27d14372b9c959 100644
--- a/src/controller/java/BUILD.gn
+++ b/src/controller/java/BUILD.gn
@@ -158,6 +158,49 @@ kotlin_library("tlv") {
   kotlinc_flags = [ "-Xlint:deprecation" ]
 }
 
+kotlin_library("tlv_reader_test") {
+  output_name = "TlvReaderTest.jar"
+
+  deps = [
+    ":tlv",
+    "${chip_root}/third_party/java_deps:junit-4",
+    "${chip_root}/third_party/java_deps:truth",
+  ]
+
+  sources = [ "tests/chip/tlv/TlvReaderTest.kt" ]
+
+  kotlinc_flags = [ "-Xlint:deprecation" ]
+}
+
+kotlin_library("tlv_writer_test") {
+  output_name = "TlvWriterTest.jar"
+
+  deps = [
+    ":tlv",
+    "${chip_root}/third_party/java_deps:junit-4",
+    "${chip_root}/third_party/java_deps:truth",
+  ]
+
+  sources = [ "tests/chip/tlv/TlvWriterTest.kt" ]
+
+  kotlinc_flags = [ "-Xlint:deprecation" ]
+}
+
+kotlin_library("tlv_read_write_test") {
+  output_name = "TlvReadWriteTest.jar"
+
+  deps = [
+    ":tlv",
+    "${chip_root}/third_party/java_deps:junit-4",
+    "${chip_root}/third_party/java_deps:kotlin-test",
+    "${chip_root}/third_party/java_deps:truth",
+  ]
+
+  sources = [ "tests/chip/tlv/TlvReadWriteTest.kt" ]
+
+  kotlinc_flags = [ "-Xlint:deprecation" ]
+}
+
 android_library("java") {
   output_name = "CHIPController.jar"
 
diff --git a/src/controller/java/tests/chip/tlv/TlvReadWriteTest.kt b/src/controller/java/tests/chip/tlv/TlvReadWriteTest.kt
new file mode 100644
index 00000000000000..fbf60827a278a4
--- /dev/null
+++ b/src/controller/java/tests/chip/tlv/TlvReadWriteTest.kt
@@ -0,0 +1,1035 @@
+/*
+ *
+ *    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.tlv
+
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.ByteString
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+// TLV Encoded structure taken from the C++ TLV unit test of the Matter SDK
+private val testTlvSampleData: ByteArray =
+  ("""
+    0xD5, 0xBB, 0xAA, 0xDD, 0xCC, 0x01, 0x00, 0xC9, 0xBB, 0xAA, 0xDD, 0xCC, 0x02, 0x00, 0x88, 0x02,
+    0x00, 0x36, 0x00, 0x00, 0x2A, 0x00, 0xEF, 0x02, 0xF0, 0x67, 0xFD, 0xFF, 0x07, 0x00, 0x90, 0x2F,
+    0x50, 0x09, 0x00, 0x00, 0x00, 0x15, 0x18, 0x17, 0xD4, 0xBB, 0xAA, 0xDD, 0xCC, 0x11, 0x00, 0xB4,
+    0xA0, 0xBB, 0x0D, 0x00, 0x14, 0xB5, 0x00, 0x28, 0x6B, 0xEE, 0x6D, 0x70, 0x11, 0x01, 0x00, 0x0E,
+    0x01, 0x53, 0x54, 0x41, 0x52, 0x54, 0x2E, 0x2E, 0x2E, 0x21, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x40, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x23, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x24, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x25, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x5E, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x26, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x2A, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x28, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x29, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x2D, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x3D, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x5B, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x5D, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x3B, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+    0x37, 0x27, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x2E, 0x2E, 0x2E, 0x45, 0x4E, 0x44, 0x18,
+    0x18, 0x18, 0xCC, 0xBB, 0xAA, 0xDD, 0xCC, 0x05, 0x00, 0x0E, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69,
+    0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x8A, 0xFF, 0xFF, 0x33, 0x33, 0x8F, 0x41, 0xAB,
+    0x00, 0x00, 0x01, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0xE6, 0x31, 0x40, 0x18
+""")
+    .trimIndent()
+    .replace("0x", "")
+    .replace(", ", "")
+    .replace(",", "")
+    .replace("\n", "")
+    .chunked(2)
+    .map { it.toInt(16) and 0xFF }
+    .map { it.toByte() }
+    .toByteArray()
+
+private val testVendorId: UShort = 0xAABBu
+private val testProductId: UShort = 0xCCDDu
+
+private val testLargeString: String =
+  """
+  START...
+  !123456789ABCDEF@123456789ABCDEF#123456789ABCDEF$123456789ABCDEF
+  %123456789ABCDEF^123456789ABCDEF&123456789ABCDEF*123456789ABCDEF
+  01234567(9ABCDEF01234567)9ABCDEF01234567-9ABCDEF01234567=9ABCDEF
+  01234567[9ABCDEF01234567]9ABCDEF01234567;9ABCDEF01234567'9ABCDEF
+  ...END
+  """
+    .trimIndent()
+    .replace("\n", "")
+
+@RunWith(JUnit4::class)
+class TlvReadWriteTest {
+  private fun String.octetsToByteArray(): ByteArray =
+    replace(" ", "").chunked(2).map { it.toInt(16) and 0xFF }.map { it.toByte() }.toByteArray()
+
+  @Test
+  fun testTlvSampleData_write() {
+    TlvWriter().apply {
+      startStructure(FullyQualifiedTag(6, testVendorId, testProductId, 1u))
+      put(FullyQualifiedTag(6, testVendorId, testProductId, 2u), true)
+      put(ImplicitProfileTag(2, 2u), false)
+      startArray(ContextSpecificTag(0))
+      put(AnonymousTag, 42)
+      put(AnonymousTag, -17)
+      put(AnonymousTag, -170000)
+      put(AnonymousTag, 40000000000UL)
+      startStructure(AnonymousTag)
+      endStructure()
+      startList(AnonymousTag)
+      putNull(FullyQualifiedTag(6, testVendorId, testProductId, 17u))
+      putNull(ImplicitProfileTag(4, 900000u))
+      putNull(AnonymousTag)
+      startStructure(ImplicitProfileTag(4, 4000000000u))
+      put(CommonProfileTag(4, 70000u), testLargeString)
+      endStructure()
+      endList()
+      endArray()
+      put(FullyQualifiedTag(6, testVendorId, testProductId, 5u), "This is a test")
+      put(ImplicitProfileTag(2, 65535u), 17.9f)
+      put(ImplicitProfileTag(4, 65536u), 17.9)
+      endStructure()
+      validateTlv()
+      assertThat(getEncoded()).isEqualTo(testTlvSampleData)
+    }
+  }
+
+  @Test
+  fun testTlvSampleData_read() {
+    TlvReader(testTlvSampleData).apply {
+      enterStructure(FullyQualifiedTag(6, testVendorId, testProductId, 1u))
+      assertThat(getBool(FullyQualifiedTag(6, testVendorId, testProductId, 2u))).isEqualTo(true)
+      assertThat(getBool(ImplicitProfileTag(2, 2u))).isEqualTo(false)
+      enterArray(ContextSpecificTag(0))
+      assertThat(getInt(AnonymousTag)).isEqualTo(42)
+      assertThat(getInt(AnonymousTag)).isEqualTo(-17)
+      assertThat(getInt(AnonymousTag)).isEqualTo(-170000)
+      assertThat(getULong(AnonymousTag)).isEqualTo(40000000000UL)
+      enterStructure(AnonymousTag)
+      exitContainer()
+      enterList(AnonymousTag)
+      getNull(FullyQualifiedTag(6, testVendorId, testProductId, 17u))
+      getNull(ImplicitProfileTag(4, 900000u))
+      getNull(AnonymousTag)
+      enterStructure(ImplicitProfileTag(4, 4000000000u))
+      assertThat(getUtf8String(CommonProfileTag(4, 70000u))).isEqualTo(testLargeString)
+      exitContainer()
+      exitContainer()
+      exitContainer()
+      assertThat(getUtf8String(FullyQualifiedTag(6, testVendorId, testProductId, 5u)))
+        .isEqualTo("This is a test")
+      assertThat(getFloat(ImplicitProfileTag(2, 65535u))).isEqualTo(17.9f)
+      assertThat(getDouble(ImplicitProfileTag(4, 65536u))).isEqualTo(17.9)
+      exitContainer()
+      assertThat(getLengthRead()).isEqualTo(testTlvSampleData.size)
+      assertThat(isEndOfTlv()).isEqualTo(true)
+    }
+  }
+
+  @Test
+  fun testTlvSampleData_read_useSkipElementAndExitContinerInTheMiddle() {
+    TlvReader(testTlvSampleData).apply {
+      enterStructure(FullyQualifiedTag(6, testVendorId, testProductId, 1u))
+      skipElement()
+      assertThat(getBool(ImplicitProfileTag(2, 2u))).isEqualTo(false)
+      enterArray(ContextSpecificTag(0))
+      assertThat(getInt(AnonymousTag)).isEqualTo(42)
+      exitContainer()
+      assertThat(getUtf8String(FullyQualifiedTag(6, testVendorId, testProductId, 5u)))
+        .isEqualTo("This is a test")
+      assertThat(getFloat(ImplicitProfileTag(2, 65535u))).isEqualTo(17.9f)
+      assertThat(getDouble(ImplicitProfileTag(4, 65536u))).isEqualTo(17.9)
+      exitContainer()
+      assertThat(getLengthRead()).isEqualTo(testTlvSampleData.size)
+      assertThat(isEndOfTlv()).isEqualTo(true)
+    }
+  }
+
+  @Test
+  fun testData_IntMinMax() {
+    val encodedTlv =
+      TlvWriter()
+        .apply {
+          put(AnonymousTag, Byte.MIN_VALUE.toByte())
+          put(AnonymousTag, Byte.MAX_VALUE.toByte())
+          put(AnonymousTag, Short.MIN_VALUE.toShort())
+          put(AnonymousTag, Short.MAX_VALUE.toShort())
+          put(AnonymousTag, Int.MIN_VALUE.toInt())
+          put(AnonymousTag, Int.MAX_VALUE.toInt())
+          put(AnonymousTag, Long.MIN_VALUE.toLong())
+          put(AnonymousTag, Long.MAX_VALUE.toLong())
+          put(AnonymousTag, UByte.MAX_VALUE.toUByte())
+          put(AnonymousTag, UShort.MAX_VALUE.toUShort())
+          put(AnonymousTag, UInt.MAX_VALUE.toUInt())
+          put(AnonymousTag, ULong.MAX_VALUE.toULong())
+        }
+        .validateTlv()
+        .getEncoded()
+
+    TlvReader(encodedTlv).apply {
+      assertThat(getByte(AnonymousTag)).isEqualTo(Byte.MIN_VALUE)
+      assertThat(getByte(AnonymousTag)).isEqualTo(Byte.MAX_VALUE)
+      assertThat(getShort(AnonymousTag)).isEqualTo(Short.MIN_VALUE)
+      assertThat(getShort(AnonymousTag)).isEqualTo(Short.MAX_VALUE)
+      assertThat(getInt(AnonymousTag)).isEqualTo(Int.MIN_VALUE)
+      assertThat(getInt(AnonymousTag)).isEqualTo(Int.MAX_VALUE)
+      assertThat(getLong(AnonymousTag)).isEqualTo(Long.MIN_VALUE)
+      assertThat(getLong(AnonymousTag)).isEqualTo(Long.MAX_VALUE)
+      assertThat(getUByte(AnonymousTag)).isEqualTo(UByte.MAX_VALUE)
+      assertThat(getUShort(AnonymousTag)).isEqualTo(UShort.MAX_VALUE)
+      assertThat(getUInt(AnonymousTag)).isEqualTo(UInt.MAX_VALUE)
+      assertThat(getULong(AnonymousTag)).isEqualTo(ULong.MAX_VALUE)
+    }
+  }
+
+  @Test
+  fun encodeBoolean_false() {
+    // Boolean false
+    val value = false
+    val encoding = "08".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      validateTlv()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getBool(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeBoolean_true() {
+    // Boolean true
+    val value = true
+    val encoding = "09".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getBool(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeSignedInt_1BytePositive() {
+    // Signed Integer, 1-octet
+    val value = 42
+    val encoding = "00 2a".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getInt(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeSignedInt_1ByteNegative() {
+    // Signed Integer, 1-octet
+    val value = -17
+    val encoding = "00 ef".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getInt(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeUnsignedInt_1Byte() {
+    // Unsigned Integer, 1-octet
+    val value = 42u
+    val encoding = "04 2a".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getUInt(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeUnsignedInt_1Byte_usePutUnsigned() {
+    // Unsigned Integer, 1-octet
+    val value = 42
+    val encoding = "04 2a".octetsToByteArray()
+
+    TlvWriter().apply {
+      putUnsigned(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+  }
+
+  @Test
+  fun encodeSignedInt_1Byte2octet() {
+    // Signed Integer, 1-byte encoded as 2-octet
+    val value = 42
+    val encoding = "01 2a 00".octetsToByteArray()
+
+    // Note: the current implementation follows the minimum encoding policy, which encodes this
+    // value as 1-octet. Testing only decoding.
+
+    TlvReader(encoding).apply { assertThat(getInt(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeSignedInt_2Bytes() {
+    // Signed Integer, 2-octet
+    val value = 4242
+    val encoding = "01 92 10".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getInt(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeSignedInt_4Bytes() {
+    // Signed Integer, 4-octet
+    val value = -170000
+    val encoding = "02 f0 67 fd ff".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getInt(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeSignedInt_8Bytes() {
+    // Signed Integer (Long), 8-octet
+    val value = 40000000000
+    val encoding = "03 00 90 2f 50 09 00 00 00".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getLong(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeUnsignedInt_8Bytes_usePutUnsigned() {
+    // Unsigned Integer (Long), 8-octet
+    val value = 40000000000
+    val encoding = "07 00 90 2f 50 09 00 00 00".octetsToByteArray()
+
+    TlvWriter().apply {
+      putUnsigned(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getULong(AnonymousTag)).isEqualTo(value.toULong()) }
+  }
+
+  @Test
+  fun encodeSignedInt_8Bytes_useGetULong_throwsIllegalArgumentException() {
+    // Signed Integer (Long), 8-octet
+    val encoding = "03 00 90 2f 50 09 00 00 00".octetsToByteArray()
+
+    // Throws exception because the encoded value is Long and not ULong as requested by getULong()
+    assertFailsWith<IllegalArgumentException> { TlvReader(encoding).getULong(AnonymousTag) }
+  }
+
+  @Test
+  fun encodeSignedInt_8Bytes_useGetInt_throwsTlvParsingException() {
+    // Signed Integer (Long), 8-octet
+    val encoding = "03 00 90 2f 50 09 00 00 00".octetsToByteArray()
+
+    // Throws exception because the encoded value is out of range of Signed Int
+    assertFailsWith<TlvParsingException> { TlvReader(encoding).getInt(AnonymousTag) }
+  }
+
+  @Test
+  fun encodeSignedInt_8Bytes_getFullyQualifiedTag_throwsIllegalArgumentException() {
+    // Signed Integer (Long), 8-octet
+    val encoding = "03 00 90 2f 50 09 00 00 00".octetsToByteArray()
+
+    // Throws exception because the encoded value has AnonymousTag tag
+    assertFailsWith<IllegalArgumentException> {
+      TlvReader(encoding).getLong(FullyQualifiedTag(6, testVendorId, testProductId, 5u))
+    }
+  }
+
+  @Test
+  fun encodeUtf8String_hello() {
+    // UTF-8 String, 1-octet length, "Hello!"
+    val value = "Hello!"
+    val encoding = "0c 06 48 65 6c 6c 6f 21".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getUtf8String(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeUtf8String_tschuh() {
+    // UTF-8 String, 1-octet length, "Tschüs"
+    val value = "Tschüs"
+    val encoding = "0c 07 54 73 63 68 c3 bc 73".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getUtf8String(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeOctetString() {
+    // Octet String, 1-octet length, octets 00 01 02 03 04
+    val value = ByteString.fromHex("00 01 02 03 04".replace(" ", ""))
+    val encoding = "10 05 00 01 02 03 04".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getByteString(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeNull() {
+    // Null
+    val encoding = "14".octetsToByteArray()
+
+    TlvWriter().apply {
+      putNull(AnonymousTag)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { getNull(AnonymousTag) }
+  }
+
+  @Test
+  fun encodeFloat_0() {
+    // Single precision floating point 0.0
+    val value = 0.0f
+    val encoding = "0a 00 00 00 00".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getFloat(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeFloat_1third() {
+    // Single precision floating point (1.0 / 3.0)
+    val value = 1 / 3.toFloat()
+    val encoding = "0a ab aa aa 3e".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getFloat(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeFloat_17_9() {
+    // Single precision floating point 17.9
+    val value = 17.9f
+    val encoding = "0a 33 33 8f 41".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getFloat(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeFloat_positiveInfinity() {
+    // Single precision floating point infinity (∞)
+    val value = Float.POSITIVE_INFINITY
+    val encoding = "0a 00 00 80 7f".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getFloat(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeFloat_negativeInfinity() {
+    // Single precision floating point negative infinity (-∞)
+    val value = Float.NEGATIVE_INFINITY
+    val encoding = "0a 00 00 80 ff".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+  }
+
+  @Test
+  fun encodeDouble_0() {
+    // Double precision floating point 0.0
+    val value = 0.0
+    val encoding = "0b 00 00 00 00 00 00 00 00".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getDouble(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeDouble_1third() {
+    // Double precision floating point (1.0 / 3.0)
+    val value = 1 / 3.toDouble()
+    val encoding = "0b 55 55 55 55 55 55 d5 3f".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getDouble(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeDouble_17_9() {
+    // Double precision floating point 17.9
+    val value = 17.9
+    val encoding = "0b 66 66 66 66 66 e6 31 40".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getDouble(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeDouble_positiveInfinity() {
+    // Double precision floating point infinity (∞)
+    val value = Double.POSITIVE_INFINITY
+    val encoding = "0b 00 00 00 00 00 00 f0 7f".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getDouble(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeDouble_negativeInfinity() {
+    // Double precision floating point negative infinity (-∞)
+    val value = Double.NEGATIVE_INFINITY
+    val encoding = "0b 00 00 00 00 00 00 f0 ff".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(AnonymousTag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getDouble(AnonymousTag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeStructure_empty() {
+    // Empty Structure, {}
+    val encoding = "15 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startStructure(AnonymousTag)
+      endStructure()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterStructure(AnonymousTag)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeStructure_empty_testEndOfContainer() {
+    // Empty Structure, {}
+    val encoding = "15 18".octetsToByteArray()
+
+    TlvReader(encoding).apply {
+      assertThat(isEndOfContainer()).isEqualTo(false)
+      enterStructure(AnonymousTag)
+      assertThat(isEndOfContainer()).isEqualTo(true)
+      exitContainer()
+      assertFailsWith<TlvParsingException> { isEndOfContainer() }
+    }
+  }
+
+  @Test
+  fun encodeStructure_notClosed_throwsTlvEncodingException() {
+    // Open Structure, {
+    TlvWriter().apply {
+      startStructure(AnonymousTag)
+      assertFailsWith<TlvEncodingException> { validateTlv() }
+    }
+  }
+
+  @Test
+  fun encodeArray_empty() {
+    // Empty Array, []
+    val encoding = "16 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startArray(AnonymousTag)
+      endArray()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterArray(AnonymousTag)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeArray_empty_closeUnopennedArray_throwsIllegalArgumentException() {
+    // Empty Array, []]
+    TlvWriter().apply {
+      startArray(AnonymousTag)
+      endArray()
+      // trying to closed container that is not openned
+      assertFailsWith<IllegalArgumentException> { endArray() }
+    }
+  }
+
+  @Test
+  fun encodeList_empty() {
+    // Empty List, []
+    val encoding = "17 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startList(AnonymousTag)
+      endList()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterList(AnonymousTag)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeStructure_intsWithContextTags() {
+    // Structure, two context specific tags, Signed Integer, 1 octet values, {0 = 42, 1 = -17}
+    val value0 = 42
+    val value1 = -17
+    val encoding = "15 20 00 2a 20 01 ef 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startStructure(AnonymousTag)
+      put(ContextSpecificTag(0), value0)
+      put(ContextSpecificTag(1), value1)
+      endStructure()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterStructure(AnonymousTag)
+      assertThat(getByte(ContextSpecificTag(0))).isEqualTo(value0)
+      assertThat(getByte(ContextSpecificTag(1))).isEqualTo(value1)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeArray_ints() {
+    // Array, Signed Integer, 1-octet values, [0, 1, 2, 3, 4]
+    val encoding = "16 00 00 00 01 00 02 00 03 00 04 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startArray(AnonymousTag)
+      put(AnonymousTag, 0)
+      put(AnonymousTag, 1)
+      put(AnonymousTag, 2)
+      put(AnonymousTag, 3)
+      put(AnonymousTag, 4)
+      endArray()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterArray(AnonymousTag)
+      assertThat(getInt(AnonymousTag)).isEqualTo(0)
+      assertThat(getInt(AnonymousTag)).isEqualTo(1)
+      assertThat(getInt(AnonymousTag)).isEqualTo(2)
+      assertThat(getInt(AnonymousTag)).isEqualTo(3)
+      assertThat(getInt(AnonymousTag)).isEqualTo(4)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeList_mixedInts() {
+    // List, mix of anonymous and context tags, Signed Integer, 1 octet values,
+    // [[1, 0 = 42, 2, 3, 0 = -17]]
+    val encoding = "17 00 01 20 00 2a 00 02 00 03 20 00 ef 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startList(AnonymousTag)
+      put(AnonymousTag, 1)
+      put(ContextSpecificTag(0), 42)
+      put(AnonymousTag, 2)
+      put(AnonymousTag, 3)
+      put(ContextSpecificTag(0), -17)
+      endList()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterList(AnonymousTag)
+      assertThat(getInt(AnonymousTag)).isEqualTo(1)
+      assertThat(getInt(ContextSpecificTag(0))).isEqualTo(42)
+      assertThat(getInt(AnonymousTag)).isEqualTo(2)
+      assertThat(getInt(AnonymousTag)).isEqualTo(3)
+      assertThat(getInt(ContextSpecificTag(0))).isEqualTo(-17)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeArray_mixedValues() {
+    // Array, mix of element types, [42, -170000, {}, 17.9, "Hello!"]
+    val encoding =
+      "16 00 2a 02 f0 67 fd ff 15 18 0a 33 33 8f 41 0c 06 48 65 6c 6c 6f 21 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startArray(AnonymousTag)
+      put(AnonymousTag, 42)
+      put(AnonymousTag, -170000)
+      startStructure(AnonymousTag)
+      endStructure()
+      put(AnonymousTag, 17.9f)
+      put(AnonymousTag, "Hello!")
+      endArray()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterArray(AnonymousTag)
+      assertThat(getInt(AnonymousTag)).isEqualTo(42)
+      assertThat(getInt(AnonymousTag)).isEqualTo(-170000)
+      enterStructure(AnonymousTag)
+      exitContainer()
+      assertThat(getFloat(AnonymousTag)).isEqualTo(17.9f)
+      assertThat(getUtf8String(AnonymousTag)).isEqualTo("Hello!")
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeAanonymousTag() {
+    // Anonymous tag, Unsigned Integer, 1-octet value, 42U
+    val value = 42U
+    var tag = AnonymousTag
+    var encoding = "04 2a".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(tag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getUInt(tag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeContextTag_withinStructure() {
+    // Context tag 1, Unsigned Integer, 1-octet value, {1 = 42U}
+    val value = 42U
+    var tag = ContextSpecificTag(1)
+    var encoding = "15 24 01 2a 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startStructure(AnonymousTag)
+      put(tag, value)
+      endStructure()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterStructure(AnonymousTag)
+      assertThat(getUInt(tag)).isEqualTo(value)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeContextTag_invalidContextTag_throwsIllegalArgumentException() {
+    // Context tag 1, Unsigned Integer, 1-octet value, {1 = 42U}
+    val value1 = 42U
+    val value2 = 17000
+    var tag1 = ContextSpecificTag(UByte.MAX_VALUE.toInt())
+    var tag2 = ContextSpecificTag(UByte.MAX_VALUE.toInt() + 1)
+
+    TlvWriter().apply {
+      startStructure(AnonymousTag)
+      put(tag1, value1)
+      // tag2 exeeds valid context specific tag value
+      assertFailsWith<IllegalArgumentException> { put(tag2, value2) }
+    }
+  }
+
+  @Test
+  fun encodeContextTag_withinList() {
+    // Context tag 1, Unsigned Integer, 1-octet value, [[1 = 42U]]
+    val value = 42U
+    var tag = ContextSpecificTag(1)
+    var encoding = "17 24 01 2a 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startList(AnonymousTag)
+      put(tag, value)
+      endList()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterList(AnonymousTag)
+      assertThat(getUInt(tag)).isEqualTo(value)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeContextTag_withinArray_throwsIllegalArgumentException() {
+    // Context tag 1, Unsigned Integer, 1-octet value, [1 = 42U]
+    val value = 42U
+    var tag = ContextSpecificTag(1)
+
+    // Array elements SHALL be of anonumous type
+    TlvWriter().apply {
+      startArray(AnonymousTag)
+      assertFailsWith<IllegalArgumentException> { put(tag, value) }
+    }
+  }
+
+  @Test
+  fun encodeContextTag_notInContainer_throwsIllegalArgumentException() {
+    // Context tag 1, Unsigned Integer, 1-octet value, 1 = 42U
+    val value = 42U
+    var tag = ContextSpecificTag(1)
+
+    // Context tag can only be used within a Structure or a List
+    assertFailsWith<IllegalArgumentException> { TlvWriter().put(tag, value) }
+  }
+
+  @Test
+  fun encodeCommonProfileTag2() {
+    // Common profile tag 1, Unsigned Integer, 1-octet value, Matter::1 = 42U
+    val value = 42U
+    var tag = CommonProfileTag(2, 1u)
+    var encoding = "44 01 00 2a".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(tag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getUInt(tag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeCommonProfileTag4() {
+    // Common profile tag 100000, Unsigned Integer, 1-octet value, Matter::100000 = 42U
+    val value = 42U
+    var tag = CommonProfileTag(4, 100000u)
+    var encoding = "64 a0 86 01 00 2a".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(tag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getUInt(tag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeFullyQualifiedTag6() {
+    // Fully qualified tag, Vendor ID 0xFFF1/65521, profile number 0xDEED/57069, 2-octet tag 1,
+    // Unsigned Integer, 1-octet value 42, 65521::57069:1 = 42U
+    val value = 42U
+    var tag = FullyQualifiedTag(6, 0xFFF1u, 0xDEEDu, 1u)
+    var encoding = "c4 f1 ff ed de 01 00 2a".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(tag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getUInt(tag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeFullyQualifiedTag8() {
+    // Fully qualified tag, Vendor ID 0xFFF1/65521, profile number 0xDEED/57069, 4-octet tag
+    // 0xAA55FEED/2857762541, Unsigned Integer, 1-octet value 42, 65521::57069:2857762541 = 42U
+    val value = 42U
+    var tag = FullyQualifiedTag(8, 0xFFF1u, 0xDEEDu, 0xAA55FEEDu)
+    var encoding = "e4 f1 ff ed de ed fe 55 aa 2a".octetsToByteArray()
+
+    TlvWriter().apply {
+      put(tag, value)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply { assertThat(getUInt(tag)).isEqualTo(value) }
+  }
+
+  @Test
+  fun encodeFullyQualifiedTags_withStructure() {
+    // Structure with the fully qualified tag, Vendor ID 0xFFF1/65521, profile number 0xDEED/57069,
+    // 2-octet tag 1. The structure contains a single element labeled using a fully qualified tag
+    // under the same profile, with 2-octet tag 0xAA55/43605.65521::57069:1 = {65521::57069:43605 =
+    // 42U}
+    val value = 42U
+    val structTag = FullyQualifiedTag(6, 0xFFF1u, 0xDEEDu, 1u)
+    var valueTag = FullyQualifiedTag(6, 0xFFF1u, 57069u, 43605u)
+    var encoding = "d5 f1 ff ed de 01 00 c4 f1 ff ed de 55 aa 2a 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      startStructure(structTag)
+      put(valueTag, value)
+      endStructure()
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterStructure(structTag)
+      assertThat(getUInt(valueTag)).isEqualTo(value)
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun encodeArray_empty_useputSignedLongArray() {
+    // Empty Array, []
+    val values = longArrayOf()
+    val encoding = "16 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      putSignedLongArray(AnonymousTag, values)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+  }
+
+  @Test
+  fun putSignedLongArray() {
+    // Anonumous Array of Signed Integers, [42, -17, -170000, 40000000000]
+    val values = longArrayOf(42, -17, -170000, 40000000000)
+    val encoding = "16 00 2a 00 ef 02 f0 67 fd ff 03 00 90 2f 50 09 00 00 00 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      putSignedLongArray(AnonymousTag, values)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterArray(AnonymousTag)
+      assertThat(getLong(AnonymousTag)).isEqualTo(values[0])
+      assertThat(getLong(AnonymousTag)).isEqualTo(values[1])
+      assertThat(getLong(AnonymousTag)).isEqualTo(values[2])
+      assertThat(getLong(AnonymousTag)).isEqualTo(values[3])
+      exitContainer()
+    }
+  }
+
+  @Test
+  fun putUnsignedLongArray() {
+    // Anonumous Array of Signed Integers, [42, 170000, 40000000000]
+    val values = longArrayOf(42, 170000, 40000000000)
+    val encoding = "16 04 2a 06 10 98 02 00 07 00 90 2f 50 09 00 00 00 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      putUnsignedLongArray(AnonymousTag, values)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterArray(AnonymousTag)
+      assertThat(getULong(AnonymousTag)).isEqualTo(values[0].toULong())
+      assertThat(getULong(AnonymousTag)).isEqualTo(values[1].toULong())
+      assertThat(isEndOfContainer()).isFalse()
+      assertThat(getULong(AnonymousTag)).isEqualTo(values[2].toULong())
+      assertThat(isEndOfContainer()).isTrue()
+      assertThat(isEndOfTlv()).isFalse()
+      exitContainer()
+      assertThat(isEndOfTlv()).isTrue()
+    }
+  }
+
+  @Test
+  fun putByteStringArray() {
+    // Anonumous Array of Signed Integers, [{00 01 02 03 04}, {FF}, {4A EF 88}]
+    val values =
+      listOf<ByteString>(
+        ByteString.fromHex("0001020304"),
+        ByteString.fromHex("FF"),
+        ByteString.fromHex("4AEF88")
+      )
+    val encoding = "16 10 05 00 01 02 03 04 10 01 FF 10 03 4A EF 88 18".octetsToByteArray()
+
+    TlvWriter().apply {
+      putByteStringArray(AnonymousTag, values)
+      assertThat(getEncoded()).isEqualTo(encoding)
+    }
+
+    TlvReader(encoding).apply {
+      enterArray(AnonymousTag)
+      assertThat(getByteString(AnonymousTag)).isEqualTo(values[0])
+      assertThat(getByteString(AnonymousTag)).isEqualTo(values[1])
+      assertThat(getByteString(AnonymousTag)).isEqualTo(values[2])
+      exitContainer()
+    }
+  }
+}
diff --git a/src/controller/java/tests/chip/tlv/TlvReaderTest.kt b/src/controller/java/tests/chip/tlv/TlvReaderTest.kt
new file mode 100644
index 00000000000000..f7a6b4dbbe0b25
--- /dev/null
+++ b/src/controller/java/tests/chip/tlv/TlvReaderTest.kt
@@ -0,0 +1,271 @@
+/*
+ *
+ *    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.tlv
+
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.ByteString
+import java.math.BigInteger
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Assert.assertThrows
+
+// Extracted from a Newman device during a pairing flow. Represents a fabric
+// ID and keys for the fabric 7885a14c693bf1cb.
+private val fabricConfig =
+  """
+  D50000050001002701CBF13B694CA18578360
+  21525010110240201300310149BF1430B26F5
+  E4BBF380D3DB855BA1300414E0E8BAA1CAC6E
+  8E7216D720BD13C61C5E0E7B9012405002406
+  00181818
+  """
+    .trimIndent()
+    .replace("\n", "")
+    .chunked(2)
+    .map { it.toInt(16) and 0xFF }
+    .map { it.toByte() }
+    .toByteArray()
+
+@RunWith(JUnit4::class)
+class TlvReaderTest {
+  @Test
+  fun parsingFabricConfig_extractsFabricId() {
+    val reader = TlvReader(fabricConfig)
+    assertThat(reader.nextElement().value).isInstanceOf(StructureValue::class.java)
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(1)
+      assertThat(value).isInstanceOf(UnsignedIntValue::class.java)
+      assertThat((value as UnsignedIntValue).value)
+        .isEqualTo(BigInteger("7885a14c693bf1cb", 16).toLong())
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(2)
+      assertThat(value).isInstanceOf(ArrayValue::class.java)
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(AnonymousTag::class.java)
+      assertThat(value).isInstanceOf(StructureValue::class.java)
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(1)
+      assertThat(value).isInstanceOf(UnsignedIntValue::class.java)
+      assertThat((value as UnsignedIntValue).value).isEqualTo(0x1001)
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(2)
+      assertThat(value).isInstanceOf(UnsignedIntValue::class.java)
+      assertThat((value as UnsignedIntValue).value).isEqualTo(0x01)
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(3)
+      assertThat(value).isInstanceOf(ByteStringValue::class.java)
+      assertThat((value as ByteStringValue).value)
+        .isEqualTo(ByteString.fromHex("149BF1430B26F5E4BBF380D3DB855BA1"))
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(4)
+      assertThat(value).isInstanceOf(ByteStringValue::class.java)
+      assertThat((value as ByteStringValue).value)
+        .isEqualTo(ByteString.fromHex("E0E8BAA1CAC6E8E7216D720BD13C61C5E0E7B901"))
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(5)
+      assertThat(value).isInstanceOf(UnsignedIntValue::class.java)
+      assertThat((value as UnsignedIntValue).value).isEqualTo(0)
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(6)
+      assertThat(value).isInstanceOf(UnsignedIntValue::class.java)
+      assertThat((value as UnsignedIntValue).value).isEqualTo(0)
+    }
+
+    reader.nextElement().apply { assertThat(value).isInstanceOf(EndOfContainerValue::class.java) }
+
+    reader.nextElement().apply { assertThat(value).isInstanceOf(EndOfContainerValue::class.java) }
+
+    reader.nextElement().apply { assertThat(value).isInstanceOf(EndOfContainerValue::class.java) }
+  }
+
+  @Test
+  fun parsingContextSpecificTagWithUnsignedIntMsbSet_extractsUnsignedInt() {
+    // ContextSpecificTag with UnsignedIntValue set to 0xFFF2(65522)
+    val config = "2500F2FF".chunked(2).map { it.toInt(16).toByte() }.toByteArray()
+    val reader = TlvReader(config)
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+      assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(0)
+      assertThat(value).isInstanceOf(UnsignedIntValue::class.java)
+      assertThat((value as UnsignedIntValue).value).isEqualTo(65522)
+    }
+  }
+
+  @Test
+  fun iterator_overAllElements_returnsExpectedNumberOfElements() {
+    // Fabric configuration contains a total of 13 elements,
+    // including container and end of container element types.
+    assertThat(TlvReader(fabricConfig).count()).isEqualTo(13)
+  }
+
+  @Test
+  fun onReset_readerStartsAtBeginning() {
+    val reader = TlvReader(fabricConfig)
+    val initialElements = reader.take(3)
+
+    reader.reset()
+
+    assertThat(reader.take(3)).isEqualTo(initialElements)
+  }
+
+  @Test
+  fun onInvalidData_throwsTlvParsingException() {
+    val reader = TlvReader(byteArrayOf(0x5))
+    assertThrows(TlvParsingException::class.java) { reader.nextElement() }
+  }
+
+  @Test
+  fun parseByteArray_extractsUtf8String() {
+    // Common-profile 2-bytes Tag with UTF-8 String 1-byte type
+    val control = 0b01001100.toByte()
+    val length = 0b101.toByte()
+    val v = "value".toByteArray(Charsets.UTF_8)
+
+    val reader = TlvReader(byteArrayOf(control, 0x0, 0x0, length, *v))
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(CommonProfileTag::class.java)
+      assertThat((tag as CommonProfileTag).size).isEqualTo(2)
+      assertThat(value).isInstanceOf(Utf8StringValue::class.java)
+      assertThat((value as Utf8StringValue).value).isEqualTo("value")
+    }
+  }
+
+  @Test
+  fun parseByteArray_extractsByteString() {
+    // Common-profile 2-bytes Tag with ByteString 1-byte type
+    val control = 0b01010000.toByte()
+    val length = 0b10000.toByte()
+    val v = ByteString.copyFromUtf8("byte_string_utf8")
+    assertThat(v.toByteArray().size).isEqualTo(16)
+
+    val reader = TlvReader(byteArrayOf(control, 0x0, 0x0, length, *v.toByteArray()))
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(CommonProfileTag::class.java)
+      assertThat((tag as CommonProfileTag).size).isEqualTo(2)
+      assertThat(value).isInstanceOf(ByteStringValue::class.java)
+      assertThat((value as ByteStringValue).value)
+        .isEqualTo(ByteString.copyFromUtf8("byte_string_utf8"))
+    }
+  }
+
+  @Test
+  fun parseByteArray_extractsArray() {
+    // Common-profile 2-bytes Tag with UTF-8 String 1-byte type
+    val stringControl = 0b01001100.toByte()
+    val stringLength = 0b101.toByte()
+    val stringValue = "value".toByteArray(Charsets.UTF_8)
+    val stringBytes = byteArrayOf(stringControl, 0x0, 0x0, stringLength, *stringValue)
+
+    // Anonymous Tag with UTF-8 String 2-byte type (length [0x110, 0x0] in little endian).
+    val stringControl2 = 0b00001101.toByte()
+    val stringLength2 = 0b110.toByte()
+    val stringValue2 = "value2".toByteArray(Charsets.UTF_8)
+    val stringBytes2 = byteArrayOf(stringControl2, stringLength2, 0x0, *stringValue2)
+
+    // Anonymous Tag with UTF-8 String 1-byte type
+    val stringControl3 = 0b00001100.toByte()
+    val stringLength3 = 0b110.toByte()
+    val stringValue3 = "value3".toByteArray(Charsets.UTF_8)
+    val stringBytes3 = byteArrayOf(stringControl3, stringLength3, *stringValue3)
+
+    // Put String2 and String3 into the array.
+    val arrayControl = 0b01010110.toByte()
+    val arrayValue = byteArrayOf(*stringBytes2, *stringBytes3)
+    val arrayBytes = byteArrayOf(arrayControl, 0x0, 0x0, *arrayValue)
+
+    // Common-profile 2-bytes Tag with UTF-8 String 1-byte type
+    val stringControl4 = 0b01001100.toByte()
+    val stringLength4 = 0b110.toByte()
+    val stringValue4 = "value4".toByteArray(Charsets.UTF_8)
+    val stringBytes4 = byteArrayOf(stringControl4, 0x0, 0x0, stringLength4, *stringValue4)
+
+    val reader =
+      TlvReader(byteArrayOf(*stringBytes, *arrayBytes, 0b00011000.toByte(), *stringBytes4))
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(CommonProfileTag::class.java)
+      assertThat((tag as CommonProfileTag).size).isEqualTo(2)
+      assertThat(value).isInstanceOf(Utf8StringValue::class.java)
+      assertThat((value as Utf8StringValue).value).isEqualTo("value")
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(CommonProfileTag::class.java)
+      assertThat((tag as CommonProfileTag).size).isEqualTo(2)
+      assertThat(value).isInstanceOf(ArrayValue::class.java)
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(AnonymousTag::class.java)
+      assertThat(value).isInstanceOf(Utf8StringValue::class.java)
+      assertThat((value as Utf8StringValue).value).isEqualTo("value2")
+    }
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(AnonymousTag::class.java)
+      assertThat(value).isInstanceOf(Utf8StringValue::class.java)
+      assertThat((value as Utf8StringValue).value).isEqualTo("value3")
+    }
+
+    assertThat(reader.nextElement().value).isInstanceOf(EndOfContainerValue::class.java)
+
+    reader.nextElement().apply {
+      assertThat(tag).isInstanceOf(CommonProfileTag::class.java)
+      assertThat((tag as CommonProfileTag).size).isEqualTo(2)
+      assertThat(value).isInstanceOf(Utf8StringValue::class.java)
+      assertThat((value as Utf8StringValue).value).isEqualTo("value4")
+    }
+  }
+
+  @Test
+  fun contextSpecificTag_parsesUnsignedByte() {
+    // Context-Specific tag with tag number 254 (0xFE) and value 2
+    val bytes = byteArrayOf(0x24, 0xFE.toByte(), 0x02)
+    val tlvReader = TlvReader(bytes)
+    val tag = tlvReader.nextElement().tag
+    assertThat(tag).isInstanceOf(ContextSpecificTag::class.java)
+    assertThat((tag as ContextSpecificTag).tagNumber).isEqualTo(0xFE)
+  }
+}
\ No newline at end of file
diff --git a/src/controller/java/tests/chip/tlv/TlvWriterTest.kt b/src/controller/java/tests/chip/tlv/TlvWriterTest.kt
new file mode 100644
index 00000000000000..a45c0bfd093837
--- /dev/null
+++ b/src/controller/java/tests/chip/tlv/TlvWriterTest.kt
@@ -0,0 +1,69 @@
+/*
+ *
+ *    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.tlv
+
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.ByteString
+import java.math.BigInteger
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+// Extracted from a Newman device during a pairing flow. Represents a fabric
+// ID and keys for the fabric 7885a14c693bf1cb.
+private val fabricConfig =
+  """
+  D50000050001002701CBF13B694CA18578360
+  21525010110240201300310149BF1430B26F5
+  E4BBF380D3DB855BA1300414E0E8BAA1CAC6E
+  8E7216D720BD13C61C5E0E7B9012405002406
+  00181818
+  """
+    .trimIndent()
+    .replace("\n", "")
+    .chunked(2)
+    .map { it.toInt(16) and 0xFF }
+    .map { it.toByte() }
+    .toByteArray()
+
+@RunWith(JUnit4::class)
+class TlvWriterTest {
+  @Test
+  fun encodingFabricConfig_allElements() {
+    val encodedTlv =
+      TlvWriter(fabricConfig.size)
+        .startStructure(FullyQualifiedTag(6, 0u, 5u, 1u))
+        .put(ContextSpecificTag(1), BigInteger("7885a14c693bf1cb", 16).toLong().toULong())
+        .startArray(ContextSpecificTag(2))
+        .startStructure(AnonymousTag)
+        .put(ContextSpecificTag(1), 0x1001u)
+        .put(ContextSpecificTag(2), 0x01u)
+        .put(ContextSpecificTag(3), ByteString.fromHex("149BF1430B26F5E4BBF380D3DB855BA1"))
+        .put(ContextSpecificTag(4), ByteString.fromHex("E0E8BAA1CAC6E8E7216D720BD13C61C5E0E7B901"))
+        .put(ContextSpecificTag(5), 0u)
+        .put(ContextSpecificTag(6), 0u)
+        .endStructure()
+        .endArray()
+        .endStructure()
+        .validateTlv()
+        .getEncoded()
+
+    assertThat(encodedTlv).isEqualTo(fabricConfig)
+  }
+}
\ No newline at end of file
diff --git a/third_party/java_deps/BUILD.gn b/third_party/java_deps/BUILD.gn
index 0a821fad9bba90..a5e7c0629d94be 100644
--- a/third_party/java_deps/BUILD.gn
+++ b/third_party/java_deps/BUILD.gn
@@ -29,6 +29,18 @@ java_prebuilt("kotlin-stdlib") {
   jar_path = "artifacts/kotlin-stdlib-1.8.10.jar"
 }
 
+java_prebuilt("kotlin-test") {
+  jar_path = "artifacts/kotlin-test-1.8.10.jar"
+}
+
 java_prebuilt("protobuf-java") {
   jar_path = "artifacts/protobuf-java-3.22.0.jar"
 }
+
+java_prebuilt("truth") {
+  jar_path = "artifacts/truth-1.1.3.jar"
+}
+
+java_prebuilt("junit-4") {
+  jar_path = "artifacts/junit-4.13.2.jar"
+}
diff --git a/third_party/java_deps/set_up_java_deps.sh b/third_party/java_deps/set_up_java_deps.sh
index de2d2c848f2b37..911a53a638708d 100755
--- a/third_party/java_deps/set_up_java_deps.sh
+++ b/third_party/java_deps/set_up_java_deps.sh
@@ -20,4 +20,7 @@ mkdir -p third_party/java_deps/artifacts
 curl --fail --location --silent --show-error https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar -o third_party/java_deps/artifacts/jsr305-3.0.2.jar
 curl --fail --location --silent --show-error https://repo1.maven.org/maven2/org/json/json/20220924/json-20220924.jar -o third_party/java_deps/artifacts/json-20220924.jar
 curl --fail --location --silent --show-error https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar -o third_party/java_deps/artifacts/kotlin-stdlib-1.8.10.jar
+curl --fail --location --silent --show-error https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-test/1.8.10/kotlin-test-1.8.10.jar -o third_party/java_deps/artifacts/kotlin-test-1.8.10.jar
 curl --fail --location --silent --show-error https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.22.0/protobuf-java-3.22.0.jar -o third_party/java_deps/artifacts/protobuf-java-3.22.0.jar
+curl --fail --location --silent --show-error https://repo1.maven.org/maven2/com/google/truth/truth/1.1.3/truth-1.1.3.jar -o third_party/java_deps/artifacts/truth-1.1.3.jar
+curl --fail --location --silent --show-error https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar -o third_party/java_deps/artifacts/junit-4.13.2.jar