#!/usr/bin/python3
import binascii


class OnietlvException(Exception):
    def __init__(self, message='onietlverror', code=-100):
        err = 'errcode: {0} message:{1}'.format(code, message)
        Exception.__init__(self, err)
        self.code = code
        self.message = message


class onie_tlv(object):
    TLV_INFO_ID_STRING = "TlvInfo\x00"
    TLV_INFO_INIA_ID = "\x00\x00\x13\x11"
    TLV_INFO_VERSION = 0x01
    TLV_INFO_LENGTH = 0x00
    TLV_INFO_LENGTH_VALUE = 0xba

    TLV_CODE_PRODUCT_NAME = 0x21
    TLV_CODE_PART_NUMBER = 0x22
    TLV_CODE_SERIAL_NUMBER = 0x23
    TLV_CODE_MAC_BASE = 0x24
    TLV_CODE_MANUF_DATE = 0x25
    TLV_CODE_DEVICE_VERSION = 0x26
    TLV_CODE_LABEL_REVISION = 0x27
    TLV_CODE_PLATFORM_NAME = 0x28
    TLV_CODE_ONIE_VERSION = 0x29
    TLV_CODE_MAC_SIZE = 0x2A
    TLV_CODE_MANUF_NAME = 0x2B
    TLV_CODE_MANUF_COUNTRY = 0x2C
    TLV_CODE_VENDOR_NAME = 0x2D
    TLV_CODE_DIAG_VERSION = 0x2E
    TLV_CODE_SERVICE_TAG = 0x2F
    TLV_CODE_VENDOR_EXT = 0xFD
    TLV_CODE_CRC_32 = 0xFE
    _TLV_DISPLAY_VENDOR_EXT = 1
    TLV_CODE_RJ_CARID = 0x01
    _TLV_INFO_HDR_LEN = 11
    TLV_CODE_PRODUCT_ID = 0x40
    TLV_CODE_HW_VERSION = 0x41
    TLV_CODE_MAIN_FILENAME = 0x42
    TLV_CODE_DTS_FINENAME = 0x43
    TLV_CODE_SY_SERIAL0 = 0x44
    TLV_CODE_SY_SERIAL1 = 0x45
    TLV_CODE_SY_SERIAL2 = 0x46
    TLV_CODE_SY_SERIAL3 = 0x47
    TLV_CODE_PROJECT_ID = 0x48
    TLV_CODE_SETMAC_VERSION = 0x49
    TLV_CODE_EEPROM_TYPE = 0x4A

    @property
    def dstatus(self):
        return self._dstatus

    @property
    def cardid(self):
        return self._cardid

    @property
    def productname(self):
        return self._productname

    @property
    def partnum(self):
        return self._partnum

    @property
    def serialnum(self):
        return self._serialnum

    @property
    def macbase(self):
        return self._macbase

    @property
    def manufdate(self):
        return self._manufdate

    @property
    def deviceversion(self):
        return self._deviceversion

    @property
    def labelrevision(self):
        return self._labelrevision

    @property
    def platformname(self):
        return self._platformname

    @property
    def onieversion(self):
        return self._onieversion

    @property
    def macsize(self):
        return self._macsize

    @property
    def manufname(self):
        return self._manufname

    @property
    def manufcountry(self):
        return self._manufcountry

    @property
    def vendorname(self):
        return self._vendorname

    @property
    def diagname(self):
        return self._diagname

    @property
    def servicetag(self):
        return self._servicetag

    @property
    def vendorext(self):
        return self._vendorext

    @property
    def fanbus(self):
        return self._fanbus

    @property
    def fanloc(self):
        return self._fanloc

    def __init__(self):
        self._cardid = ""
        self._productname = ""
        self._partnum = ""
        self._serialnum = ""
        self._macbase = ""
        self._manufdate = ""
        self._deviceversion = ""
        self._labelrevision = ""
        self._platformname = ""
        self._onieversion = ""
        self._macsize = ""
        self._manufname = ""
        self._manufcountry = ""
        self._vendorname = ""
        self._diagname = ""
        self._servicetag = ""
        self._vendorext = ""
        self._productid = ""
        self._hwversion = ""
        self._mainfilename = ""
        self._dtsfilename = ""
        self._syserial0 = ""
        self._syserial1 = ""
        self._syserial2 = ""
        self._syserial3 = ""
        self._projectid = ""
        self._setmacversion = ""
        self._eepromtype = ""
        self._crc32 = ""
        self._dstatus = 0

    def oniecrc32(self, v):
        data_array = bytearray()
        for x in v:
            data_array.append(ord(x))
        return '0x%08x' % (binascii.crc32(bytes(data_array)) & 0xffffffff)

    def getTLV_BODY(self, type, value):
        x = []
        temp_t = ""
        if type == self.TLV_CODE_MAC_BASE:
            arr = value.split(':')
            for tt in arr:
                temp_t += chr(int(tt, 16))
        elif type == self.TLV_CODE_DEVICE_VERSION:
            temp_t = chr(value)
        elif type == self.TLV_CODE_MAC_SIZE:
            temp_t = chr(value >> 8) + chr(value & 0x00ff)
        else:
            temp_t = value
        x.append(chr(type))
        x.append(chr(len(temp_t)))
        for i in temp_t:
            x.append(i)
        return x

    def generate_ext(self, cardid):
        s = "%08x" % cardid
        ret = ""
        for t in range(0, 4):
            ret += chr(int(s[2 * t:2 * t + 2], 16))
        ret = chr(0x01) + chr(len(ret)) + ret
        return ret

    def generate_value(self, _t):
        ret = []
        for i in self.TLV_INFO_ID_STRING:
            ret.append(i)
        ret.append(chr(self.TLV_INFO_VERSION))
        ret.append(chr(self.TLV_INFO_LENGTH))
        ret.append(chr(self.TLV_INFO_LENGTH_VALUE))

        total_len = 0
        for key in _t:
            x = self.getTLV_BODY(key, _t[key])
            ret += x
            total_len += len(x)
        ret[10] = chr(total_len + 6)

        ret.append(chr(0xFE))
        ret.append(chr(0x04))
        s = self.oniecrc32(''.join(ret))
        for t in range(0, 4):
            ret.append(chr(int(s[2 * t + 2:2 * t + 4], 16)))
        totallen = len(ret)
        if (totallen < 256):
            for left_t in range(0, 256 - totallen):
                ret.append(chr(0x00))
        return (ret, True)

    def decode_tlv(self, e):
        tlv_index = 0
        tlv_end = len(e)
        ret = []
        while tlv_index < tlv_end and (tlv_index + 2 + ord(e[tlv_index + 1])) <= len(e):
            rt = self.decoder(e[tlv_index:tlv_index + 2 + ord(e[tlv_index + 1])])
            ret.append(rt)
            if ord(e[tlv_index]) == self.TLV_CODE_CRC_32:
                break
            tlv_index += ord(e[tlv_index + 1]) + 2
        return ret

    def decode(self, e):
        if e[0:8] != self.TLV_INFO_ID_STRING:
            raise OnietlvException("ONIE tlv head info error,not onie tlv type", -1)
        total_len = (ord(e[9]) << 8) | ord(e[10])
        tlv_index = self._TLV_INFO_HDR_LEN
        tlv_end = self._TLV_INFO_HDR_LEN + total_len
        if tlv_end > len(e):
            raise OnietlvException("ONIE tlv length error", -2)
        ret = []
        ret = self.decode_tlv(e[tlv_index:tlv_end])
        for item in ret:
            if item['code'] == self.TLV_CODE_VENDOR_EXT:
                if item["value"][0:4] == self.TLV_INFO_INIA_ID:
                    rt = self.decode_tlv(item["value"][4:])
                else:
                    rt = self.decode_tlv(item["value"][0:])
                ret.extend(rt)
        return ret

    def decoder(self, t):
        if ord(t[0]) == self.TLV_CODE_PRODUCT_NAME:
            name = "Product Name"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._productname = value
        elif ord(t[0]) == self.TLV_CODE_PART_NUMBER:
            name = "Part Number"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._partnum = value
        elif ord(t[0]) == self.TLV_CODE_SERIAL_NUMBER:
            name = "Serial Number"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._serialnum = value
        elif ord(t[0]) == self.TLV_CODE_MAC_BASE:
            name = "Base MAC Address"
            _len = ord(t[1])
            value = ":".join(['%02X' % ord(T) for T in t[2:8]]).upper()
            self._macbase = value
        elif ord(t[0]) == self.TLV_CODE_MANUF_DATE:
            name = "Manufacture Date"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._manufdate = value
        elif ord(t[0]) == self.TLV_CODE_DEVICE_VERSION:
            name = "Device Version"
            _len = ord(t[1])
            value = ord(t[2])
            self._deviceversion = value
        elif ord(t[0]) == self.TLV_CODE_LABEL_REVISION:
            name = "Label Revision"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._labelrevision = value
        elif ord(t[0]) == self.TLV_CODE_PLATFORM_NAME:
            name = "Platform Name"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._platformname = value
        elif ord(t[0]) == self.TLV_CODE_ONIE_VERSION:
            name = "ONIE Version"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._onieversion = value
        elif ord(t[0]) == self.TLV_CODE_MAC_SIZE:
            name = "MAC Addresses"
            _len = ord(t[1])
            value = str((ord(t[2]) << 8) | ord(t[3]))
            self._macsize = value
        elif ord(t[0]) == self.TLV_CODE_MANUF_NAME:
            name = "Manufacturer"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._manufname = value
        elif ord(t[0]) == self.TLV_CODE_MANUF_COUNTRY:
            name = "Manufacture Country"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._manufcountry = value
        elif ord(t[0]) == self.TLV_CODE_VENDOR_NAME:
            name = "Vendor Name"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._vendorname = value
        elif ord(t[0]) == self.TLV_CODE_DIAG_VERSION:
            name = "Diag Version"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._diagname = value
        elif ord(t[0]) == self.TLV_CODE_SERVICE_TAG:
            name = "Service Tag"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._servicetag = value
        elif ord(t[0]) == self.TLV_CODE_VENDOR_EXT:
            name = "Vendor Extension"
            _len = ord(t[1])
            value = ""
            if self._TLV_DISPLAY_VENDOR_EXT:
                value = t[2:2 + ord(t[1])]
            self._vendorext = value
        elif ord(t[0]) == self.TLV_CODE_CRC_32 and len(t) == 6:
            name = "CRC-32"
            _len = ord(t[1])
            value = "0x%08X" % (((ord(t[2]) << 24) | (
                ord(t[3]) << 16) | (ord(t[4]) << 8) | ord(t[5])),)
            self._crc32 = value
        elif ord(t[0]) == self.TLV_CODE_RJ_CARID:
            name = "Card id"
            _len = ord(t[1])
            value = ""
            for c in t[2:2 + ord(t[1])]:
                value += "%02X" % (ord(c),)
            self._cardid = value
        elif ord(t[0]) == self.TLV_CODE_PRODUCT_ID:
            name = "Product id"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._productid = value
        elif ord(t[0]) == self.TLV_CODE_HW_VERSION:
            name = "Hardware Version"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._hwversion = value
        elif ord(t[0]) == self.TLV_CODE_MAIN_FILENAME:
            name = "Main File Name"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._mainfilename = value
        elif ord(t[0]) == self.TLV_CODE_DTS_FINENAME:
            name = "DTS File Name"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._dtsfilename = value
        elif ord(t[0]) == self.TLV_CODE_SY_SERIAL0:
            name = "SY Serial 0"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._syserial0 = value
        elif ord(t[0]) == self.TLV_CODE_SY_SERIAL1:
            name = "SY Serial 1"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._syserial1 = value
        elif ord(t[0]) == self.TLV_CODE_SY_SERIAL2:
            name = "SY Serial 2"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._syserial2 = value
        elif ord(t[0]) == self.TLV_CODE_SY_SERIAL3:
            name = "SY Serial 3"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._syserial3 = value
        elif ord(t[0]) == self.TLV_CODE_PROJECT_ID:
            name = "Project id"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._projectid = value
        elif ord(t[0]) == self.TLV_CODE_SETMAC_VERSION:
            name = "Setmac Version"
            _len = ord(t[1])
            value = t[2:2 + ord(t[1])]
            self._setmacversion = value
        elif ord(t[0]) == self.TLV_CODE_EEPROM_TYPE:
            name = "EEPROM Type"
            _len = ord(t[1])
            value = ""
            for c in t[2:2 + ord(t[1])]:
                value += "%02X" % (ord(c),)
            self._eepromtype = value
        else:
            name = "Unknown"
            _len = ord(t[1])
            value = ""
            for c in t[2:2 + ord(t[1])]:
                value += "0x%02X " % (ord(c),)
        return {"name": name, "code": ord(t[0]), "value": value, "lens": _len}

    def __str__(self):
        formatstr = "Card id              : %s      \n" \
                    "Product Name         : %s      \n" \
                    "Part Number          : %s      \n" \
                    "Serial Number        : %s      \n" \
                    "Base MAC Address     : %s      \n" \
                    "Manufacture Date     : %s      \n" \
                    "Device Version       : %s      \n" \
                    "Label Revision       : %s      \n" \
                    "Platform Name        : %s      \n" \
                    "ONIE Version         : %s      \n" \
                    "MAC Addresses        : %s      \n" \
                    "Manufacturer         : %s      \n" \
                    "Manufacture Country  : %s      \n" \
                    "Vendor Name          : %s      \n" \
                    "Diag Version         : %s      \n" \
                    "Service Tag          : %s      \n" \
                    "CRC-32               : %s      \n"
        return formatstr % (self._cardid,
                            self._productname,
                            self._partnum,
                            self._serialnum,
                            self._macbase,
                            self._manufdate,
                            self._deviceversion,
                            self._labelrevision,
                            self._platformname,
                            self._onieversion,
                            self._macsize,
                            self._manufname,
                            self._manufcountry,
                            self._vendorname,
                            self._diagname,
                            self._servicetag,
                            self._crc32)