bf74c34534dc004c0f0758c6601dfb03513946e4
[platform/upstream/connectedhomeip.git] / src / controller / python / chip / ChipTLV.py
1 #!/usr/bin/env python3
2 # coding=utf-8
3
4 #
5 #   Copyright (c) 2020 Project CHIP Authors
6 #   Copyright (c) 2019-2020 Google LLC.
7 #   All rights reserved.
8 #
9 #   Licensed under the Apache License, Version 2.0 (the "License");
10 #   you may not use this file except in compliance with the License.
11 #   You may obtain a copy of the License at
12 #
13 #       http://www.apache.org/licenses/LICENSE-2.0
14 #
15 #   Unless required by applicable law or agreed to in writing, software
16 #   distributed under the License is distributed on an "AS IS" BASIS,
17 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 #   See the License for the specific language governing permissions and
19 #   limitations under the License.
20 #
21
22 #
23 #   @file
24 #         This file contains definitions for working with data encoded in Chip TLV format
25 #
26
27
28 from __future__ import absolute_import
29 from __future__ import print_function
30
31 import struct
32 from collections import Mapping, Sequence, OrderedDict
33
34
35 TLV_TYPE_SIGNED_INTEGER = 0x00
36 TLV_TYPE_UNSIGNED_INTEGER = 0x04
37 TLV_TYPE_BOOLEAN = 0x08
38 TLV_TYPE_FLOATING_POINT_NUMBER = 0x0A
39 TLV_TYPE_UTF8_STRING = 0x0C
40 TLV_TYPE_BYTE_STRING = 0x10
41 TLV_TYPE_NULL = 0x14
42 TLV_TYPE_STRUCTURE = 0x15
43 TLV_TYPE_ARRAY = 0x16
44 TLV_TYPE_PATH = 0x17
45
46 TLV_TAG_CONTROL_ANONYMOUS = 0x00
47 TLV_TAG_CONTROL_CONTEXT_SPECIFIC = 0x20
48 TLV_TAG_CONTROL_COMMON_PROFILE_2Bytes = 0x40
49 TLV_TAG_CONTROL_COMMON_PROFILE_4Bytes = 0x60
50 TLV_TAG_CONTROL_IMPLICIT_PROFILE_2Bytes = 0x80
51 TLV_TAG_CONTROL_IMPLICIT_PROFILE_4Bytes = 0xA0
52 TLV_TAG_CONTROL_FULLY_QUALIFIED_6Bytes = 0xC0
53 TLV_TAG_CONTROL_FULLY_QUALIFIED_8Bytes = 0xE0
54
55 TLVBoolean_False = TLV_TYPE_BOOLEAN
56 TLVBoolean_True = TLV_TYPE_BOOLEAN + 1
57
58 TLVEndOfContainer = 0x18
59
60 INT8_MIN = -128
61 INT16_MIN = -32768
62 INT32_MIN = -2147483648
63 INT64_MIN = -9223372036854775808
64
65 INT8_MAX = 127
66 INT16_MAX = 32767
67 INT32_MAX = 2147483647
68 INT64_MAX = 9223372036854775807
69
70 UINT8_MAX = 255
71 UINT16_MAX = 65535
72 UINT32_MAX = 4294967295
73 UINT64_MAX = 18446744073709551615
74
75 ElementTypes = {
76     0x00: "Signed Integer 1-byte value",
77     0x01: "Signed Integer 2-byte value",
78     0x02: "Signed Integer 4-byte value",
79     0x03: "Signed Integer 8-byte value",
80     0x04: "Unsigned Integer 1-byte value",
81     0x05: "Unsigned Integer 2-byte value",
82     0x06: "Unsigned Integer 4-byte value",
83     0x07: "Unsigned Integer 8-byte value",
84     0x08: "Boolean False",
85     0x09: "Boolean True",
86     0x0A: "Floating Point 4-byte value",
87     0x0B: "Floating Point 8-byte value",
88     0x0C: "UTF-8 String 1-byte length",
89     0x0D: "UTF-8 String 2-byte length",
90     0x0E: "UTF-8 String 4-byte length",
91     0x0F: "UTF-8 String 8-byte length",
92     0x10: "Byte String 1-byte length",
93     0x11: "Byte String 2-byte length",
94     0x12: "Byte String 4-byte length",
95     0x13: "Byte String 8-byte length",
96     0x14: "Null",
97     0x15: "Structure",
98     0x16: "Array",
99     0x17: "Path",
100     0x18: "End of Collection",
101 }
102
103 TagControls = {
104     0x00: "Anonymous",
105     0x20: "Context 1-byte",
106     0x40: "Common Profile 2-byte",
107     0x60: "Common Profile 4-byte",
108     0x80: "Implicit Profile 2-byte",
109     0xA0: "Implicit Profile 4-byte",
110     0xC0: "Fully Qualified 6-byte",
111     0xE0: "Fully Qualified 8-byte",
112 }
113
114
115 class TLVWriter(object):
116     def __init__(self, encoding=None, implicitProfile=None):
117         self._encoding = encoding if encoding is not None else bytearray()
118         self._implicitProfile = implicitProfile
119         self._containerStack = []
120
121     @property
122     def encoding(self):
123         """The object into which encoded TLV data is written.
124
125         By default this is a bytearray object.
126         """
127         return self._encoding
128
129     @encoding.setter
130     def encoding(self, val):
131         self._encoding = val
132
133     @property
134     def implicitProfile(self):
135         """The Chip profile id used when encoding implicit profile tags.
136
137         Setting this value will result in an implicit profile tag being encoded
138         whenever the profile of the tag to be encoded matches the specified implicit
139         profile id.
140
141         Setting this value to None (the default) disabled encoding of implicit
142         profile tags.
143         """
144         return self._implicitProfile
145
146     @implicitProfile.setter
147     def implicitProfile(self, val):
148         self._implicitProfile = val
149
150     def put(self, tag, val):
151         """Write a value in TLV format with the specified TLV tag.
152
153         val can be a Python object which will be encoded as follows:
154         - Python bools, floats and strings are encoded as their respective TLV types.
155         - Python ints are encoded as unsigned TLV integers if zero or positive; signed TLV
156           integers if negative.
157         - None is encoded as a TLV Null.
158         - bytes and bytearray objects are encoded as TVL byte strings.
159         - Mapping-like objects (e.g. dict) are encoded as TLV structures.  The keys of the
160           map object are expected to be tag values, as described below for the tag argument.
161           Map values are encoded recursively, using the same rules as defined for the val
162           argument. The encoding order of elements depends on the type of the map object.
163           Elements within a dict are automatically encoded tag numerical order. Elements
164           within other forms of mapping object (e.g. OrderedDict) are encoded in the
165           object's natural iteration order.
166         - Sequence-like objects (e.g. arrays) are written as TLV arrays. Elements within
167           the array are encoded recursively, using the same rules as defined for the val
168           argument.
169
170         tag can be a small int (0-255), a tuple of two integers, or None.
171         If tag is an integer, it is encoded as a TLV context-specific tag.
172         If tag is a two-integer tuple, it is encoded as a TLV profile-specific tag, with
173           the first integer encoded as the profile id and the second as the tag number.
174         If tag is None, it is encoded as a TLV anonymous tag.
175         """
176         if val is None:
177             self.putNull(tag)
178         elif isinstance(val, bool):
179             self.putBool(tag, val)
180         elif isinstance(val, int):
181             if val < 0:
182                 self.putSignedInt(tag, val)
183             else:
184                 self.putUnsignedInt(tag, val)
185         elif isinstance(val, float):
186             self.putFloat(tag, val)
187         elif isinstance(val, str):
188             self.putString(tag, val)
189         elif isinstance(val, bytes) or isinstance(val, bytearray):
190             self.putBytes(tag, val)
191         elif isinstance(val, Mapping):
192             self.startStructure(tag)
193             if type(val) == dict:
194                 val = OrderedDict(
195                     sorted(val.items(), key=lambda item: tlvTagToSortKey(item[0]))
196                 )
197             for containedTag, containedVal in val.items():
198                 self.put(containedTag, containedVal)
199             self.endContainer()
200         elif isinstance(val, Sequence):
201             self.startArray(tag)
202             for containedVal in val:
203                 self.put(None, containedVal)
204             self.endContainer()
205         else:
206             raise ValueError("Attempt to TLV encode unsupported value")
207
208     def putSignedInt(self, tag, val):
209         """Write a value as a TLV signed integer with the specified TLV tag."""
210         if val >= INT8_MIN and val <= INT8_MAX:
211             format = "<b"
212         elif val >= INT16_MIN and val <= INT16_MAX:
213             format = "<h"
214         elif val >= INT32_MIN and val <= INT32_MAX:
215             format = "<l"
216         elif val >= INT64_MIN and val <= INT64_MAX:
217             format = "<q"
218         else:
219             raise ValueError("Integer value out of range")
220         val = struct.pack(format, val)
221         controlAndTag = self._encodeControlAndTag(
222             TLV_TYPE_SIGNED_INTEGER, tag, lenOfLenOrVal=len(val)
223         )
224         self._encoding.extend(controlAndTag)
225         self._encoding.extend(val)
226
227     def putUnsignedInt(self, tag, val):
228         """Write a value as a TLV unsigned integer with the specified TLV tag."""
229         val = self._encodeUnsignedInt(val)
230         controlAndTag = self._encodeControlAndTag(
231             TLV_TYPE_UNSIGNED_INTEGER, tag, lenOfLenOrVal=len(val)
232         )
233         self._encoding.extend(controlAndTag)
234         self._encoding.extend(val)
235
236     def putFloat(self, tag, val):
237         """Write a value as a TLV float with the specified TLV tag."""
238         val = struct.pack("d", val)
239         controlAndTag = self._encodeControlAndTag(
240             TLV_TYPE_FLOATING_POINT_NUMBER, tag, lenOfLenOrVal=len(val)
241         )
242         self._encoding.extend(controlAndTag)
243         self._encoding.extend(val)
244
245     def putString(self, tag, val):
246         """Write a value as a TLV string with the specified TLV tag."""
247         val = val.encode("utf-8")
248         valLen = self._encodeUnsignedInt(len(val))
249         controlAndTag = self._encodeControlAndTag(
250             TLV_TYPE_UTF8_STRING, tag, lenOfLenOrVal=len(valLen)
251         )
252         self._encoding.extend(controlAndTag)
253         self._encoding.extend(valLen)
254         self._encoding.extend(val)
255
256     def putBytes(self, tag, val):
257         """Write a value as a TLV byte string with the specified TLV tag."""
258         valLen = self._encodeUnsignedInt(len(val))
259         controlAndTag = self._encodeControlAndTag(
260             TLV_TYPE_BYTE_STRING, tag, lenOfLenOrVal=len(valLen)
261         )
262         self._encoding.extend(controlAndTag)
263         self._encoding.extend(valLen)
264         self._encoding.extend(val)
265
266     def putBool(self, tag, val):
267         """Write a value as a TLV boolean with the specified TLV tag."""
268         if val:
269             type = TLVBoolean_True
270         else:
271             type = TLVBoolean_False
272         controlAndTag = self._encodeControlAndTag(type, tag)
273         self._encoding.extend(controlAndTag)
274
275     def putNull(self, tag):
276         """Write a TLV null with the specified TLV tag."""
277         controlAndTag = self._encodeControlAndTag(TLV_TYPE_NULL, tag)
278         self._encoding.extend(controlAndTag)
279
280     def startContainer(self, tag, containerType):
281         """Start writing a TLV container with the specified TLV tag.
282
283         containerType can be one of TLV_TYPE_STRUCTURE, TLV_TYPE_ARRAY or
284         TLV_TYPE_PATH.
285         """
286         self._verifyValidContainerType(containerType)
287         controlAndTag = self._encodeControlAndTag(containerType, tag)
288         self._encoding.extend(controlAndTag)
289         self._containerStack.insert(0, containerType)
290
291     def startStructure(self, tag):
292         """Start writing a TLV structure with the specified TLV tag."""
293         self.startContainer(tag, containerType=TLV_TYPE_STRUCTURE)
294
295     def startArray(self, tag):
296         """Start writing a TLV array with the specified TLV tag."""
297         self.startContainer(tag, containerType=TLV_TYPE_ARRAY)
298
299     def startPath(self, tag):
300         """Start writing a TLV path with the specified TLV tag."""
301         self.startContainer(tag, containerType=TLV_TYPE_PATH)
302
303     def endContainer(self):
304         """End writing the current TLV container."""
305         self._containerStack.pop(0)
306         controlAndTag = self._encodeControlAndTag(TLVEndOfContainer, None)
307         self._encoding.extend(controlAndTag)
308
309     def _encodeControlAndTag(self, type, tag, lenOfLenOrVal=0):
310         controlByte = type
311         if lenOfLenOrVal == 2:
312             controlByte |= 1
313         elif lenOfLenOrVal == 4:
314             controlByte |= 2
315         elif lenOfLenOrVal == 8:
316             controlByte |= 3
317         if tag is None:
318             if (
319                 type != TLVEndOfContainer
320                 and len(self._containerStack) != 0
321                 and self._containerStack[0] == TLV_TYPE_STRUCTURE
322             ):
323                 raise ValueError("Attempt to encode anonymous tag within TLV structure")
324             controlByte |= TLV_TAG_CONTROL_ANONYMOUS
325             return struct.pack("<B", controlByte)
326         if isinstance(tag, int):
327             if tag < 0 or tag > UINT8_MAX:
328                 raise ValueError("Context-specific TLV tag number out of range")
329             if len(self._containerStack) == 0:
330                 raise ValueError(
331                     "Attempt to encode context-specific TLV tag at top level"
332                 )
333             if self._containerStack[0] == TLV_TYPE_ARRAY:
334                 raise ValueError(
335                     "Attempt to encode context-specific tag within TLV array"
336                 )
337             controlByte |= TLV_TAG_CONTROL_CONTEXT_SPECIFIC
338             return struct.pack("<BB", controlByte, tag)
339         if isinstance(tag, tuple):
340             (profile, tagNum) = tag
341             if not isinstance(tagNum, int):
342                 raise ValueError("Invalid object given for TLV tag")
343             if tagNum < 0 or tagNum > UINT32_MAX:
344                 raise ValueError("TLV tag number out of range")
345             if profile != None:
346                 if not isinstance(profile, int):
347                     raise ValueError("Invalid object given for TLV profile id")
348                 if profile < 0 or profile > UINT32_MAX:
349                     raise ValueError("TLV profile id value out of range")
350             if (
351                 len(self._containerStack) != 0
352                 and self._containerStack[0] == TLV_TYPE_ARRAY
353             ):
354                 raise ValueError(
355                     "Attempt to encode profile-specific tag within TLV array"
356                 )
357             if profile is None or profile == self._implicitProfile:
358                 if tagNum <= UINT16_MAX:
359                     controlByte |= TLV_TAG_CONTROL_IMPLICIT_PROFILE_2Bytes
360                     return struct.pack("<BH", controlByte, tagNum)
361                 else:
362                     controlByte |= TLV_TAG_CONTROL_IMPLICIT_PROFILE_4Bytes
363                     return struct.pack("<BL", controlByte, tagNum)
364             elif profile == 0:
365                 if tagNum <= UINT16_MAX:
366                     controlByte |= TLV_TAG_CONTROL_COMMON_PROFILE_2Bytes
367                     return struct.pack("<BH", controlByte, tagNum)
368                 else:
369                     controlByte |= TLV_TAG_CONTROL_COMMON_PROFILE_4Bytes
370                     return struct.pack("<BL", controlByte, tagNum)
371             else:
372                 if tagNum <= UINT16_MAX:
373                     controlByte |= TLV_TAG_CONTROL_FULLY_QUALIFIED_6Bytes
374                     return struct.pack("<BLH", controlByte, profile, tagNum)
375                 else:
376                     controlByte |= TLV_TAG_CONTROL_FULLY_QUALIFIED_8Bytes
377                     return struct.pack("<BLL", controlByte, profile, tagNum)
378         raise ValueError("Invalid object given for TLV tag")
379
380     @staticmethod
381     def _encodeUnsignedInt(val):
382         if val < 0:
383             raise ValueError("Integer value out of range")
384         if val <= UINT8_MAX:
385             format = "<B"
386         elif val <= UINT16_MAX:
387             format = "<H"
388         elif val <= UINT32_MAX:
389             format = "<L"
390         elif val <= UINT64_MAX:
391             format = "<Q"
392         else:
393             raise ValueError("Integer value out of range")
394         return struct.pack(format, val)
395
396     @staticmethod
397     def _verifyValidContainerType(containerType):
398         if (
399             containerType != TLV_TYPE_STRUCTURE
400             and containerType != TLV_TYPE_ARRAY
401             and containerType != TLV_TYPE_PATH
402         ):
403             raise ValueError("Invalid TLV container type")
404
405
406 class TLVReader(object):
407     def __init__(self, tlv):
408         self._tlv = tlv
409         self._bytesRead = 0
410         self._decodings = []
411
412     @property
413     def decoding(self):
414         return self._decodings
415
416     def get(self):
417         """Get the dictionary representation of tlv data"""
418         out = {}
419         self._get(self._tlv, self._decodings, out)
420         return out
421
422     def _decodeControlByte(self, tlv, decoding):
423         (controlByte,) = struct.unpack("<B", tlv[self._bytesRead : self._bytesRead + 1])
424         controlTypeIndex = controlByte & 0xE0
425         decoding["tagControl"] = TagControls[controlTypeIndex]
426         elementtypeIndex = controlByte & 0x1F
427         decoding["type"] = ElementTypes[elementtypeIndex]
428         self._bytesRead += 1
429
430     def _decodeControlAndTag(self, tlv, decoding):
431         """The control byte specifies the type of a TLV element and how its tag, length and value fields are encoded.
432         The control byte consists of two subfields: an element type field which occupies the lower 5 bits,
433         and a tag control field which occupies the upper 3 bits. The element type field encodes the element’s type
434         as well as how the corresponding length and value fields are encoded.  In the case of Booleans and the
435         null value, the element type field also encodes the value itself."""
436
437         self._decodeControlByte(tlv, decoding)
438
439         if decoding["tagControl"] == "Anonymous":
440             decoding["tag"] = None
441             decoding["tagLen"] = 0
442         elif decoding["tagControl"] == "Context 1-byte":
443             (decoding["tag"],) = struct.unpack(
444                 "<B", tlv[self._bytesRead : self._bytesRead + 1]
445             )
446             decoding["tagLen"] = 1
447             self._bytesRead += 1
448         elif decoding["tagControl"] == "Common Profile 2-byte":
449             profile = 0
450             (tag,) = struct.unpack("<H", tlv[self._bytesRead : self._bytesRead + 2])
451             decoding["profileTag"] = (profile, tag)
452             decoding["tagLen"] = 2
453             self._bytesRead += 2
454         elif decoding["tagControl"] == "Common Profile 4-byte":
455             profile = 0
456             (tag,) = struct.unpack("<L", tlv[self._bytesRead : self._bytesRead + 4])
457             decoding["profileTag"] = (profile, tag)
458             decoding["tagLen"] = 4
459             self._bytesRead += 4
460         elif decoding["tagControl"] == "Implicit Profile 2-byte":
461             profile = None
462             (tag,) = struct.unpack("<H", tlv[self._bytesRead : self._bytesRead + 2])
463             decoding["profileTag"] = (profile, tag)
464             decoding["tagLen"] = 2
465             self._bytesRead += 2
466         elif decoding["tagControl"] == "Implicit Profile 4-byte":
467             profile = None
468             (tag,) = struct.unpack("<L", tlv[self._bytesRead : self._bytesRead + 4])
469             decoding["profileTag"] = (profile, tag)
470             decoding["tagLen"] = 4
471             self._bytesRead += 4
472         elif decoding["tagControl"] == "Fully Qualified 6-byte":
473             (profile,) = struct.unpack("<L", tlv[self._bytesRead : self._bytesRead + 4])
474             (tag,) = struct.unpack("<H", tlv[self._bytesRead + 4 : self._bytesRead + 6])
475             decoding["profileTag"] = (profile, tag)
476             decoding["tagLen"] = 2
477             self._bytesRead += 6
478         elif decoding["tagControl"] == "Fully Qualified 8-byte":
479             (profile,) = struct.unpack("<L", tlv[self._bytesRead : self._bytesRead + 4])
480             (tag,) = struct.unpack("<L", tlv[self._bytesRead + 4 : self._bytesRead + 8])
481             decoding["profileTag"] = (profile, tag)
482             decoding["tagLen"] = 4
483             self._bytesRead += 8
484
485     def _decodeStrLength(self, tlv, decoding):
486         """UTF-8 or Byte StringLength fields are encoded in 0, 1, 2 or 4 byte widths, as specified by
487         the element type field. If the element type needs a length field grab the next bytes as length"""
488         if "length" in decoding["type"]:
489             if "1-byte" in decoding["type"]:
490                 (decoding["strDataLen"],) = struct.unpack(
491                     "<B", tlv[self._bytesRead : self._bytesRead + 1]
492                 )
493                 decoding["strDataLenLen"] = 1
494                 self._bytesRead += 1
495             elif "2-byte" in decoding["type"]:
496                 (decoding["strDataLen"],) = struct.unpack(
497                     "<H", tlv[self._bytesRead : self._bytesRead + 2]
498                 )
499                 decoding["strDataLenLen"] = 2
500                 self._bytesRead += 2
501             elif "4-byte" in decoding["type"]:
502                 (decoding["strDataLen"],) = struct.unpack(
503                     "<L", tlv[self._bytesRead : self._bytesRead + 4]
504                 )
505                 decoding["strDataLenLen"] = 4
506                 self._bytesRead += 4
507             elif "8-byte" in decoding["type"]:
508                 (decoding["strDataLen"],) = struct.unpack(
509                     "<Q", tlv[self._bytesRead : self._bytesRead + 8]
510                 )
511                 decoding["strDataLenLen"] = 8
512                 self._bytesRead += 8
513         else:
514             decoding["strDataLen"] = 0
515             decoding["strDataLenLen"] = 0
516
517     def _decodeVal(self, tlv, decoding):
518         """decode primitive tlv value to the corresponding python value, tlv array and path are decoded as
519         python list, tlv structure is decoded as python dictionary"""
520         if decoding["type"] == "Structure":
521             decoding["value"] = {}
522             decoding["Structure"] = []
523             self._get(tlv, decoding["Structure"], decoding["value"])
524         elif decoding["type"] == "Array":
525             decoding["value"] = []
526             decoding["Array"] = []
527             self._get(tlv, decoding["Array"], decoding["value"])
528         elif decoding["type"] == "Path":
529             decoding["value"] = []
530             decoding["Path"] = []
531             self._get(tlv, decoding["Path"], decoding["value"])
532         elif decoding["type"] == "Null":
533             decoding["value"] = None
534         elif decoding["type"] == "End of Collection":
535             decoding["value"] = None
536         elif decoding["type"] == "Boolean True":
537             decoding["value"] = True
538         elif decoding["type"] == "Boolean False":
539             decoding["value"] = False
540         elif decoding["type"] == "Unsigned Integer 1-byte value":
541             (decoding["value"],) = struct.unpack(
542                 "<B", tlv[self._bytesRead : self._bytesRead + 1]
543             )
544             self._bytesRead += 1
545         elif decoding["type"] == "Signed Integer 1-byte value":
546             (decoding["value"],) = struct.unpack(
547                 "<b", tlv[self._bytesRead : self._bytesRead + 1]
548             )
549             self._bytesRead += 1
550         elif decoding["type"] == "Unsigned Integer 2-byte value":
551             (decoding["value"],) = struct.unpack(
552                 "<H", tlv[self._bytesRead : self._bytesRead + 2]
553             )
554             self._bytesRead += 2
555         elif decoding["type"] == "Signed Integer 2-byte value":
556             (decoding["value"],) = struct.unpack(
557                 "<h", tlv[self._bytesRead : self._bytesRead + 2]
558             )
559             self._bytesRead += 2
560         elif decoding["type"] == "Unsigned Integer 4-byte value":
561             (decoding["value"],) = struct.unpack(
562                 "<L", tlv[self._bytesRead : self._bytesRead + 4]
563             )
564             self._bytesRead += 4
565         elif decoding["type"] == "Signed Integer 4-byte value":
566             (decoding["value"],) = struct.unpack(
567                 "<l", tlv[self._bytesRead : self._bytesRead + 4]
568             )
569             self._bytesRead += 4
570         elif decoding["type"] == "Unsigned Integer 8-byte value":
571             (decoding["value"],) = struct.unpack(
572                 "<Q", tlv[self._bytesRead : self._bytesRead + 8]
573             )
574             self._bytesRead += 8
575         elif decoding["type"] == "Signed Integer 8-byte value":
576             (decoding["value"],) = struct.unpack(
577                 "<q", tlv[self._bytesRead : self._bytesRead + 8]
578             )
579             self._bytesRead += 8
580         elif decoding["type"] == "Floating Point 4-byte value":
581             (decoding["value"],) = struct.unpack(
582                 "<f", tlv[self._bytesRead : self._bytesRead + 4]
583             )
584             self._bytesRead += 4
585         elif decoding["type"] == "Floating Point 8-byte value":
586             (decoding["value"],) = struct.unpack(
587                 "<d", tlv[self._bytesRead : self._bytesRead + 8]
588             )
589             self._bytesRead += 8
590         elif "UTF-8 String" in decoding["type"]:
591             (val,) = struct.unpack(
592                 "<%ds" % decoding["strDataLen"],
593                 tlv[self._bytesRead : self._bytesRead + decoding["strDataLen"]],
594             )
595             try:
596                 decoding["value"] = str(val, "utf-8")
597             except Exception as ex:
598                 decoding["value"] = val
599             self._bytesRead += decoding["strDataLen"]
600         elif "Byte String" in decoding["type"]:
601             (val,) = struct.unpack(
602                 "<%ds" % decoding["strDataLen"],
603                 tlv[self._bytesRead : self._bytesRead + decoding["strDataLen"]],
604             )
605
606             decoding["value"] = val
607             self._bytesRead += decoding["strDataLen"]
608         else:
609             raise ValueError("Attempt to decode unsupported TLV type")
610
611     def _get(self, tlv, decodings, out):
612         endOfEncoding = False
613
614         while len(tlv[self._bytesRead :]) > 0 and endOfEncoding == False:
615             decoding = {}
616             self._decodeControlAndTag(tlv, decoding)
617             self._decodeStrLength(tlv, decoding)
618             self._decodeVal(tlv, decoding)
619             decodings.append(decoding)
620
621             if decoding["type"] == "End of Collection":
622                 endOfEncoding = True
623             else:
624                 if "profileTag" in list(decoding.keys()):
625                     out[decoding["profileTag"]] = decoding["value"]
626                 elif "tag" in list(decoding.keys()):
627                     if decoding["tag"] is not None:
628                         out[decoding["tag"]] = decoding["value"]
629                     else:
630                         if isinstance(out, Mapping):
631                             out["Any"] = decoding["value"]
632                         elif isinstance(out, Sequence):
633                             out.append(decoding["value"])
634                 else:
635                     raise ValueError("Attempt to decode unsupported TLV tag")
636
637
638 def tlvTagToSortKey(tag):
639     if tag is None:
640         return -1
641     if isinstance(tag, int):
642         majorOrder = 0
643     elif isinstance(tag, tuple):
644         (profileId, tag) = tag
645         if profileId is None:
646             majorOrder = 1
647         else:
648             majorOrder = profileId + 2
649     else:
650         raise ValueError("Invalid TLV tag")
651     return (majorOrder << 32) + tag
652
653
654 if __name__ == "__main__":
655     val = dict(
656         [
657             (1, 0),
658             (2, 65536),
659             (3, True),
660             (4, None),
661             (5, "Hello!"),
662             (6, bytearray([0xDE, 0xAD, 0xBE, 0xEF])),
663             (7, ["Goodbye!", 71024724507, False]),
664             ((0x235A0000, 42), "FOO"),
665             ((None, 42), "BAR"),
666         ]
667     )
668
669     writer = TLVWriter()
670     encodedVal = writer.put(None, val)
671
672     reader = TLVReader(writer.encoding)
673     out = reader.get()
674
675     print("TLVReader input: " + str(val))
676     print("TLVReader output: " + str(out["Any"]))
677
678     if val == out["Any"]:
679         print("Test Success")
680     else:
681         print("Test Failure")