""" Uses coreapi_data for message and TLV types, and defines TLV data types and objects used for parsing and building CORE API messages. CORE API messaging is leveraged for communication with the GUI. """ import socket import struct from enum import Enum from core.enumerations import ConfigTlvs from core.enumerations import EventTlvs from core.enumerations import EventTypes from core.enumerations import ExceptionTlvs from core.enumerations import ExecuteTlvs from core.enumerations import FileTlvs from core.enumerations import InterfaceTlvs from core.enumerations import LinkTlvs from core.enumerations import MessageFlags from core.enumerations import MessageTypes from core.enumerations import NodeTlvs from core.enumerations import RegisterTlvs from core.enumerations import SessionTlvs from core.misc import log from core.misc.ipaddress import IpAddress from core.misc.ipaddress import MacAddress logger = log.get_logger(__name__) class CoreTlvData(object): """ Helper base class used for packing and unpacking values using struct. """ # format string for packing data data_format = None # python data type for the data data_type = None # pad length for data after packing pad_len = None @classmethod def pack(cls, value): data = struct.pack(cls.data_format, value) length = len(data) - cls.pad_len return length, data @classmethod def unpack(cls, data): return struct.unpack(cls.data_format, data)[0] @classmethod def pack_string(cls, value): return cls.pack(cls.from_string(value)) @classmethod def from_string(cls, value): return cls.data_type(value) class CoreTlvDataObj(CoreTlvData): """ Helper class for packing custom object data. """ @classmethod def pack(cls, obj): data = struct.pack(cls.data_format, cls.get_value(obj)) length = len(data) - cls.pad_len return length, data @classmethod def unpack(cls, data): return cls.new_obj(struct.unpack(cls.data_format, data)[0]) @staticmethod def get_value(obj): raise NotImplementedError @staticmethod def new_obj(obj): raise NotImplementedError class CoreTlvDataUint16(CoreTlvData): """ Helper class for packing uint16 data. """ data_format = "!H" data_type = int pad_len = 0 class CoreTlvDataUint32(CoreTlvData): """ Helper class for packing uint32 data. """ data_format = "!2xI" data_type = int pad_len = 2 class CoreTlvDataUint64(CoreTlvData): """ Helper class for packing uint64 data. """ data_format = "!2xQ" data_type = long pad_len = 2 class CoreTlvDataString(CoreTlvData): """ Helper class for packing string data. """ data_type = str @classmethod def pack(cls, value): if not isinstance(value, str): raise ValueError("value not a string: %s" % value) if len(value) < 256: header_len = CoreTlv.header_len else: header_len = CoreTlv.long_header_len pad_len = -(header_len + len(value)) % 4 return len(value), value + '\0' * pad_len @classmethod def unpack(cls, data): return data.rstrip('\0') class CoreTlvDataUint16List(CoreTlvData): """ List of unsigned 16-bit values. """ data_type = tuple data_format = "!H" @classmethod def pack(cls, values): if not isinstance(values, tuple): raise ValueError("value not a tuple: %s" % values) data = "" for value in values: data += struct.pack(cls.data_format, value) pad_len = -(CoreTlv.header_len + len(data)) % 4 return len(data), data + "\0" * pad_len @classmethod def unpack(cls, data): data_format = "!%dH" % (len(data) / 2) return struct.unpack(data_format, data) @classmethod def from_string(cls, value): return tuple(map(lambda (x): int(x), value.split())) class CoreTlvDataIpv4Addr(CoreTlvDataObj): data_type = IpAddress.from_string data_format = "!2x4s" pad_len = 2 @staticmethod def get_value(obj): return obj.addr @staticmethod def new_obj(value): return IpAddress(af=socket.AF_INET, address=value) class CoreTlvDataIPv6Addr(CoreTlvDataObj): data_format = "!16s2x" data_type = IpAddress.from_string pad_len = 2 @staticmethod def get_value(obj): return obj.addr @staticmethod def new_obj(value): return IpAddress(af=socket.AF_INET6, address=value) class CoreTlvDataMacAddr(CoreTlvDataObj): data_format = "!2x8s" data_type = MacAddress.from_string pad_len = 2 @staticmethod def get_value(obj): # extend to 64 bits return '\0\0' + obj.addr @staticmethod def new_obj(value): # only use 48 bits return MacAddress(address=value[2:]) class CoreTlv(object): header_format = "!BB" header_len = struct.calcsize(header_format) long_header_format = "!BBH" long_header_len = struct.calcsize(long_header_format) tlv_type_map = Enum tlv_data_class_map = {} def __init__(self, tlv_type, tlv_data): self.tlv_type = tlv_type if tlv_data: try: self.value = self.tlv_data_class_map[self.tlv_type].unpack(tlv_data) except KeyError: self.value = tlv_data else: self.value = None @classmethod def unpack(cls, data): """ parse data and return unpacked class. """ tlv_type, tlv_len = struct.unpack(cls.header_format, data[:cls.header_len]) header_len = cls.header_len if tlv_len == 0: tlv_type, zero, tlv_len = struct.unpack(cls.long_header_format, data[:cls.long_header_len]) header_len = cls.long_header_len tlv_size = header_len + tlv_len tlv_size += -tlv_size % 4 # for 32-bit alignment return cls(tlv_type, data[header_len:tlv_size]), data[tlv_size:] @classmethod def pack(cls, tlv_type, value): tlv_len, tlv_data = cls.tlv_data_class_map[tlv_type].pack(value) if tlv_len < 256: hdr = struct.pack(cls.header_format, tlv_type, tlv_len) else: hdr = struct.pack(cls.long_header_format, tlv_type, 0, tlv_len) return hdr + tlv_data @classmethod def pack_string(cls, tlv_type, value): return cls.pack(tlv_type, cls.tlv_data_class_map[tlv_type].from_string(value)) def type_str(self): try: return self.tlv_type_map(self.tlv_type).name except ValueError: return "unknown tlv type: %s" % str(self.tlv_type) def __str__(self): return "%s " % (self.__class__.__name__, self.type_str(), self.value) class CoreNodeTlv(CoreTlv): tlv_type_map = NodeTlvs tlv_data_class_map = { NodeTlvs.NUMBER.value: CoreTlvDataUint32, NodeTlvs.TYPE.value: CoreTlvDataUint32, NodeTlvs.NAME.value: CoreTlvDataString, NodeTlvs.IP_ADDRESS.value: CoreTlvDataIpv4Addr, NodeTlvs.MAC_ADDRESS.value: CoreTlvDataMacAddr, NodeTlvs.IP6_ADDRESS.value: CoreTlvDataIPv6Addr, NodeTlvs.MODEL.value: CoreTlvDataString, NodeTlvs.EMULATION_SERVER.value: CoreTlvDataString, NodeTlvs.SESSION.value: CoreTlvDataString, NodeTlvs.X_POSITION.value: CoreTlvDataUint16, NodeTlvs.Y_POSITION.value: CoreTlvDataUint16, NodeTlvs.CANVAS.value: CoreTlvDataUint16, NodeTlvs.EMULATION_ID.value: CoreTlvDataUint32, NodeTlvs.NETWORK_ID.value: CoreTlvDataUint32, NodeTlvs.SERVICES.value: CoreTlvDataString, NodeTlvs.LATITUDE.value: CoreTlvDataString, NodeTlvs.LONGITUDE.value: CoreTlvDataString, NodeTlvs.ALTITUDE.value: CoreTlvDataString, NodeTlvs.ICON.value: CoreTlvDataString, NodeTlvs.OPAQUE.value: CoreTlvDataString, } class CoreLinkTlv(CoreTlv): tlv_type_map = LinkTlvs tlv_data_class_map = { LinkTlvs.N1_NUMBER.value: CoreTlvDataUint32, LinkTlvs.N2_NUMBER.value: CoreTlvDataUint32, LinkTlvs.DELAY.value: CoreTlvDataUint64, LinkTlvs.BANDWIDTH.value: CoreTlvDataUint64, LinkTlvs.PER.value: CoreTlvDataString, LinkTlvs.DUP.value: CoreTlvDataString, LinkTlvs.JITTER.value: CoreTlvDataUint64, LinkTlvs.MER.value: CoreTlvDataUint16, LinkTlvs.BURST.value: CoreTlvDataUint16, LinkTlvs.SESSION.value: CoreTlvDataString, LinkTlvs.MBURST.value: CoreTlvDataUint16, LinkTlvs.TYPE.value: CoreTlvDataUint32, LinkTlvs.GUI_ATTRIBUTES.value: CoreTlvDataString, LinkTlvs.UNIDIRECTIONAL.value: CoreTlvDataUint16, LinkTlvs.EMULATION_ID.value: CoreTlvDataUint32, LinkTlvs.NETWORK_ID.value: CoreTlvDataUint32, LinkTlvs.KEY.value: CoreTlvDataUint32, LinkTlvs.INTERFACE1_NUMBER.value: CoreTlvDataUint16, LinkTlvs.INTERFACE1_IP4.value: CoreTlvDataIpv4Addr, LinkTlvs.INTERFACE1_IP4_MASK.value: CoreTlvDataUint16, LinkTlvs.INTERFACE1_MAC.value: CoreTlvDataMacAddr, LinkTlvs.INTERFACE1_IP6.value: CoreTlvDataIPv6Addr, LinkTlvs.INTERFACE1_IP6_MASK.value: CoreTlvDataUint16, LinkTlvs.INTERFACE2_NUMBER.value: CoreTlvDataUint16, LinkTlvs.INTERFACE2_IP4.value: CoreTlvDataIpv4Addr, LinkTlvs.INTERFACE2_IP4_MASK.value: CoreTlvDataUint16, LinkTlvs.INTERFACE2_MAC.value: CoreTlvDataMacAddr, LinkTlvs.INTERFACE2_IP6.value: CoreTlvDataIPv6Addr, LinkTlvs.INTERFACE2_IP6_MASK.value: CoreTlvDataUint16, LinkTlvs.INTERFACE1_NAME.value: CoreTlvDataString, LinkTlvs.INTERFACE2_NAME.value: CoreTlvDataString, LinkTlvs.OPAQUE.value: CoreTlvDataString, } class CoreExecuteTlv(CoreTlv): tlv_type_map = ExecuteTlvs tlv_data_class_map = { ExecuteTlvs.NODE.value: CoreTlvDataUint32, ExecuteTlvs.NUMBER.value: CoreTlvDataUint32, ExecuteTlvs.TIME.value: CoreTlvDataUint32, ExecuteTlvs.COMMAND.value: CoreTlvDataString, ExecuteTlvs.RESULT.value: CoreTlvDataString, ExecuteTlvs.STATUS.value: CoreTlvDataUint32, ExecuteTlvs.SESSION.value: CoreTlvDataString, } class CoreRegisterTlv(CoreTlv): tlv_type_map = RegisterTlvs tlv_data_class_map = { RegisterTlvs.WIRELESS.value: CoreTlvDataString, RegisterTlvs.MOBILITY.value: CoreTlvDataString, RegisterTlvs.UTILITY.value: CoreTlvDataString, RegisterTlvs.EXECUTE_SERVER.value: CoreTlvDataString, RegisterTlvs.GUI.value: CoreTlvDataString, RegisterTlvs.EMULATION_SERVER.value: CoreTlvDataString, RegisterTlvs.SESSION.value: CoreTlvDataString, } class CoreConfigTlv(CoreTlv): tlv_type_map = ConfigTlvs tlv_data_class_map = { ConfigTlvs.NODE.value: CoreTlvDataUint32, ConfigTlvs.OBJECT.value: CoreTlvDataString, ConfigTlvs.TYPE.value: CoreTlvDataUint16, ConfigTlvs.DATA_TYPES.value: CoreTlvDataUint16List, ConfigTlvs.VALUES.value: CoreTlvDataString, ConfigTlvs.CAPTIONS.value: CoreTlvDataString, ConfigTlvs.BITMAP.value: CoreTlvDataString, ConfigTlvs.POSSIBLE_VALUES.value: CoreTlvDataString, ConfigTlvs.GROUPS.value: CoreTlvDataString, ConfigTlvs.SESSION.value: CoreTlvDataString, ConfigTlvs.INTERFACE_NUMBER.value: CoreTlvDataUint16, ConfigTlvs.NETWORK_ID.value: CoreTlvDataUint32, ConfigTlvs.OPAQUE.value: CoreTlvDataString, } class CoreFileTlv(CoreTlv): tlv_type_map = FileTlvs tlv_data_class_map = { FileTlvs.NODE.value: CoreTlvDataUint32, FileTlvs.NAME.value: CoreTlvDataString, FileTlvs.MODE.value: CoreTlvDataString, FileTlvs.NUMBER.value: CoreTlvDataUint16, FileTlvs.TYPE.value: CoreTlvDataString, FileTlvs.SOURCE_NAME.value: CoreTlvDataString, FileTlvs.SESSION.value: CoreTlvDataString, FileTlvs.DATA.value: CoreTlvDataString, FileTlvs.COMPRESSED_DATA.value: CoreTlvDataString, } class CoreInterfaceTlv(CoreTlv): tlv_type_map = InterfaceTlvs tlv_data_class_map = { InterfaceTlvs.NODE.value: CoreTlvDataUint32, InterfaceTlvs.NUMBER.value: CoreTlvDataUint16, InterfaceTlvs.NAME.value: CoreTlvDataString, InterfaceTlvs.IP_ADDRESS.value: CoreTlvDataIpv4Addr, InterfaceTlvs.MASK.value: CoreTlvDataUint16, InterfaceTlvs.MAC_ADDRESS.value: CoreTlvDataMacAddr, InterfaceTlvs.IP6_ADDRESS.value: CoreTlvDataIPv6Addr, InterfaceTlvs.IP6_MASK.value: CoreTlvDataUint16, InterfaceTlvs.TYPE.value: CoreTlvDataUint16, InterfaceTlvs.SESSION.value: CoreTlvDataString, InterfaceTlvs.STATE.value: CoreTlvDataUint16, InterfaceTlvs.EMULATION_ID.value: CoreTlvDataUint32, InterfaceTlvs.NETWORK_ID.value: CoreTlvDataUint32, } class CoreEventTlv(CoreTlv): tlv_type_map = EventTlvs tlv_data_class_map = { EventTlvs.NODE.value: CoreTlvDataUint32, EventTlvs.TYPE.value: CoreTlvDataUint32, EventTlvs.NAME.value: CoreTlvDataString, EventTlvs.DATA.value: CoreTlvDataString, EventTlvs.TIME.value: CoreTlvDataString, EventTlvs.SESSION.value: CoreTlvDataString, } class CoreSessionTlv(CoreTlv): tlv_type_map = SessionTlvs tlv_data_class_map = { SessionTlvs.NUMBER.value: CoreTlvDataString, SessionTlvs.NAME.value: CoreTlvDataString, SessionTlvs.FILE.value: CoreTlvDataString, SessionTlvs.NODE_COUNT.value: CoreTlvDataString, SessionTlvs.DATE.value: CoreTlvDataString, SessionTlvs.THUMB.value: CoreTlvDataString, SessionTlvs.USER.value: CoreTlvDataString, SessionTlvs.OPAQUE.value: CoreTlvDataString, } class CoreExceptionTlv(CoreTlv): tlv_type_map = ExceptionTlvs tlv_data_class_map = { ExceptionTlvs.NODE.value: CoreTlvDataUint32, ExceptionTlvs.SESSION.value: CoreTlvDataString, ExceptionTlvs.LEVEL.value: CoreTlvDataUint16, ExceptionTlvs.SOURCE.value: CoreTlvDataString, ExceptionTlvs.DATE.value: CoreTlvDataString, ExceptionTlvs.TEXT.value: CoreTlvDataString, ExceptionTlvs.OPAQUE.value: CoreTlvDataString, } class CoreMessage(object): header_format = "!BBH" header_len = struct.calcsize(header_format) message_type = None flag_map = MessageFlags tlv_class = CoreTlv def __init__(self, flags, hdr, data): self.raw_message = hdr + data self.flags = flags self.tlv_data = {} self.parse_data(data) @classmethod def unpack_header(cls, data): """ parse data and return (message_type, message_flags, message_len). :param str data: data to parse :return: unpacked tuple :rtype: tuple """ message_type, message_flags, message_len = struct.unpack(cls.header_format, data[:cls.header_len]) return message_type, message_flags, message_len @classmethod def pack(cls, message_flags, tlv_data): header = struct.pack(cls.header_format, cls.message_type, message_flags, len(tlv_data)) return header + tlv_data def add_tlv_data(self, key, value): if key in self.tlv_data: raise KeyError("key already exists: %s (val=%s)" % (key, value)) self.tlv_data[key] = value def get_tlv(self, tlv_type): return self.tlv_data.get(tlv_type) def parse_data(self, data): while data: tlv, data = self.tlv_class.unpack(data) self.add_tlv_data(tlv.tlv_type, tlv.value) def pack_tlv_data(self): """ Opposite of parse_data(). Return packed TLV data using self.tlv_data dict. Used by repack(). :return: packed data :rtype: str """ tlv_data = "" keys = sorted(self.tlv_data.keys()) for key in keys: value = self.tlv_data[key] tlv_data += self.tlv_class.pack(key, value) return tlv_data def repack(self): """ Invoke after updating self.tlv_data[] to rebuild self.raw_message. Useful for modifying a message that has been parsed, before sending the raw data again. :return: nothing """ tlv_data = self.pack_tlv_data() self.raw_message = self.pack(self.flags, tlv_data) def type_str(self): try: return MessageTypes(self.message_type).name except ValueError: return "unknown message type: %s" % str(self.message_type) def flag_str(self): message_flags = [] flag = 1L while True: if self.flags & flag: try: message_flags.append(self.flag_map(flag).name) except ValueError: message_flags.append("0x%x" % flag) flag <<= 1 if not (self.flags & ~(flag - 1)): break return "0x%x <%s>" % (self.flags, " | ".join(message_flags)) def __str__(self): result = "%s " % (self.__class__.__name__, self.type_str(), self.flag_str()) for key, value in self.tlv_data.iteritems(): try: tlv_type = self.tlv_class.tlv_type_map(key).name except ValueError: tlv_type = "tlv type %s" % key result += "\n %s: %s" % (tlv_type, value) return result def node_numbers(self): """ Return a list of node numbers included in this message. """ number1 = None number2 = None # not all messages have node numbers if self.message_type == MessageTypes.NODE.value: number1 = self.get_tlv(NodeTlvs.NUMBER.value) elif self.message_type == MessageTypes.LINK.value: number1 = self.get_tlv(LinkTlvs.N1_NUMBER.value) number2 = self.get_tlv(LinkTlvs.N2_NUMBER.value) elif self.message_type == MessageTypes.EXECUTE.value: number1 = self.get_tlv(ExecuteTlvs.NODE.value) elif self.message_type == MessageTypes.CONFIG.value: number1 = self.get_tlv(ConfigTlvs.NODE.value) elif self.message_type == MessageTypes.FILE.value: number1 = self.get_tlv(FileTlvs.NODE.value) elif self.message_type == MessageTypes.INTERFACE.value: number1 = self.get_tlv(InterfaceTlvs.NODE.value) elif self.message_type == MessageTypes.EVENT.value: number1 = self.get_tlv(EventTlvs.NODE) result = [] if number1: result.append(number1) if number2: result.append(number2) return result def session_numbers(self): """ Return a list of session numbers included in this message. """ result = [] if self.message_type == MessageTypes.SESSION.value: sessions = self.get_tlv(SessionTlvs.NUMBER.value) elif self.message_type == MessageTypes.EXCEPTION.value: sessions = self.get_tlv(ExceptionTlvs.SESSION.value) else: # All other messages share TLV number 0xA for the session number(s). sessions = self.get_tlv(NodeTlvs.SESSION.value) if sessions: for session_id in sessions.split("|"): result.append(int(session_id)) return result class CoreNodeMessage(CoreMessage): message_type = MessageTypes.NODE.value tlv_class = CoreNodeTlv class CoreLinkMessage(CoreMessage): message_type = MessageTypes.LINK.value tlv_class = CoreLinkTlv class CoreExecMessage(CoreMessage): message_type = MessageTypes.EXECUTE.value tlv_class = CoreExecuteTlv class CoreRegMessage(CoreMessage): message_type = MessageTypes.REGISTER.value tlv_class = CoreRegisterTlv class CoreConfMessage(CoreMessage): message_type = MessageTypes.CONFIG.value tlv_class = CoreConfigTlv class CoreFileMessage(CoreMessage): message_type = MessageTypes.FILE.value tlv_class = CoreFileTlv class CoreIfaceMessage(CoreMessage): message_type = MessageTypes.INTERFACE.value tlv_class = CoreInterfaceTlv class CoreEventMessage(CoreMessage): message_type = MessageTypes.EVENT.value tlv_class = CoreEventTlv class CoreSessionMessage(CoreMessage): message_type = MessageTypes.SESSION.value tlv_class = CoreSessionTlv class CoreExceptionMessage(CoreMessage): message_type = MessageTypes.EXCEPTION.value tlv_class = CoreExceptionTlv CLASS_MAP = { MessageTypes.NODE.value: CoreNodeMessage, MessageTypes.LINK.value: CoreLinkMessage, MessageTypes.EXECUTE.value: CoreExecMessage, MessageTypes.REGISTER.value: CoreRegMessage, MessageTypes.CONFIG.value: CoreConfMessage, MessageTypes.FILE.value: CoreFileMessage, MessageTypes.INTERFACE.value: CoreIfaceMessage, MessageTypes.EVENT.value: CoreEventMessage, MessageTypes.SESSION.value: CoreSessionMessage, MessageTypes.EXCEPTION.value: CoreExceptionMessage, } def str_to_list(value): """ Helper to convert pipe-delimited string ("a|b|c") into a list (a, b, c). :param str value: string to convert :return: converted list :rtype: list """ if value is None: return None return value.split("|") def state_name(value): """ Helper to convert state number into state name using event types. :param int value: state value to derive name from :return: state name :rtype: str """ try: value = EventTypes(value).name except ValueError: value = "unknown" return value