blob: fa7a23d6e66ecfde8d7f57fb338faee919a4cf8b [file] [log] [blame]
/*
*
* 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)
}
}