def fields_in(kls, pkt, parent, serial): for name, typ in pkt.Meta.all_field_types: val = pkt.__getitem__( name, parent=parent, serial=serial, allow_bitarray=True, unpacking=False, do_transform=False, ) size_bits = typ.size_bits if callable(size_bits): size_bits = size_bits(pkt) group = pkt.Meta.name_to_group.get(name, pkt.__class__.__name__) if not typ._multiple: yield FieldInfo(name, typ, val, size_bits, group) else: if not isinstance(val, list): raise BadConversion("Expected field to be a list", name=name, val=type(val)) number = typ._multiple if callable(number): number = number(pkt) if len(val) != number: raise BadConversion( "Expected correct number of items", name=name, found=len(val), want=number ) for v in val: yield FieldInfo(name, typ, v, size_bits, group)
def unpack_bytes(kls, data, protocol, pkt_type, protocol_register, unknown_ok=False): if isinstance(data, str): data = binascii.unhexlify(data) if isinstance(data, bytes): b = bitarray(endian="little") b.frombytes(data) data = b prot = protocol_register.get(protocol) if prot is None: raise BadConversion( "Unknown packet protocol", wanted=protocol, available=list(protocol_register) ) Packet, messages_register = prot mkls = None for k in messages_register: if pkt_type in k.by_type: mkls = k.by_type[pkt_type] break if mkls is None: if unknown_ok: mkls = Packet else: raise BadConversion("Unknown message type!", protocol=protocol, pkt_type=pkt_type) return mkls.create(data)
def pack(self, em, meta, val): """Get us the value of the specified member of the enum""" available = [] for name, member in em.__members__.items(): available.append((name, member.value)) if val == name or val == repr( member) or val == member.value or val is member: return member.value if self.allow_unknown: if isinstance(val, int) and not isinstance(val, bool): return val elif isinstance(val, UnknownEnum): return val.value elif isinstance(val, str): m = regexes["unknown_enum"].match(val) if m: return int(m["value"]) if isinstance(val, enum.Enum): raise BadConversion("Can't convert value of wrong Enum", val=val, wanted=em, got=type(val), meta=meta) else: raise BadConversion("Value wasn't a valid enum value", val=val, available=available, meta=meta)
def to_bitarray(self, meta, val): """Return us the val as a bitarray""" if type(val) is bitarray: return val b = bitarray(endian="little") if type(val) is str: # We care about when the single quotes aren't here for when we copy output from `lifx unpack` into a `lifx pack` # This is because we say something like `lifx pack -- '{"thing": "<class 'delfick_project.norms.spec_base.NotSpecified'>"}' # And the quotes cancel each other out if val in ( "<class delfick_project.norms.spec_base.NotSpecified>", "<class 'delfick_project.norms.spec_base.NotSpecified'>", ): val = "" try: b.frombytes(binascii.unhexlify(val)) except binascii.Error as error: raise BadConversion("Failed to turn str into bytes", meta=meta, val=val, error=error) else: try: b.frombytes(val) except TypeError: raise BadConversion("Failed to get a bitarray from the value", value=val, meta=meta) return b
def unpack(self, bitmask, meta, val): """Get us a list of bitmask members contained within the value""" result = [] for v in val: if isinstance(v, bitmask): result.append(v) elif isinstance(v, enum.Enum): raise BadConversion( "Can't convert value of wrong Enum", val=v, wanted=bitmask, got=type(v), meta=meta, ) else: if type(v) is int: for name, member in bitmask.__members__.items(): if type(v) is int and v & member.value: result.append(member) else: found = False for name, member in bitmask.__members__.items(): if v == name or v == repr(member): result.append(member) found = True break if not found: raise BadConversion( "Can't convert value into value from mask", val=v, wanted=bitmask) return set(result)
def pack(self, bitmask, meta, val): """Return us a sum'd value of the bitmask members referred to by val""" final = 0 used = [] for v in val: if isinstance(v, bitmask): if v not in used: final += v.value used.append(v) elif isinstance(v, enum.Enum): raise BadConversion( "Can't convert value of wrong Enum", val=v, wanted=bitmask, got=type(v), meta=meta, ) else: found = False for name, member in bitmask.__members__.items(): if v == name or v == repr(member) or v == member.value: if member not in used: final += member.value used.append(member) found = True break if not found: raise BadConversion("Can't convert value into mask", mask=bitmask, got=v) return final
def to_bitarray(self): fmt = self.typ.struct_format val = self.value if val is sb.NotSpecified: raise BadConversion("Cannot pack an unspecified value", got=val, field=self.name, group=self.group, typ=self.typ) if type(val) is bitarray: return val if type(fmt) is str: return self.struct_format(fmt, val) elif fmt is bool: if type(val) is not bool: raise BadConversion( "Trying to convert a non boolean into 1 bit", got=val, group=self.group, field=self.name) return (bitarray("0", endian="little") if val is False else bitarray("1", endian="little")) else: b = bitarray(endian="little") b.frombytes(val) return b
def unpack(self, em, meta, val): """Get us a member of the enum""" if isinstance(val, em): return val elif isinstance(val, enum.Enum): raise BadConversion("Can't convert value of wrong Enum", val=val, wanted=em, got=type(val), meta=meta) available = [] for name, member in em.__members__.items(): available.append((name, member.value)) if val == name or val == repr(member) or val == member.value: return member if self.allow_unknown: if isinstance(val, int) and not isinstance(val, bool): return UnknownEnum(val) elif isinstance(val, UnknownEnum): return val elif isinstance(val, str): m = regexes["unknown_enum"].match(val) if m: return UnknownEnum(int(m["value"])) # Only here if didn't match any members raise BadConversion( "Value is not a valid value of the enum", val=val, enum=em, available=available, meta=meta, )
def get_message_type(kls, data, protocol_register): """ Given a ProtocolRegister and some data (bytes or dictionary) return ``(pkt_type, Packet, kls, pkt)`` pkt_type The number assigned to this payload type. This code assumes the unpacked data has a ``pkt_type`` property that is retrieved for this. Packet The ``parent_packet`` class for the protocol the data represents. If ``data`` is bytes, get the integer from bits 16 to 28 as protocol. Use this number in protocol register to find the ``parent_packet``. kls The payload class representing this protocol and pkt_type. pkt An instance of the ``parent_packet`` from the data. """ if type(data) is str: data = binascii.unhexlify(data) if type(data) is bytes: b = bitarray(endian="little") b.frombytes(data) data = b protocol = struct.unpack("<H", data[16:16 + 12].tobytes())[0] prot = protocol_register.get(protocol) if prot is None: raise BadConversion("Unknown packet protocol", wanted=protocol, available=list(protocol_register)) Packet, messages_register = prot pkt = Packet.unpack(data) message_type = dictobj.__getitem__(pkt, "pkt_type") k = None for k in (messages_register or [kls]): if message_type in k.by_type: if pkt.payload is NotSpecified and k.by_type[ message_type].Payload.Meta.field_types: raise BadConversion("packet had no payload", got=repr(pkt)) break if k is None: return message_type, Packet, None, pkt return message_type, Packet, k.by_type.get(message_type), pkt
def packet_type_from_dict(kls, data): if "protocol" in data: protocol = data["protocol"] elif "frame_header" in data and "protocol" in data["frame_header"]: protocol = data["frame_header"]["protocol"] else: raise BadConversion("Couldn't work out protocol from dictionary", got=data) if "pkt_type" in data: pkt_type = data["pkt_type"] elif "pkt_type" in data.get("protocol_header", {}): pkt_type = data["protocol_header"]["pkt_type"] else: raise BadConversion("Couldn't work out pkt_type from dictionary", got=data) return protocol, pkt_type
def unpackd(self): val = self.val typ = self.typ fmt = typ.struct_format if fmt is None: return val.tobytes() if fmt is bool and self.size_bits is 1: return False if val.to01() is '0' else True if len(val) < typ.original_size: padding = bitarray('0' * (typ.original_size - len(val)), endian="little") if getattr(self.typ, "left_cut", False): val = padding + val else: val = val + padding try: return struct.unpack(typ.struct_format, val.tobytes())[0] except (struct.error, TypeError, ValueError) as error: raise BadConversion("Failed to unpack field", group=self.group, field=self.name, typ=typ, val=val.to01(), error=error)
def packet_type(kls, data): if isinstance(data, dict): return kls.packet_type_from_dict(data) elif isinstance(data, bytes): return kls.packet_type_from_bytes(data) elif isinstance(data, bitarray): return kls.packet_type_from_bitarray(data) else: raise BadConversion("Can't determine packet type from data", got=data)
def pack_payload(kls, pkt_type, data, messages_register=None): """ Given some payload data as a dictionary and it's ``pkt_type``, return a hexlified string of the payload. """ for k in messages_register or [kls]: if int(pkt_type) in k.by_type: return k.by_type[int(pkt_type)].Payload.create(data).pack() raise BadConversion("Unknown message type!", pkt_type=pkt_type)
def packet_type_from_bytes(kls, data): if len(data) < 4: raise BadConversion("Data is too small to be a LIFX packet", got=len(data)) b = bitarray(endian="little") b.frombytes(data[2:4]) protbts = b[:12].tobytes() protocol = protbts[0] + (protbts[1] << 8) pkt_type = None if protocol == 1024: if len(data) < 36: raise BadConversion( "Data is too small to be a LIFX packet", need_atleast=36, got=len(data) ) pkt_type = data[32] + (data[33] << 8) return protocol, pkt_type
def pack_payload(kls, message_type, data, messages_register=None): """ Given some payload data as a dictionary and it's ``pkt_type``, return a hexlified string of the payload. """ for k in (messages_register or [kls]): if int(message_type) in k.by_type: return k.by_type[int(message_type)].Payload.normalise( Meta.empty(), data).pack() raise BadConversion("Unknown message type!", pkt_type=message_type)
def pack(self, em, meta, val): """Get us the value of the specified member of the enum""" available = [] for name, member in em.__members__.items(): available.append((name, member.value)) if val == name or val == repr( member) or val == member.value or val is member: return member.value if isinstance(val, enum.Enum): raise BadConversion("Can't convert value of wrong Enum", val=val, wanted=em, got=type(val), meta=meta) else: raise BadConversion("Value wasn't a valid enum value", val=val, available=available, meta=meta)
def packet_type_from_bitarray(kls, data): if len(data) < 28: raise BadConversion("Data is too small to be a LIFX packet", got=len(data) // 8) b = bitarray(endian="little") b.extend(data[16 : 16 + 12]) protbts = b.tobytes() protocol = protbts[0] + (protbts[1] << 8) pkt_type = None if protocol == 1024: if len(data) < 288: raise BadConversion( "Data is too small to be a LIFX packet", need_atleast=36, got=len(data) // 8 ) ptbts = data[256 : 256 + 16].tobytes() pkt_type = ptbts[0] + (ptbts[1] << 8) return protocol, pkt_type
def pack(kls, data, protocol_register, unknown_ok=False): """ Return a hexlified string of the data. This uses ``pkt_type`` and ``protocol`` in the data, along with the protocol_register to find the appropriate class to use to perform the packing. """ if "pkt_type" in data: message_type = data["pkt_type"] elif "pkt_type" in data.get("protocol_header", {}): message_type = data["protocol_header"]["pkt_type"] else: raise BadConversion( "Don't know how to pack this dictionary, it doesn't specify a pkt_type!" ) if "protocol" in data: protocol = data["protocol"] elif "frame_header" in data and "protocol" in data["frame_header"]: protocol = data["frame_header"]["protocol"] else: raise BadConversion( "Don't know how to pack this dictionary, it doesn't specify a protocol!" ) prot = protocol_register.get(protocol) if prot is None: raise BadConversion("Unknown packet protocol", wanted=protocol, available=list(protocol_register)) Packet, messages_register = prot for k in (messages_register or [kls]): if message_type in k.by_type: return k.by_type[message_type].normalise(Meta.empty(), data).pack() if unknown_ok: return Packet.normalise(Meta.empty(), data).pack() raise BadConversion("Unknown message type!", pkt_type=message_type)
def struct_format(self, fmt, val): b = bitarray(endian="little") try: if val is Optional: val = 0 b.frombytes(struct.pack(fmt, val)) except struct.error as error: raise BadConversion("Failed trying to convert a value", val=val, fmt=fmt, error=error, group=self.group, name=self.name) return b
def unpack(self, em, meta, val): """Get us a member of the enum""" if isinstance(val, em): return val elif isinstance(val, enum.Enum): raise BadConversion("Can't convert value of wrong Enum", val=val, wanted=em, got=type(val), meta=meta) available = [] for name, member in em.__members__.items(): available.append((name, member.value)) if val == name or val == repr(member) or val == member.value: return member # Only here if didn't match any members raise BadConversion("Value is not a valid value of the enum", val=val, enum=em, available=available, meta=meta)
def unpack(kls, data, protocol_register, unknown_ok=False): """ Return a fully resolved packet instance from the data and protocol_register. unknown_ok Whether we return an instance of the parent_packet with unresolved payload if we don't have a payload class, or if we raise an error """ message_type, Packet, PacketKls, pkt = kls.get_message_type( data, protocol_register) if PacketKls: return kls.unpack_pkt(PacketKls, pkt) elif unknown_ok: return pkt raise BadConversion("Unknown message type!", pkt_type=message_type)
def get_packet_type(kls, data, protocol_register): """ Given a ProtocolRegister and some data (bytes or dictionary) return ``(protocol, pkt_type, Packet, kls, data)`` protocol The number specifying the "protocol" of this message. This is likely to always be 1024. pkt_type The number assigned to this payload type. This code assumes the unpacked data has a ``pkt_type`` property that is retrieved for this. Packet The ``parent_packet`` class for the protocol the data represents. If ``data`` is bytes, get the integer from bits 16 to 28 as protocol. Use this number in protocol register to find the ``parent_packet``. kls The payload class representing this protocol and pkt_type. This is None if the protocol/pkt_type pair is unknown data If the data was a dictionary, bytes or bitarray then returned as is if the data was a str, then we return it as unhexlified bytes """ if isinstance(data, str): data = binascii.unhexlify(data) protocol, pkt_type = PacketTypeExtractor.packet_type(data) prot = protocol_register.get(protocol) if prot is None: raise BadConversion( "Unknown packet protocol", wanted=protocol, available=list(protocol_register) ) Packet, messages_register = prot mkls = None for k in messages_register: if pkt_type in k.by_type: mkls = k.by_type[pkt_type] break return protocol, pkt_type, Packet, mkls, data
def val_to_bitarray(val, doing): """Convert a value into a bitarray""" if type(val) is bitarray: return val if type(val) is str: val = binascii.unhexlify(val.encode()) if type(val) is not bytes: raise BadConversion("Couldn't get bitarray from a value", value=val, doing=doing) b = bitarray(endian="little") b.frombytes(val) return b
def pack(kls, data, protocol_register, unknown_ok=False): """ Return a hexlified string of the data. This uses ``pkt_type`` and ``protocol`` in the data, along with the protocol_register to find the appropriate class to use to perform the packing. """ protocol, pkt_type, Packet, PacketKls, data = kls.get_packet_type(data, protocol_register) if PacketKls is None: if not unknown_ok: raise BadConversion("Unknown message type!", protocol=protocol, pkt_type=pkt_type) PacketKls = Packet return PacketKls.create(data).pack()
def create(kls, data, protocol_register, unknown_ok=False): """ Return a fully resolved packet instance from the data and protocol_register. unknown_ok Whether we return an instance of the parent_packet with unresolved payload if we don't have a payload class, or if we raise an error """ protocol, pkt_type, Packet, PacketKls, data = kls.get_packet_type(data, protocol_register) if PacketKls: return PacketKls.create(data) if unknown_ok: return Packet.create(data) raise BadConversion("Unknown message type!", protocol=protocol, pkt_type=pkt_type)
def _spec(self, pkt, unpacking=False): """ Return a delfick_project.norms spec object for normalising values before and after packing/unpacking """ spec = self.spec_from_conversion(pkt, unpacking) if spec is not None: if self._dynamic is sb.NotSpecified: return spec else: return self.dynamic_wrapper(spec, pkt, unpacking=unpacking) raise BadConversion( "Cannot create a specification for this conversion", conversion=self.conversion, type=self.__class__.__name__, )
def pack(kls, pkt, payload=None, parent=None, serial=None): """ This uses the ``Meta`` on the packet to determine the order of all the fields and uses that to extract values from the object and uses the type object for each field to convert the value into a bitarray object. Finally, the bitarray object is essentially concated together to create one final bitarray object. This code assumes the packet has little endian. If ``payload`` is provided and this packet is a ``parent_packet`` and it's last field has a ``message_type`` property of 0, then that payload is converted into a bitarray and added to the end of the result. """ final = bitarray(endian="little") for info in kls.fields_in(pkt, parent, serial): result = info.to_sized_bitarray() if result is None: raise BadConversion("Failed to convert field into a bitarray", field=info.as_dict()) final += result # If this is a parent packet with a Payload of message_type 0 # Then this means we have no payload fields and so must append # The entire payload at the end # As opposed to individual fields in the payload one at a time if getattr(pkt, "parent_packet", False) and pkt.Meta.field_types: name, typ = pkt.Meta.field_types[-1] if getattr(typ, "message_type", None) is 0: final += val_to_bitarray( payload or pkt[name], doing="Adding payload when packing a packet") return final