def pkt_from_bitarray(kls, pkt_kls, value): i = 0 final = pkt_kls() for name, typ in pkt_kls.Meta.all_field_types: single_size_bits = typ.size_bits if callable(single_size_bits): single_size_bits = single_size_bits(final) multiple = typ._multiple size_bits = single_size_bits if multiple: if callable(multiple): multiple = multiple(final) size_bits *= multiple val = value[i : i + size_bits] i += size_bits if multiple: if typ.struct_format: res = [] j = 0 for _ in range(multiple): v = val[j : j + single_size_bits] j += single_size_bits info = BitarraySlice(name, typ, v, single_size_bits, pkt_kls.__name__) res.append(info.unpackd) val = res final[name] = val else: info = BitarraySlice(name, typ, val, size_bits, pkt_kls.__name__) dictobj.__setitem__(final, info.name, info.unpackd) return final, i
def _set_group_item(self, key, val): """ Used by __setitem__ to put a group field onto the packet Ensures: * Empty payload group is only filled by str/bytes/bitarray * sb.NotSpecified translates to setting that for all the fields in the group * When val is an instance of the type for this group, we transfer values directly * Non empty payload groups is filled by something with items() so we can then set the values for the fields in the group """ typ = self.Meta.field_types_dict.get(key) if getattr(typ, "message_type", None) == 0: # Message_type of 0 indicates an empty Payload # So we store the whole thing as is on the packet # We also make sure this is a str (hexlified bytes), bytes or bitarray # __getitem__ has the opposite logic to grab this group as is if type(val) not in (bytes, bitarray, str): msg = "Setting non bytes payload on a packet that doesn't know what fields it's payload has" raise ValueError("{0}\tkey={1}\tgot={2}".format( msg, key, repr(val))) dictobj.__setitem__(self, key, val) return if val is sb.NotSpecified: for field in self.Meta.groups[key]: if field in self: self[field] = sb.NotSpecified return # if we're setting a group from an instance of the group # then just steal the raw values without the transform dance if hasattr(typ, "Meta") and issubclass(type(val), typ): field_types = typ.Meta.field_types_dict for field, v in val.actual_items(): if field in field_types: dictobj.__setitem__(self, field, v) return # We're setting a group, we need to get things from the group via items if not hasattr(val, "items"): msg = "Setting a group on a packet must be done with a value that has an items() method" raise ValueError("{0}\tkey={1}\tgot={2}".format( msg, key, repr(val))) # Set from our value for field, v in val.items(): if field in self.Meta.groups[key]: self[field] = v
def clone(self, overrides=None): """ Efficiently create a shallow copy of this packet without going through delfick_project.norms The exception is anything in overrides will go through delfick_project.norms """ clone = self.__class__() for key, value in self.actual_items(): if overrides and key in overrides: clone[key] = overrides[key] else: dictobj.__setitem__(clone, key, value) return clone
def normalise(self, meta, val): val = sb.dictionary_spec().normalise(meta, val) pkt = self.kls() for attr, spec in self.attrs: if callable(spec): spec = spec(pkt, False) v = val.get(attr, pkt.actual(attr)) if attr not in val and attr in self.name_to_group: g = self.name_to_group[attr] if g in val and attr in val[g]: v = val[g][attr] v = spec.normalise(meta.at(attr), v) dictobj.__setitem__(pkt, attr, v) return pkt
def __setitem__(self, key, val): """ Set values on the object This will unpack dictionaries if we are setting a dictionary for a group field. We also see if a field has a transform option and use it if it's there """ if key in self.Meta.groups: if val is Initial: # Special case because of the logic in dictobj that sets default values on initialization # Should only happen for group fields where it makes no sense return self._set_group_item(key, val) return # If our type has a transformation, apply it to our value # The assumption is that if there is a transformation, # The value will always be given with the transformation in mind # untransform will be used when extracting the value typ = self.Meta.all_field_types_dict.get(key) if typ and typ._transform is not sb.NotSpecified and val not in ( sb.NotSpecified, Optional): val = typ.do_transform(self, val) # If we have multiple, then we want to make sure we create actual objects # So that if we modify an object in place, it's updated on the packet if typ and hasattr( typ, "_multiple") and typ._multiple and val is not sb.NotSpecified: val = typ.spec(self, unpacking=True).normalise(Meta.empty().at(key), val) # Otherwise we set directly on the packet dictobj.__setitem__(self, key, val)
def __getitem__( self, key, do_spec=True, do_transform=True, parent=None, serial=None, allow_bitarray=False, unpacking=True, ): """ Dictionary access for a key on the object This works for groups as well where the values in the fields for that group are returned as a dictionary. This process will use ``delfick_project.norms`` to ensure the value you get back is normalised. Unless ``do_spec`` is ``False``. We will also use the type transform unless ``do_transform`` is ``False``. And finally if the do_spec is True and we get back a bitarray and allow_bitarray is False, we will return the result of calling tobytes on that object. """ M = object.__getattribute__(self, "Meta") # If we're requesting one of the groups, then we must assemble from the keys in that group # Unless the group is an empty Payload (message_type of 0 on the type) # In that case, there are no fields, so we return as it is found on the class # (__setitem__ has logic to store this group on the class as is) if key in M.groups: field_types = M.field_types_dict if key in field_types and getattr(field_types[key], "message_type", None) != 0: final = field_types[key]() for k in M.groups[key]: actual = self.actual(k) if actual is sb.NotSpecified: final[k] = self.__getitem__(k, parent=self, serial=serial) else: dictobj.__setitem__(final, k, self.actual(k)) return final try: actual = super(dictobj, self).__getitem__(key) except KeyError: if key not in M.all_names and key not in M.groups: raise actual = sb.NotSpecified if key in M.groups and actual is not sb.NotSpecified: # Can only mean we have an empty payload group here b = val_to_bitarray(actual, doing="Converting payload from parent packet") if not allow_bitarray: return b.tobytes() else: return b if do_spec and key in M.all_names: typ = M.all_field_types_dict[key] res = object.__getattribute__(self, "getitem_spec")(typ, key, actual, parent, serial, do_transform, allow_bitarray, unpacking) # Make it so if there isn't a list specified, but you access it, we store the list we return # So that if you modify that list, it modifies on the packet if typ and hasattr( typ, "_multiple" ) and typ._multiple and actual is sb.NotSpecified: dictobj.__setitem__(self, key, res) return res return actual