blob: cfb9d98c4299c9c627fd451ce60bef15bad754cf [file] [log] [blame]
/*
*
* Copyright (c) 2023 Project CHIP Authors
* Copyright (c) 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.jsontlv
import com.google.common.truth.Truth.assertThat
import com.google.gson.JsonParser
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import chip.tlv.*
import chip.jsontlv.fromJsonString
import chip.jsontlv.toJsonString
@RunWith(JUnit4::class)
class JsonToTlvToJsonTest {
private fun String.octetsToByteArray(): ByteArray =
replace(" ", "").chunked(2).map { it.toInt(16) and 0xFF }.map { it.toByte() }.toByteArray()
private fun checkValidConversion(
jsonOriginal: String,
tlvEncoding: ByteArray,
jsonExpected: String = jsonOriginal
) {
assertThat(TlvWriter().fromJsonString(jsonOriginal)).isEqualTo(tlvEncoding)
assertThat(TlvReader(tlvEncoding).toJsonString())
.isEqualTo(JsonParser.parseString(jsonExpected).asJsonObject.toString())
if (jsonOriginal != jsonExpected) {
assertThat(TlvWriter().fromJsonString(jsonExpected)).isEqualTo(tlvEncoding)
}
}
@Test
fun convertBoolean_false() {
// Boolean false
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(0), false)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"0:BOOL" : false
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertBoolean_true() {
// Boolean true
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), true)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:BOOL" : true
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertSignedInt_1BytePositive() {
// Signed Integer 42, 1-octet
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(2), 42)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"2:INT" : 42
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertSignedInt_1ByteNegative() {
// Signed Integer -17, 1-octet
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(3), -17)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"3:INT" : -17
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertUnsignedInt_1Byte() {
// Unsigned Integer 42, 1-octet
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(4), 42U)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"value:4:UINT" : 42
}
"""
val expectedJson = """
{
"4:UINT" : 42
}
"""
checkValidConversion(json, encoding, expectedJson)
}
@Test
fun convertSignedInt_1Byte2octet() {
// Signed Integer 42, 1-byte encoded as 2-octet
val encoding = "15 21 06 2a 00 18".octetsToByteArray()
val expectedJson = """
{
"6:INT" : 42
}
"""
// Note: the current implementation follows the minimum encoding policy, which encodes this
// value as 1-octet. Testing only decoding.
assertThat(TlvReader(encoding).toJsonString())
.isEqualTo(JsonParser.parseString(expectedJson).asJsonObject.toString())
}
@Test
fun convertSignedInt_2Bytes() {
// Signed Integer 4242, 2-octet
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(7), 4242)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"7:INT" : 4242
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertSignedInt_4Bytes() {
// Signed Integer -170000, 4-octet
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(80), -170000)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"80:INT" : -170000
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertSignedInt_8Bytes() {
// Signed Integer (Long) 40000000000, 8-octet
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(202), 40000000000)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"202:INT" : "40000000000"
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertUnsignedInt_8Bytes() {
// Unsigned Integer (Long) 40000000000, 8-octet
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(222), 40000000000U)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"222:UINT" : "40000000000"
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertUtf8String_hello() {
// UTF-8 String, 1-octet length, "Hello!"
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(0), "Hello!")
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"0:STRING" : "Hello!"
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertUtf8String_tschuh() {
// UTF-8 String, 1-octet length, "Tschüs"
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(250), "Tschüs")
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"250:STRING" : "Tschüs"
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertOctetString() {
// Octet String, 1-octet length, octets 00 01 02 03 04
val value = "0001020304".octetsToByteArray()
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), value)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:BYTES" : "AAECAwQ="
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertNull() {
// Null
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.putNull(ContextSpecificTag(1))
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:NULL" : null
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertFloat_0() {
// Single precision floating point 0.0
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), 0.0f)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:FLOAT" : 0.0
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertFloat_1third() {
// Single precision floating point (1.0 / 3.0)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(100), 1.0f / 3.0f)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"100:FLOAT" : 0.33333334
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertFloat_17_9() {
// Single precision floating point 17.9
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(101), 17.9f)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"101:FLOAT" : 17.9
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertFloat_positiveInfinity_throwsIllegalArgumentException() {
// Single precision floating point infinity (∞)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), Float.POSITIVE_INFINITY)
.endStructure()
.validateTlv()
.getEncoded()
// Throws exception because the encoded value is unsupported Infinity value
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertFloat_negativeInfinity_throwsIllegalArgumentException() {
// Single precision floating point negative infinity (-∞)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), Float.NEGATIVE_INFINITY)
.endStructure()
.validateTlv()
.getEncoded()
// Throws exception because the encoded value is unsupported Infinity value
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertDouble_0() {
// Double precision floating point 0.0
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), 0.0)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:DOUBLE" : 0.0
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertDouble_1third() {
// Double precision floating point (1.0 / 3.0)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), 1.0 / 3.0)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:DOUBLE" : 0.3333333333333333
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertDouble_17_9() {
// Double precision floating point 17.9
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), 17.9)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:DOUBLE" : 17.9
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertDouble_positiveInfinity_throwsIllegalArgumentException() {
// Double precision floating point infinity (∞)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), Double.POSITIVE_INFINITY)
.endStructure()
.validateTlv()
.getEncoded()
// Throws exception because the encoded value is unsupported Infinity value
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertDouble_negativeInfinity_throwsIllegalArgumentException() {
// Double precision floating point negative infinity (-∞)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(1), Double.NEGATIVE_INFINITY)
.endStructure()
.validateTlv()
.getEncoded()
// Throws exception because the encoded value is unsupported Infinity value
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertEmptyStructure() {
// Empty Structure, {}
val encoding =
TlvWriter().startStructure(AnonymousTag).endStructure().validateTlv().getEncoded()
val json = """
{
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertEmptyStructureWithinStructure() {
// Empty Structure, {}
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startStructure(ContextSpecificTag(1))
.endStructure()
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:STRUCT" : {}
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertEmptyStructureWithCommonProfileTag_throwsIllegalArgumentException() {
// Empty Structure, {}
val encoding =
TlvWriter()
.startStructure(CommonProfileTag(2, 1000u))
.endStructure()
.validateTlv()
.getEncoded()
// Throws exception because top level element must be anonymous Structure
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertTopLevelArray_throwsIllegalArgumentException() {
// Empty Array, []
val tag = AnonymousTag
val encoding = TlvWriter().startArray(tag).endArray().validateTlv().getEncoded()
// Throws exception because top level element must be anonymous Structure
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertTopLevelInt_throwsIllegalArgumentException() {
// Empty Array, []
val value = 42
val tag = CommonProfileTag(2, 1000u)
val encoding = TlvWriter().put(tag, value).validateTlv().getEncoded()
// Throws exception because top level element must be anonymous Structure
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertArray_empty() {
// Empty Array, []
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(ContextSpecificTag(1))
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1:ARRAY-?" : []
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArrayWithCommonProfileTag2_empty() {
// Empty Array, []
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(CommonProfileTag(2, 10000u))
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"value:10000:ARRAY-?" : []
}
"""
val expectedJson = """
{
"10000:ARRAY-?" : []
}
"""
checkValidConversion(json, encoding, expectedJson)
}
@Test
fun convertArrayWithCommonProfileTag4_empty() {
// Empty Array, []
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(CommonProfileTag(4, 1000000u))
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1000000:ARRAY-?" : []
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertList_empty_throwsIllegalArgumentException() {
// Empty List, []
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startList(ContextSpecificTag(1))
.endList()
.endStructure()
.validateTlv()
.getEncoded()
// Throws exception because TLV Lists are not supported
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertIntegersWithContextTags() {
// Structure, two context specific tags, Signed Integer, 1 octet values, {0 = 42, 1 = -17}
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(0), 42)
.put(ContextSpecificTag(1), -17)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"0:INT" : 42,
"1:INT" : -17
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertStructure_intsWithContextTags() {
// Structure, two context specific tags, Signed Integer, 1 octet values, {0 = 42, 1 = -17}
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startStructure(ContextSpecificTag(0))
.put(ContextSpecificTag(0), 42)
.put(ContextSpecificTag(1), -17)
.endStructure()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:STRUCT" : {
"0:INT" : 42,
"1:INT" : -17
}
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArray_ints() {
// Array of Signed Integers (1-octet values): [0, 1, 2, 3, 4]
val values = longArrayOf(0, 1, 2, 3, 4)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.putSignedLongArray(ContextSpecificTag(0), values)
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-INT" : [
0,
1,
2,
3,
4
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArray_double() {
// Array of Doubles: [1.1, 134.2763, -12345.87]
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(ContextSpecificTag(0))
.put(AnonymousTag, 1.1)
.put(AnonymousTag, 134.2763)
.put(AnonymousTag, -12345.87)
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-DOUBLE" : [
1.1,
134.2763,
-12345.87
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArray_float() {
// Array of Floats: [1.1, 134.2763, -12345.87]
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(CommonProfileTag(2, 1000u))
.put(AnonymousTag, 1.1f)
.put(AnonymousTag, 134.2763f)
.put(AnonymousTag, -12345.87f)
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"1000:ARRAY-FLOAT" : [
1.1,
134.2763,
-12345.87
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArray_string() {
// Array of Strings: ["ABC", "Options", "more"]
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(CommonProfileTag(4, 100000u))
.put(AnonymousTag, "ABC")
.put(AnonymousTag, "Options")
.put(AnonymousTag, "more")
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"100000:ARRAY-STRING" : [
"ABC",
"Options",
"more"
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArray_boolean() {
// Array of Booleans: [true, false, false]
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(ContextSpecificTag(255))
.put(AnonymousTag, true)
.put(AnonymousTag, false)
.put(AnonymousTag, false)
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"255:ARRAY-BOOL" : [
true,
false,
false
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArray_null() {
// Array of Nulls: [null, null]
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(ContextSpecificTag(1))
.putNull(AnonymousTag)
.putNull(AnonymousTag)
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"1:ARRAY-NULL" : [
null,
null
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArray_boolean_throwsIllegalArgumentException() {
// Array of bools: error type doesn't match
val json =
"""
{
"value:1:ARRAY-BOOL" : [
"yes",
"no"
]
}
"""
// Throws exception because subtype encoded in the Json key (Boolean) doesn't match the String
// type of the elements in the array
assertFailsWith<IllegalArgumentException> { TlvWriter().fromJsonString(json) }
}
@Test
fun convertArray_uint_throwsIllegalArgumentException() {
// Array of unsigned integers: error type doesn't match
val json =
"""
{
"value:1:ARRAY-UINT" : [
"yes",
"no"
]
}
"""
// Throws exception because subtype encoded in the Json key (Boolean) doesn't match the String
// type of the elements in the array
assertFailsWith<IllegalArgumentException> { TlvWriter().fromJsonString(json) }
}
@Test
fun convertArray_float_throwsIllegalArgumentException() {
// Array of floats: error type doesn't match
val json =
"""
{
"1:ARRAY-FLOAT" : [
{
"1" : 22,
"2" : 23
}
]
}
"""
// Throws exception because subtype encoded in the Json key (Float) doesn't match the Structure
// type of the elements in the array
assertFailsWith<IllegalArgumentException> { TlvWriter().fromJsonString(json) }
}
@Test
fun convertArray_uint2_throwsIllegalArgumentException() {
// Array of unsigned integer: error type doesn't match
val json = """
{
"2:ARRAY-UINT" : [
null
]
}
"""
// Throws exception because subtype encoded in the Json key (UInt) doesn't match the Null
// type of the elements in the array
assertFailsWith<IllegalArgumentException> { TlvWriter().fromJsonString(json) }
}
@Test
fun convertByteStringArray_throwsIllegalArgumentException() {
// Anonymous Array of ByteString, [{00 01 02 03 04}, {FF}, {4A EF 88}]
val json =
"""
{
"value:ARRAY-BYTES": [
"AA45ECAwQ="
]
}
"""
// Throws exception because string is invalid base64 encoded value
assertFailsWith<IllegalArgumentException> { TlvWriter().fromJsonString(json) }
}
@Test
fun convertArray_mixedValues_throwsIllegalArgumentException() {
// Array of mixed elements: [42, -170000, {}, 17.9, "Hello!"]
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(ContextSpecificTag(0))
.put(AnonymousTag, 42)
.put(AnonymousTag, -170000)
.startStructure(AnonymousTag)
.endStructure()
.put(AnonymousTag, 17.9)
.put(AnonymousTag, "Hello!")
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
// Throws exception because TLV Array Must have same type elements
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertAnonymousTag_throwsIllegalArgumentException() {
// Anonymous tag, Unsigned Integer, 1-octet value, 42U
val json = """
{
"value:UINT" : 42
}
"""
// Throws exception because element within structure cannot have anonymous tag
assertFailsWith<IllegalArgumentException> { TlvWriter().fromJsonString(json) }
}
@Test
fun convertKeyWithoutTag_throwsIllegalArgumentException2() {
// Anonymous tag, Unsigned Integer, 1-octet value, 42U
val json = """
{
"UINT" : 42
}
"""
// Throws exception because Json key must have valid tag field
assertFailsWith<IllegalArgumentException> { TlvWriter().fromJsonString(json) }
}
@Test
fun convertContextTag_withinStructure() {
// Context tag 255 (max), Unsigned Integer, 1-octet value: {255 = 42U}
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startStructure(ContextSpecificTag(0))
.put(ContextSpecificTag(255), 42U)
.endStructure()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"value:0:STRUCT": {
"name:255:UINT" : 42
}
}
"""
val expectedJson =
"""
{
"0:STRUCT": {
"255:UINT" : 42
}
}
"""
checkValidConversion(json, encoding, expectedJson)
}
@Test
fun convertStructWithMixedTags() {
// Context and Common Profile tags, Unsigned Integer structure: {255 = 42, 256 = 17000, 65535 =
// 1, 65536 = 345678, 4294967295 = 500000000000}
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startStructure(ContextSpecificTag(0))
.put(ContextSpecificTag(UByte.MAX_VALUE.toInt()), 42U)
.put(CommonProfileTag(2, UByte.MAX_VALUE + 1U), 17000U)
.put(CommonProfileTag(2, UShort.MAX_VALUE.toUInt()), 1U)
.put(CommonProfileTag(4, UShort.MAX_VALUE + 1U), 345678U)
.put(CommonProfileTag(4, UInt.MAX_VALUE), 500000000000U)
.endStructure()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:STRUCT": {
"255:UINT" : 42,
"256:UINT" : 17000,
"65535:UINT" : 1,
"65536:UINT" : 345678,
"4294967295:UINT" : "500000000000"
}
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertContextTag_invalidContextTag_throwsIllegalArgumentException() {
// Context and Common Profile tags, Unsigned Integer structure: {255 = 42, 256 = 17000, 65535 =
// 1, 65536 = 345678, 4294967295 = 500000000000, , 4294967296 = 34}
val json =
"""
{
"0:STRUCT": {
"255:UINT" : 42,
"256:UINT" : 17000,
"65535:UINT" : 1,
"65536:UINT" : 345678,
"4294967295:UINT" : "500000000000",
"invalid:4294967296:UINT" : 34
}
}
"""
// 4294967296 exceeds valid context specific or common profile tag value of 32-bits
assertFailsWith<IllegalArgumentException> { TlvWriter().fromJsonString(json) }
}
@Test
fun convertCommonProfileTag2() {
// Common profile tag 1, Unsigned Integer, 1-octet value, Matter::1 = 42U
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(CommonProfileTag(2, 1000u), 42U)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"1000:UINT" : 42
}
"""
checkValidConversion(json, encoding)
}
@Test
fun encodeCommonProfileTag4() {
// Common profile tag 100000, Unsigned Integer, 1-octet value, Matter::100000 = 42U
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(CommonProfileTag(4, 100000u), 42U)
.endStructure()
.validateTlv()
.getEncoded()
val json = """
{
"100000:UINT" : 42
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertFullyQualifiedTag8_throwsIllegalArgumentException() {
// 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 encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(FullyQualifiedTag(8, 0xFFF1u, 0xDEEDu, 0xAA55FEEDu), 42U)
.endStructure()
.validateTlv()
.getEncoded()
// Fully qualified tags are not supported
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertFullyQualifiedTags_throwsIllegalArgumentException() {
// 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)
val valueTag = FullyQualifiedTag(6, 0xFFF1u, 57069u, 43605u)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startStructure(structTag)
.put(valueTag, value)
.endStructure()
.endStructure()
.validateTlv()
.getEncoded()
// Fully qualified tags are not supported
assertFailsWith<IllegalArgumentException> { TlvReader(encoding).toJsonString() }
}
@Test
fun convertSignedLongArray() {
// Anonymous Array of Signed Integers: [42, -17, -170000, 40000000000]
val values = longArrayOf(42, -17, -170000, 40000000000)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.putSignedLongArray(ContextSpecificTag(0), values)
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-INT": [
42,
-17,
-170000,
"40000000000"
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertUnsignedLongArray() {
// Anonymous Array of Unigned Integers: [42, 170000, 40000000000]
val values = longArrayOf(42, 170000, 40000000000)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.putUnsignedLongArray(ContextSpecificTag(0), values)
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-UINT": [
42,
170000,
"40000000000"
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertByteStringArray() {
// Anonymous Array of ByteArray, [{00 01 02 03 04}, {FF}, {4A EF 88}]
val values =
listOf<ByteArray>(
"0001020304".octetsToByteArray(),
"FF".octetsToByteArray(),
"4AEF88".octetsToByteArray()
)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.putByteStringArray(ContextSpecificTag(0), values)
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-BYTES": [
"AAECAwQ=",
"/w==",
"Su+I"
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertStructure_Mixed() {
// Structure with mixed elements
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startStructure(ContextSpecificTag(0))
.put(ContextSpecificTag(0), 20.toLong())
.put(ContextSpecificTag(1), true)
.put(ContextSpecificTag(2), 0.toULong())
.put(ContextSpecificTag(3), "Test ByteString Value".toByteArray())
.put(ContextSpecificTag(4), "hello")
.put(ContextSpecificTag(5), -500000)
.put(ContextSpecificTag(6), 17.9)
.put(ContextSpecificTag(7), 17.9f)
.endStructure()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:STRUCT": {
"0:INT": 20,
"1:BOOL": true,
"2:UINT": 0,
"3:BYTES": "VGVzdCBCeXRlU3RyaW5nIFZhbHVl",
"4:STRING": "hello",
"5:INT": -500000,
"6:DOUBLE": 17.9,
"7:FLOAT": 17.9
}
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArrayofStructureWithMixedElements() {
// Array of structures with mixed elements
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(CommonProfileTag(2, 1000U))
.startStructure(AnonymousTag)
.put(ContextSpecificTag(0), 20)
.put(ContextSpecificTag(1), true)
.put(ContextSpecificTag(2), 0.toULong())
.put(ContextSpecificTag(3), "Test ByteString Value 1".toByteArray())
.put(ContextSpecificTag(4), "hello1")
.put(ContextSpecificTag(5), -500000)
.put(ContextSpecificTag(6), 17.9)
.put(ContextSpecificTag(7), 17.9f)
.endStructure()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(0), -10)
.put(ContextSpecificTag(1), false)
.put(ContextSpecificTag(2), 128.toULong())
.put(ContextSpecificTag(3), "Test ByteString Value 2".toByteArray())
.put(ContextSpecificTag(4), "hello2")
.put(ContextSpecificTag(5), 40000000000)
.put(ContextSpecificTag(6), -1754.923)
.put(ContextSpecificTag(7), 97.945f)
.endStructure()
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"1000:ARRAY-STRUCT": [
{
"0:INT": 20,
"1:BOOL": true,
"2:UINT": 0,
"3:BYTES": "VGVzdCBCeXRlU3RyaW5nIFZhbHVlIDE=",
"4:STRING": "hello1",
"5:INT": -500000,
"6:DOUBLE": 17.9,
"7:FLOAT": 17.9
},
{
"0:INT": -10,
"1:BOOL": false,
"2:UINT": 128,
"3:BYTES": "VGVzdCBCeXRlU3RyaW5nIFZhbHVlIDI=",
"4:STRING": "hello2",
"5:INT": "40000000000",
"6:DOUBLE": -1754.923,
"7:FLOAT": 97.945
}
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertingStructureWithMixedElementsAndNames() {
// Array of structures with mixed elements
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startStructure(CommonProfileTag(4, 444444U))
.startStructure(ContextSpecificTag(0))
.put(ContextSpecificTag(0), "Test String Element #0")
.put(ContextSpecificTag(1), 1)
.put(ContextSpecificTag(2), true)
.endStructure()
.put(ContextSpecificTag(1), 17.4f)
.startStructure(ContextSpecificTag(2))
.put(ContextSpecificTag(0), "Test String Element #2")
.put(ContextSpecificTag(3), "Test ByteString Element #3".toByteArray())
.endStructure()
.endStructure()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"value_name:444444:STRUCT": {
"0:STRUCT": {
"name0:0:STRING": "Test String Element #0",
"name1:1:INT": 1,
"name2:2:BOOL": true
},
"name2:1:FLOAT": 17.4,
"2:STRUCT": {
"name0:0:STRING": "Test String Element #2",
"3:BYTES": "VGVzdCBCeXRlU3RyaW5nIEVsZW1lbnQgIzM="
}
}
}
"""
val expectedJson =
"""
{
"444444:STRUCT": {
"0:STRUCT": {
"0:STRING": "Test String Element #0",
"1:INT": 1,
"2:BOOL": true
},
"1:FLOAT": 17.4,
"2:STRUCT": {
"0:STRING": "Test String Element #2",
"3:BYTES": "VGVzdCBCeXRlU3RyaW5nIEVsZW1lbnQgIzM="
}
}
}
"""
checkValidConversion(json, encoding, expectedJson)
}
@Test
fun convertArrayOfStrings() {
// Array of String elements
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(ContextSpecificTag(0))
.put(AnonymousTag, "Test array member 0")
.put(AnonymousTag, "Test array member 1")
.put(AnonymousTag, "Test array member 2")
.put(AnonymousTag, "Test array member 3")
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-STRING": [
"Test array member 0",
"Test array member 1",
"Test array member 2",
"Test array member 3"
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArrayOfByteStrings() {
// Array of ByteArray elements
val values =
listOf<ByteArray>(
"Test array member 0".toByteArray(),
"Test array member 1".toByteArray(),
"Test array member 2".toByteArray(),
"Test array member 3".toByteArray(),
)
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.putByteStringArray(ContextSpecificTag(0), values)
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-BYTES": [
"VGVzdCBhcnJheSBtZW1iZXIgMA==",
"VGVzdCBhcnJheSBtZW1iZXIgMQ==",
"VGVzdCBhcnJheSBtZW1iZXIgMg==",
"VGVzdCBhcnJheSBtZW1iZXIgMw=="
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertTopLevelMixedValues() {
// Top level elements with mixed values
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.put(ContextSpecificTag(0), 42)
.put(ContextSpecificTag(1), "Test array member 0".toByteArray())
.put(ContextSpecificTag(2), 156.398)
.put(ContextSpecificTag(3), 73709551615U)
.put(ContextSpecificTag(4), true)
.putNull(ContextSpecificTag(5))
.startStructure(ContextSpecificTag(6))
.put(ContextSpecificTag(1), "John")
.put(ContextSpecificTag(2), 34U)
.put(ContextSpecificTag(3), true)
.startArray(ContextSpecificTag(4))
.put(AnonymousTag, 5)
.put(AnonymousTag, 9)
.put(AnonymousTag, 10)
.endArray()
.startArray(ContextSpecificTag(5))
.put(AnonymousTag, "Ammy")
.put(AnonymousTag, "David")
.put(AnonymousTag, "Larry")
.endArray()
.startArray(ContextSpecificTag(6))
.put(AnonymousTag, true)
.put(AnonymousTag, false)
.put(AnonymousTag, true)
.endArray()
.endStructure()
.put(ContextSpecificTag(7), 0.0f)
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"value:0:INT": 42,
"value:1:BYTES": "VGVzdCBhcnJheSBtZW1iZXIgMA==",
"value:2:DOUBLE": 156.398,
"value:3:UINT": "73709551615",
"value:4:BOOL": true,
"value:5:NULL": null,
"value:6:STRUCT": {
"name:1:STRING": "John",
"age:2:UINT": 34,
"approved:3:BOOL": true,
"kids:4:ARRAY-INT": [
5,
9,
10
],
"names:5:ARRAY-STRING": [
"Ammy",
"David",
"Larry"
],
"6:ARRAY-BOOL": [
true,
false,
true
]
},
"value:7:FLOAT": 0.0
}
"""
val expectedJson =
"""
{
"0:INT": 42,
"1:BYTES": "VGVzdCBhcnJheSBtZW1iZXIgMA==",
"2:DOUBLE": 156.398,
"3:UINT": "73709551615",
"4:BOOL": true,
"5:NULL": null,
"6:STRUCT": {
"1:STRING": "John",
"2:UINT": 34,
"3:BOOL": true,
"4:ARRAY-INT": [
5,
9,
10
],
"5:ARRAY-STRING": [
"Ammy",
"David",
"Larry"
],
"6:ARRAY-BOOL": [
true,
false,
true
]
},
"7:FLOAT": 0.0
}
"""
checkValidConversion(json, encoding, expectedJson)
}
@Test
fun convertArray_UIntMinMax() {
// Array of Unsigned Integers, where each element represents MAX possible value for unsigned
// integere
// types UByte, UShort, UInt, and ULong: [0xFF, 0xFFFF, 0xFFFFFFFF, 0xFFFFFFFF_FFFFFFFF]
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(ContextSpecificTag(0))
.put(AnonymousTag, UByte.MAX_VALUE)
.put(AnonymousTag, UShort.MAX_VALUE)
.put(AnonymousTag, UInt.MAX_VALUE)
.put(AnonymousTag, ULong.MAX_VALUE)
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-UINT": [
255,
65535,
4294967295,
"18446744073709551615"
]
}
"""
checkValidConversion(json, encoding)
}
@Test
fun convertArray_IntMinMax() {
// Array of Integers, where each element represents MIN or MAX possible value for integere
// types Byte, Short, Int, and Long
val encoding =
TlvWriter()
.startStructure(AnonymousTag)
.startArray(ContextSpecificTag(0))
.put(AnonymousTag, Byte.MIN_VALUE)
.put(AnonymousTag, Byte.MAX_VALUE)
.put(AnonymousTag, Short.MIN_VALUE)
.put(AnonymousTag, Short.MAX_VALUE)
.put(AnonymousTag, Int.MIN_VALUE)
.put(AnonymousTag, Int.MAX_VALUE)
.put(AnonymousTag, Long.MIN_VALUE)
.put(AnonymousTag, Long.MAX_VALUE)
.endArray()
.endStructure()
.validateTlv()
.getEncoded()
val json =
"""
{
"0:ARRAY-INT": [
-128,
127,
-32768,
32767,
-2147483648,
2147483647,
"-9223372036854775808",
"9223372036854775807"
]
}
"""
checkValidConversion(json, encoding)
}
}