# -*- coding: utf-8 -*- from . import devices from binascii import hexlify, unhexlify from struct import pack, unpack TLV_TYPES = { 0x0001: "Checksum rectifier", 0x0002: "Padding", 0x0003: "Part number?", 0x0005: "Copyright notice", 0x0006: "Block Type 7 format definition", 0x0007: "Binary descriptor", 0x0008: "Binary Region 0C (boot.bin)", 0x0401: "Binary Component Firmware (SensorHub, ANT_BLE_BT, GPS, WiFi)", 0x0505: "Binary Region 05", 0x0555: "Binary Region 55", 0x0557: "Binary Region 57", 0x02bd: "Binary Region 0E (fw_all.bin)", 0xffff: "EOF marker", } class TLV: def __init__(self, type_id: int, expected_length: int, value=None, offset: int=None): self.type_id = type_id self.is_binary = False self.offset = offset self.comment = TLV_TYPES.get(type_id, "Type {:04x} / {:d}".format(type_id, type_id)) self.length = expected_length self.value = None self.is_parsed = False if value is not None: self.value = bytes(value) @staticmethod def factory(type_id: int, length: int = None, offset: int = None): if type_id == 0x0001: new_tlv = TLV1(type_id, length) elif type_id == 0x0002: new_tlv = TLV2(type_id, length) elif type_id == 0x0005: new_tlv = TLV5(type_id, length) elif type_id == 0x0006: new_tlv = TLV6(type_id, length) elif type_id == 0x0007: new_tlv = TLV7(type_id, length) elif type_id in [0x0008, 0x0401, 0x0505, 0x0555, 0x0557, 0x02bd]: new_tlv = TLVbinary(type_id, length) new_tlv.is_binary = True else: new_tlv = TLV(type_id, length) new_tlv.offset = offset return new_tlv def __str__(self): plural = "" if self.length != 1: plural = "s" offset = "" if self.offset: offset = " at 0x{:x}".format(self.offset) lenstr = "" if self.length: lenstr = ", {:d} Byte{}".format(self.length, plural) return "TLV Type {:04x}{}{} - {}".format(self.type_id, offset, lenstr, self.comment) def set_value(self, new_value: bytes): self.value = new_value def get_actual_length(self): if self.value is None: return 0 return len(self.value) def get_record_length(self): # Length including record definition return self.get_actual_length() + 4 def get_value(self): return self.value def get_header(self): header = pack("HH", self.type_id, self.get_actual_length()) return header def get(self): header = self.get_header() if self.value is None: return header return header + self.value def dump(self): """Should return a list of key-value-comment tuples so the object can be recreated using create_from_dump() later.""" data = [] data.append(("type", "0x{:04x}".format(self.type_id), self.comment)) data.append(("length", self.get_actual_length(), None)) if self.value is not None: hexstr = hexlify(self.value).decode("utf-8") hexstr = " ".join(hexstr[i:i+2] for i in range(0, len(hexstr), 2)) data.append(("value", hexstr, None)) return data def load_dump(self, values): for (k, v) in values: if k == "value": self.value = unhexlify("".join(v.split(" "))) self.length = len(self.value) @staticmethod def create_from_dump(values): """Use data exported with dump() to recreate object.""" tlv = None for (k, v) in values: if k == "type": type_id = int(v, 0) tlv = TLV.factory(type_id) tlv.load_dump(values) return tlv class TLV1(TLV): def dump(self): data = [] data.append(("type", "0x{:04x}".format(self.type_id), self.comment)) return data def load_dump(self, values): # Will be calculated in a final pass self.value = 0x00 self.length = 1 class TLV2(TLV): def dump(self): data = [] data.append(("type", "0x{:04x}".format(self.type_id), self.comment)) data.append(("length", self.get_actual_length(), "Length of padding block")) return data def load_dump(self, values): for (k, v) in values: if k == "length": self.value = b"\x00" * int(v) self.length = len(self.value) class TLV5(TLV): def dump(self): data = [] data.append(("type", "0x{:04x}".format(self.type_id), self.comment)) data.append(("length", self.get_actual_length(), None)) data.append(("text", self.value.decode("utf-8"), None)) return data def load_dump(self, values): for (k, v) in values: if k == "text": self.value = v.encode("utf-8") elif k == "length": self.length = int(v) if len(self.value) != self.length: print("WARNING: Imported copyright text doesn't match supposed length.") self.length = len(self.value) class TLV6(TLV): """ Describes following TLV7: http://www.gpspassion.com/forumsen/topic.asp?TOPIC_ID=137838&whichpage=12 """ # Field ids in Type 6 payloads (describe Type 7 data format) # First nibble might be data type: 0 = B, 1 = H, 2 = L FIELD_TYPES = { 0x000a: ["B", "XOR flag/value"], 0x000b: ["B", "Reset/Downgrade flag"], 0x1009: ["H", "Device hw_id"], 0x100a: ["H", "Block type"], 0x100d: ["H", "Firmware version"], 0x1014: ["H", "Field 1014"], 0x1015: ["H", "Field 1015"], 0x1016: ["H", "Field 1016 (WiFi fw)"], 0x2015: ["L", "Binary length"], 0x5003: ["", "End of definition marker"], } def __init__(self, type_id: int, expected_length: int, value=None, offset: int=None): super().__init__(type_id, expected_length, value, offset) self.fids = [] self.format = "" self.fields = [] def add_fid(self, fid: int): fdef = self.FIELD_TYPES[fid] self.fids.append(fid) self.format += fdef[0] self.fields.append(fdef[1]) def parse(self): if self.is_parsed: # already parsed return if len(self.value) % 2 != 0: raise Exception("Invalid TLV6 payload length!") for i in range(0, len(self.value), 2): fid = unpack("