| /* |
| * |
| * 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 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 { |
| private fun String.octetsToByteArray(): ByteArray = |
| replace(" ", "").chunked(2).map { it.toInt(16) and 0xFF }.map { it.toByte() }.toByteArray() |
| |
| @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("149BF1430B26F5E4BBF380D3DB855BA1".octetsToByteArray()) |
| } |
| |
| 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("E0E8BAA1CAC6E8E7216D720BD13C61C5E0E7B901".octetsToByteArray()) |
| } |
| |
| 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 = "byte_string_utf8".toByteArray() |
| assertThat(v.size).isEqualTo(16) |
| |
| 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(ByteStringValue::class.java) |
| assertThat((value as ByteStringValue).value) |
| .isEqualTo("byte_string_utf8".toByteArray()) |
| } |
| } |
| |
| @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) |
| } |
| } |