| /* |
| * |
| * 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 matter.tlv |
| |
| import com.google.common.truth.Truth.assertThat |
| 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 const val TEST_VENDOR_ID: UShort = 0xAABBu |
| private const val TEST_PRODUCT_ID: 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, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u)) |
| put(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 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, TEST_VENDOR_ID, TEST_PRODUCT_ID, 17u)) |
| putNull(ImplicitProfileTag(4, 900000u)) |
| putNull(AnonymousTag) |
| startStructure(ImplicitProfileTag(4, 4000000000u)) |
| put(CommonProfileTag(4, 70000u), testLargeString) |
| endStructure() |
| endList() |
| endArray() |
| put(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 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, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u)) |
| assertThat(getBool(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 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, TEST_VENDOR_ID, TEST_PRODUCT_ID, 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, TEST_VENDOR_ID, TEST_PRODUCT_ID, 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, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u)) |
| skipElement() |
| assertThat(getBool(ImplicitProfileTag(2, 2u))).isEqualTo(false) |
| enterArray(ContextSpecificTag(0)) |
| assertThat(getInt(AnonymousTag)).isEqualTo(42) |
| exitContainer() |
| assertThat(getUtf8String(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 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_copyElement() { |
| val reader = TlvReader(testTlvSampleData) |
| val encoding = TlvWriter().copyElement(reader).validateTlv().getEncoded() |
| assertThat(encoding).isEqualTo(testTlvSampleData) |
| } |
| |
| @Test |
| fun testTlvSampleData_copyElementWithTag() { |
| val reader = TlvReader(testTlvSampleData) |
| val encoding = |
| TlvWriter() |
| .copyElement(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u), reader) |
| .validateTlv() |
| .getEncoded() |
| assertThat(encoding).isEqualTo(testTlvSampleData) |
| } |
| |
| @Test |
| fun testCopyElement_throwsIllegalArgumentException() { |
| val encoding = |
| TlvWriter().startStructure(AnonymousTag).endStructure().validateTlv().getEncoded() |
| val reader = TlvReader(encoding) |
| reader.skipElement() |
| |
| // Throws exception because the reader is positioned at the end of container element |
| assertFailsWith<IllegalArgumentException> { TlvWriter().copyElement(reader) } |
| } |
| |
| @Test |
| fun testCopyElement_replaceTag() { |
| val tag = CommonProfileTag(2, 1000u) |
| val encoding = |
| TlvWriter().startStructure(AnonymousTag).endStructure().validateTlv().getEncoded() |
| val expectedEncoding = TlvWriter().startStructure(tag).endStructure().validateTlv().getEncoded() |
| |
| assertThat(TlvWriter().copyElement(tag, TlvReader(encoding)).validateTlv().getEncoded()) |
| .isEqualTo(expectedEncoding) |
| } |
| |
| @Test |
| fun testCopyElementUInt_replaceTag() { |
| val value = 42U |
| val tag1 = CommonProfileTag(2, 1u) |
| val tag2 = CommonProfileTag(2, 2u) |
| val encoding = TlvWriter().put(tag1, value).validateTlv().getEncoded() |
| val expectedEncoding = TlvWriter().put(tag2, value).validateTlv().getEncoded() |
| |
| assertThat(TlvWriter().copyElement(tag2, TlvReader(encoding)).validateTlv().getEncoded()) |
| .isEqualTo(expectedEncoding) |
| } |
| |
| @Test |
| fun testTlvSampleData_copyElementsOneByOne() { |
| val reader = TlvReader(testTlvSampleData) |
| reader.skipElement() |
| val encoding = |
| TlvWriter() |
| .startStructure(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u)) |
| .copyElement(reader) |
| .copyElement(reader) |
| .copyElement(reader) |
| .copyElement(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 5u), reader) |
| .copyElement(reader) |
| .copyElement(reader) |
| .endStructure() |
| .validateTlv() |
| .getEncoded() |
| assertThat(encoding).isEqualTo(testTlvSampleData) |
| } |
| |
| @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, TEST_VENDOR_ID, TEST_PRODUCT_ID, 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 = "00 01 02 03 04".octetsToByteArray() |
| 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 encodeAnonymousTagInStructure_throwsIllegalArgumentException() { |
| // Anonymous tag 1, Unsigned Integer, 1-octet value, {1 = 42U} |
| TlvWriter().apply { |
| startStructure(AnonymousTag) |
| // anonymous tags are not allowed within structure elements |
| assertFailsWith<IllegalArgumentException> { put(AnonymousTag, 42U) } |
| } |
| } |
| |
| @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 anonymous 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() { |
| // Anonymous 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() { |
| // Anonymous 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<ByteArray>( |
| "0001020304".octetsToByteArray(), |
| "FF".octetsToByteArray(), |
| "4AEF88".octetsToByteArray() |
| ) |
| 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() |
| } |
| } |
| } |