Skip to content

Commit 011c885

Browse files
emargolispull[bot]
authored andcommitted
Implement copyElement Method for the TlvWriter Class in Kotlin (#26377)
- Added helper method peekElement() to the TlvReader class - Added new method and new testcases. - Some other minor fixes and cleanups.
1 parent c8fa08a commit 011c885

File tree

3 files changed

+172
-22
lines changed

3 files changed

+172
-22
lines changed

src/controller/java/src/chip/tlv/TlvReader.kt

+13
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,19 @@ class TlvReader(bytes: ByteArray) : Iterable<Element> {
101101
return Element(tag, value)
102102
}
103103

104+
/**
105+
* Reads the next element from the TLV. Unlike nextElement() this method leaves the TLV reader
106+
* positioned at the same element and doesn't advance it to the next element.
107+
*
108+
* @throws TlvParsingException if the TLV data was invalid
109+
*/
110+
fun peekElement(): Element {
111+
val currentIndex = index
112+
val element = nextElement()
113+
index = currentIndex
114+
return element
115+
}
116+
104117
/**
105118
* Reads the encoded Long value and advances to the next element.
106119
*

src/controller/java/src/chip/tlv/TlvWriter.kt

+62-6
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ class TlvWriter(initialCapacity: Int = 32) {
4747
"Invalid use of context tag at index ${bytes.size()}: can only be used within a " +
4848
"structure or a list"
4949
}
50-
}
51-
52-
if (containerDepth > 0 && containerType[containerDepth - 1] is ArrayType) {
50+
} else if (containerType[containerDepth - 1] is ArrayType) {
5351
require(tag is AnonymousTag) {
5452
"Invalid element tag at index ${bytes.size()}: elements of an array SHALL be anonymous"
5553
}
54+
} else if (containerType[containerDepth - 1] is StructureType && type !is EndOfContainerType) {
55+
require(tag !is AnonymousTag) {
56+
"Invalid element tag at index ${bytes.size()}: elements of a structure cannot be anonymous"
57+
}
5658
}
5759

5860
if (tag is ContextSpecificTag) {
@@ -326,6 +328,62 @@ class TlvWriter(initialCapacity: Int = 32) {
326328
return put(Element(AnonymousTag, EndOfContainerValue))
327329
}
328330

331+
/**
332+
* Copies a TLV element from a reader object into the writer.
333+
*
334+
* This method encodes a new TLV element whose type, tag and value are taken from a TlvReader
335+
* object. When the method is called, the supplied reader object is expected to be positioned on
336+
* the source TLV element. The newly encoded element will have the same type, tag and contents as
337+
* the input container. If the supplied element is a TLV container (structure, array or list), the
338+
* entire contents of the container will be copied.
339+
*
340+
* @param reader a TlvReader object positioned at a Tlv element whose tag, type and value should
341+
* be copied. If this method is executed successfully, the reader will be positioned at the end
342+
* of the element that was copied.
343+
*/
344+
fun copyElement(reader: TlvReader): TlvWriter {
345+
return copyElement(reader.peekElement().tag, reader)
346+
}
347+
348+
/**
349+
* Copies a TLV element from a reader object into the writer.
350+
*
351+
* This method encodes a new TLV element whose type and value are taken from a TLVReader object.
352+
* When the method is called, the supplied reader object is expected to be positioned on the
353+
* source TLV element. The newly encoded element will have the same type and contents as the input
354+
* container, however the tag will be set to the specified argument. If the supplied element is a
355+
* TLV container (structure, array or list), the entire contents of the container will be copied.
356+
*
357+
* @param tag the TLV tag to be encoded with the element.
358+
* @param reader a TlvReader object positioned at a Tlv element whose type and value should be
359+
* copied. If this method is executed successfully, the reader will be positioned at the end of
360+
* the element that was copied.
361+
*/
362+
fun copyElement(tag: Tag, reader: TlvReader): TlvWriter {
363+
var depth = 0
364+
do {
365+
val element = reader.nextElement()
366+
val value = element.value
367+
368+
when (depth) {
369+
0 -> {
370+
require(value !is EndOfContainerValue) {
371+
"The TlvReader is positioned at invalid element: EndOfContainer"
372+
}
373+
put(Element(tag, value))
374+
}
375+
else -> put(element)
376+
}
377+
378+
if (value is EndOfContainerValue) {
379+
depth--
380+
} else if (value is StructureValue || value is ArrayValue || value is ListValue) {
381+
depth++
382+
}
383+
} while (depth > 0)
384+
return this
385+
}
386+
329387
/** Returns the total number of bytes written since the writer was initialized. */
330388
fun getLengthWritten(): Int {
331389
return bytes.size()
@@ -334,9 +392,7 @@ class TlvWriter(initialCapacity: Int = 32) {
334392
/** Verifies that all open containers are closed. */
335393
fun validateTlv(): TlvWriter {
336394
if (containerDepth > 0) {
337-
throw TlvEncodingException(
338-
"Invalid Tlv data at index ${bytes.size()}: $containerDepth containers are not closed"
339-
)
395+
throw TlvEncodingException("Invalid Tlv data: $containerDepth containers are not closed")
340396
}
341397
return this
342398
}

src/controller/java/tests/chip/tlv/TlvReadWriteTest.kt

+97-16
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ private val testTlvSampleData: ByteArray =
6363
.map { it.toByte() }
6464
.toByteArray()
6565

66-
private val testVendorId: UShort = 0xAABBu
67-
private val testProductId: UShort = 0xCCDDu
66+
private const val TEST_VENDOR_ID: UShort = 0xAABBu
67+
private const val TEST_PRODUCT_ID: UShort = 0xCCDDu
6868

6969
private val testLargeString: String =
7070
"""
@@ -86,8 +86,8 @@ class TlvReadWriteTest {
8686
@Test
8787
fun testTlvSampleData_write() {
8888
TlvWriter().apply {
89-
startStructure(FullyQualifiedTag(6, testVendorId, testProductId, 1u))
90-
put(FullyQualifiedTag(6, testVendorId, testProductId, 2u), true)
89+
startStructure(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u))
90+
put(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 2u), true)
9191
put(ImplicitProfileTag(2, 2u), false)
9292
startArray(ContextSpecificTag(0))
9393
put(AnonymousTag, 42)
@@ -97,15 +97,15 @@ class TlvReadWriteTest {
9797
startStructure(AnonymousTag)
9898
endStructure()
9999
startList(AnonymousTag)
100-
putNull(FullyQualifiedTag(6, testVendorId, testProductId, 17u))
100+
putNull(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 17u))
101101
putNull(ImplicitProfileTag(4, 900000u))
102102
putNull(AnonymousTag)
103103
startStructure(ImplicitProfileTag(4, 4000000000u))
104104
put(CommonProfileTag(4, 70000u), testLargeString)
105105
endStructure()
106106
endList()
107107
endArray()
108-
put(FullyQualifiedTag(6, testVendorId, testProductId, 5u), "This is a test")
108+
put(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 5u), "This is a test")
109109
put(ImplicitProfileTag(2, 65535u), 17.9f)
110110
put(ImplicitProfileTag(4, 65536u), 17.9)
111111
endStructure()
@@ -117,8 +117,8 @@ class TlvReadWriteTest {
117117
@Test
118118
fun testTlvSampleData_read() {
119119
TlvReader(testTlvSampleData).apply {
120-
enterStructure(FullyQualifiedTag(6, testVendorId, testProductId, 1u))
121-
assertThat(getBool(FullyQualifiedTag(6, testVendorId, testProductId, 2u))).isEqualTo(true)
120+
enterStructure(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u))
121+
assertThat(getBool(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 2u))).isEqualTo(true)
122122
assertThat(getBool(ImplicitProfileTag(2, 2u))).isEqualTo(false)
123123
enterArray(ContextSpecificTag(0))
124124
assertThat(getInt(AnonymousTag)).isEqualTo(42)
@@ -128,15 +128,15 @@ class TlvReadWriteTest {
128128
enterStructure(AnonymousTag)
129129
exitContainer()
130130
enterList(AnonymousTag)
131-
getNull(FullyQualifiedTag(6, testVendorId, testProductId, 17u))
131+
getNull(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 17u))
132132
getNull(ImplicitProfileTag(4, 900000u))
133133
getNull(AnonymousTag)
134134
enterStructure(ImplicitProfileTag(4, 4000000000u))
135135
assertThat(getUtf8String(CommonProfileTag(4, 70000u))).isEqualTo(testLargeString)
136136
exitContainer()
137137
exitContainer()
138138
exitContainer()
139-
assertThat(getUtf8String(FullyQualifiedTag(6, testVendorId, testProductId, 5u)))
139+
assertThat(getUtf8String(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 5u)))
140140
.isEqualTo("This is a test")
141141
assertThat(getFloat(ImplicitProfileTag(2, 65535u))).isEqualTo(17.9f)
142142
assertThat(getDouble(ImplicitProfileTag(4, 65536u))).isEqualTo(17.9)
@@ -149,13 +149,13 @@ class TlvReadWriteTest {
149149
@Test
150150
fun testTlvSampleData_read_useSkipElementAndExitContinerInTheMiddle() {
151151
TlvReader(testTlvSampleData).apply {
152-
enterStructure(FullyQualifiedTag(6, testVendorId, testProductId, 1u))
152+
enterStructure(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u))
153153
skipElement()
154154
assertThat(getBool(ImplicitProfileTag(2, 2u))).isEqualTo(false)
155155
enterArray(ContextSpecificTag(0))
156156
assertThat(getInt(AnonymousTag)).isEqualTo(42)
157157
exitContainer()
158-
assertThat(getUtf8String(FullyQualifiedTag(6, testVendorId, testProductId, 5u)))
158+
assertThat(getUtf8String(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 5u)))
159159
.isEqualTo("This is a test")
160160
assertThat(getFloat(ImplicitProfileTag(2, 65535u))).isEqualTo(17.9f)
161161
assertThat(getDouble(ImplicitProfileTag(4, 65536u))).isEqualTo(17.9)
@@ -165,6 +165,77 @@ class TlvReadWriteTest {
165165
}
166166
}
167167

168+
@Test
169+
fun testTlvSampleData_copyElement() {
170+
val reader = TlvReader(testTlvSampleData)
171+
val encoding = TlvWriter().copyElement(reader).validateTlv().getEncoded()
172+
assertThat(encoding).isEqualTo(testTlvSampleData)
173+
}
174+
175+
@Test
176+
fun testTlvSampleData_copyElementWithTag() {
177+
val reader = TlvReader(testTlvSampleData)
178+
val encoding =
179+
TlvWriter()
180+
.copyElement(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u), reader)
181+
.validateTlv()
182+
.getEncoded()
183+
assertThat(encoding).isEqualTo(testTlvSampleData)
184+
}
185+
186+
@Test
187+
fun testCopyElement_throwsIllegalArgumentException() {
188+
val encoding =
189+
TlvWriter().startStructure(AnonymousTag).endStructure().validateTlv().getEncoded()
190+
val reader = TlvReader(encoding)
191+
reader.skipElement()
192+
193+
// Throws exception because the reader is positioned at the end of container element
194+
assertFailsWith<IllegalArgumentException> { TlvWriter().copyElement(reader) }
195+
}
196+
197+
@Test
198+
fun testCopyElement_replaceTag() {
199+
val tag = CommonProfileTag(2, 1000u)
200+
val encoding =
201+
TlvWriter().startStructure(AnonymousTag).endStructure().validateTlv().getEncoded()
202+
val expectedEncoding = TlvWriter().startStructure(tag).endStructure().validateTlv().getEncoded()
203+
204+
assertThat(TlvWriter().copyElement(tag, TlvReader(encoding)).validateTlv().getEncoded())
205+
.isEqualTo(expectedEncoding)
206+
}
207+
208+
@Test
209+
fun testCopyElementUInt_replaceTag() {
210+
val value = 42U
211+
val tag1 = CommonProfileTag(2, 1u)
212+
val tag2 = CommonProfileTag(2, 2u)
213+
val encoding = TlvWriter().put(tag1, value).validateTlv().getEncoded()
214+
val expectedEncoding = TlvWriter().put(tag2, value).validateTlv().getEncoded()
215+
216+
assertThat(TlvWriter().copyElement(tag2, TlvReader(encoding)).validateTlv().getEncoded())
217+
.isEqualTo(expectedEncoding)
218+
}
219+
220+
@Test
221+
fun testTlvSampleData_copyElementsOneByOne() {
222+
val reader = TlvReader(testTlvSampleData)
223+
reader.skipElement()
224+
val encoding =
225+
TlvWriter()
226+
.startStructure(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 1u))
227+
.copyElement(reader)
228+
.copyElement(reader)
229+
.copyElement(reader)
230+
.copyElement(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 5u), reader)
231+
.copyElement(reader)
232+
.copyElement(reader)
233+
.endStructure()
234+
.validateTlv()
235+
.getEncoded()
236+
assertThat(encoding).isEqualTo(testTlvSampleData)
237+
}
238+
168239
@Test
169240
fun testData_IntMinMax() {
170241
val encodedTlv =
@@ -378,7 +449,7 @@ class TlvReadWriteTest {
378449

379450
// Throws exception because the encoded value has AnonymousTag tag
380451
assertFailsWith<IllegalArgumentException> {
381-
TlvReader(encoding).getLong(FullyQualifiedTag(6, testVendorId, testProductId, 5u))
452+
TlvReader(encoding).getLong(FullyQualifiedTag(6, TEST_VENDOR_ID, TEST_PRODUCT_ID, 5u))
382453
}
383454
}
384455

@@ -820,6 +891,16 @@ class TlvReadWriteTest {
820891
}
821892
}
822893

894+
@Test
895+
fun encodeAnonymousTagInStructure_throwsIllegalArgumentException() {
896+
// Anonymous tag 1, Unsigned Integer, 1-octet value, {1 = 42U}
897+
TlvWriter().apply {
898+
startStructure(AnonymousTag)
899+
// anonymous tags are not allowed within structure elements
900+
assertFailsWith<IllegalArgumentException> { put(AnonymousTag, 42U) }
901+
}
902+
}
903+
823904
@Test
824905
fun encodeContextTag_withinList() {
825906
// Context tag 1, Unsigned Integer, 1-octet value, [[1 = 42U]]
@@ -847,7 +928,7 @@ class TlvReadWriteTest {
847928
val value = 42U
848929
var tag = ContextSpecificTag(1)
849930

850-
// Array elements SHALL be of anonumous type
931+
// Array elements SHALL be of anonymous type
851932
TlvWriter().apply {
852933
startArray(AnonymousTag)
853934
assertFailsWith<IllegalArgumentException> { put(tag, value) }
@@ -965,7 +1046,7 @@ class TlvReadWriteTest {
9651046

9661047
@Test
9671048
fun putSignedLongArray() {
968-
// Anonumous Array of Signed Integers, [42, -17, -170000, 40000000000]
1049+
// Anonymous Array of Signed Integers, [42, -17, -170000, 40000000000]
9691050
val values = longArrayOf(42, -17, -170000, 40000000000)
9701051
val encoding = "16 00 2a 00 ef 02 f0 67 fd ff 03 00 90 2f 50 09 00 00 00 18".octetsToByteArray()
9711052

@@ -986,7 +1067,7 @@ class TlvReadWriteTest {
9861067

9871068
@Test
9881069
fun putUnsignedLongArray() {
989-
// Anonumous Array of Signed Integers, [42, 170000, 40000000000]
1070+
// Anonymous Array of Signed Integers, [42, 170000, 40000000000]
9901071
val values = longArrayOf(42, 170000, 40000000000)
9911072
val encoding = "16 04 2a 06 10 98 02 00 07 00 90 2f 50 09 00 00 00 18".octetsToByteArray()
9921073

0 commit comments

Comments
 (0)