class CheckSum(Integer): first = args.Argument(default=None) last = args.Argument(default=None) def attach_to_class(self, cls): super(CheckSum, self).attach_to_class(cls) self.fields = [] in_range = False for field in cls._fields: if field is self: # Can't go beyond the integrity field itself break if self.first is None: self.first = field if field is self.first: in_range = True if in_range: self.fields.append(field) field.after_encode(self.update_encoded_value) if field is self.last: # End of the line break def build_cache(self, instance): for field in self.fields: if field.name not in instance._raw_values: # Set the value to itself just to update the encoded value setattr(instance, field.name, getattr(instance, field.name)) def read(self, file): try: given_bytes = super(CheckSum, self).read(file) given_value = super(CheckSum, self).decode(given_bytes) except FullyDecoded as obj: given_bytes = obj.bytes given_value = obj.value self.build_cache(file) if given_value != self.get_calculated_value(file): raise IntegrityError('%s does not match calculated value' % self.name) raise FullyDecoded(given_bytes, given_value) def get_calculated_value(self, instance): data = b''.join(instance._extract(field) for field in self.fields) return self.calculate(data) def update_encoded_value(self, instance, value): setattr(instance, self.name, self.get_calculated_value(instance)) def calculate(self, data): return sum(data)
class Date(Field): """ A field that contains data in the form of dates, represented in Python by datetime.date. format A strptime()-style format string. See http://docs.python.org/library/datetime.html for details """ format = args.Argument(default='%Y-%m-%d') def decode(self, value): """ Parse a string value according to self.format and return only the date portion. """ if isinstance(value, datetime.date): return value return datetime.datetime.strptime(value, self.format).date() def encode(self, value): """ Format a date according to self.format and return that as a string. """ return value.strftime(self.format)
class Integer(bin.Integer): size = args.Override(resolve_field=False) signed = args.Argument(default=False) def encode(self, value): if value > (1 << self.size) - 1: raise ValueError("Value is large for this field.") return value & ((1 << self.size) - 1) def decode(self, value): if value > (1 << self.size) - 1: raise ValueError("Value is large for this field.") return value & ((1 << self.size) - 1)
class Field(bin.Field): size = args.Argument(resolve_field=True) choices = args.Argument(default=()) default = args.Argument(default=args.NotProvided) def __init__(self, label='', **kwargs): self.label = label for name, arg in self.arguments.items(): if name in kwargs: value = kwargs.pop(name) elif arg.has_default: value = arg.default else: raise TypeError("The %s argument is required for %s fields" % (arg.name, self.__class__.__name__)) setattr(self, name, value) if kwargs: raise TypeError("%s is not a valid argument for %s fields" % (list(kwargs.keys())[0], self.__class__.__name__)) # Once the base values are all in place, arguments can be initialized properly for name, arg in self.arguments.items(): setattr(self, name, arg.initialize(self, getattr(self, name)))
class String(Field): size = args.Override(default=None) encoding = args.Argument(resolve_field=True) padding = args.Argument(default=b'\x00') terminator = args.Argument(default=b'\x00') def read(self, file): if self.size is not None: return file.read(self.size) else: # TODO: There's gotta be a better way, but it works for now value = b'' while True: data = file.read(1) value += data if data == self.terminator: break return value def decode(self, value): return value.rstrip(self.terminator).rstrip(self.padding).decode( self.encoding) def encode(self, value): value = value.encode(self.encoding) if self.size is not None: value = value.ljust(self.size, self.padding) else: value += self.terminator return value def validate(self, obj, value): value = value.encode(self.encoding) if self.size is not None: if len(value) > self.size: raise ValueError("String %r is longer than %r bytes." % (value, self.size))
class Field(metaclass=common.DeclarativeFieldMetaclass): size = args.Argument(resolve_field=True) offset = args.Argument(default=None, resolve_field=True) choices = args.Argument(default=()) default = args.Argument(default=args.NotProvided) after_encode = Trigger() after_decode = Trigger() @after_encode def update_size(self, obj, value): if isinstance(self.size, Field): setattr(obj, self.size.name, len(value)) def __init__(self, label='', **kwargs): self.label = label self._parent = None for name, arg in self.arguments.items(): if name in kwargs: value = kwargs.pop(name) elif arg.has_default: value = arg.default else: raise TypeError("The %s argument is required for %s fields" % (arg.name, self.__class__.__name__)) setattr(self, name, value) if kwargs: raise TypeError("%s is not a valid argument for %s fields" % (list(kwargs.keys())[0], self.__class__.__name__)) # Once the base values are all in place, arguments can be initialized properly for name, arg in self.arguments.items(): setattr(self, name, arg.initialize(self, getattr(self, name))) self.instance = None def for_instance(self, instance): if instance is None: return self field = copy.copy(self) for name, attr in self.arguments.items(): value = getattr(self, name) if attr.resolve_field and hasattr(value, 'resolve'): resolved = value.resolve(instance) setattr(field, name, resolved) elif hasattr(value, '__call__'): setattr(field, name, value(instance)) field.instance = instance return field def resolve(self, instance): if self._parent is not None: instance = self._parent.resolve(instance) return getattr(instance, self.name) def read(self, obj): # If the size can be determined easily, read # that number of bytes and return it directly. if self.size is not None: return obj.read(self.size) # Otherwise, the field needs to supply its own # technique for determining how much data to read. raise NotImplementedError() def write(self, obj, value): # By default, this doesn't do much, but individual # fields can/should override it if necessary obj.write(value) def set_name(self, name): self.name = name label = self.label or name.replace('_', ' ') self.label = label.title() def attach_to_class(self, cls): cls._fields.append(self) def validate(self, obj, value): # This should raise a ValueError if the value is invalid # It should simply return without an error if it's valid field = self.for_instance(obj) # First, make sure the value can be encoded field.encode(value) # Then make sure it's a valid option, if applicable if self.choices and value not in set(v for v, desc in self.choices): raise ValueError("%r is not a valid choice" % value) def _extract(self, instance): field = self.for_instance(instance) try: return field.read(instance), None except FullyDecoded as obj: return obj.bytes, obj.value def read_value(self, file): try: bytes = self.read(file) value = self.decode(bytes) return bytes, value except FullyDecoded as obj: return obj.bytes, obj.value def __get__(self, instance, owner): if not instance: return self # Customizes the field for this particular instance # Use field instead of self for the rest of the method field = self.for_instance(instance) try: value = instance._extract(field) except IOError: if field.default is not args.NotProvided: return field.default raise AttributeError("Attribute %r has no data" % field.name) if field.name not in instance.__dict__: instance.__dict__[field.name] = field.decode(value) field.after_decode.apply(instance, value) return instance.__dict__[field.name] def __set__(self, instance, value): field = self.for_instance(instance) instance.__dict__[self.name] = value instance._raw_values[self.name] = field.encode(value) self.after_encode.apply(instance, value) def __repr__(self): return '<%s: %s>' % (self.name, type(self).__name__) def __hash__(self): return id(self) def __eq__(self, other): return Condition(self, other, lambda a, b: a == b) def __ne__(self, other): return Condition(self, other, lambda a, b: a != b)
class Integer(Field): size = args.Override(resolve_field=False) signed = args.Argument(default=False) endianness = args.Argument(default=BigEndian) signing = args.Argument(default=TwosComplement) @signing.init @endianness.init def init_size(self, value): return value(self.size) def encode(self, value): if self.signed: value = self.signing.encode(value) elif value < 0: raise ValueError("Value cannot be negative.") if value > (1 << (self.size * 8)) - 1: raise ValueError("Value is large for this field.") return self.endianness.encode(value) def decode(self, value): value = self.endianness.decode(value) if self.signed: value = self.signing.decode(value) return value def __add__(self, other): return CalculatedValue(self, other, lambda a, b: a + b) __radd__ = __add__ def __sub__(self, other): return CalculatedValue(self, other, lambda a, b: a - b) def __rsub__(self, other): return CalculatedValue(self, other, lambda a, b: b - a) def __mul__(self, other): return CalculatedValue(self, other, lambda a, b: a * b) __rmul__ = __mul__ def __pow__(self, other): return CalculatedValue(self, other, lambda a, b: a**b) def __rpow__(self, other): return CalculatedValue(self, other, lambda a, b: b**a) def __truediv__(self, other): return CalculatedValue(self, other, lambda a, b: a / b) def __rtruediv__(self, other): return CalculatedValue(self, other, lambda a, b: b / a) def __floordiv__(self, other): return CalculatedValue(self, other, lambda a, b: a // b) def __rfloordiv__(self, other): return CalculatedValue(self, other, lambda a, b: b // a) def __divmod__(self, other): return CalculatedValue(self, other, lambda a, b: divmod(a, b)) def __rdivmod__(self, other): return CalculatedValue(self, other, lambda a, b: divmod(b, a)) def __and__(self, other): return CalculatedValue(self, other, lambda a, b: a & b) __rand__ = __and__ def __or__(self, other): return CalculatedValue(self, other, lambda a, b: a | b) __ror__ = __or__ def __xor__(self, other): return CalculatedValue(self, other, lambda a, b: a ^ b) __rxor__ = __xor__ def __lt__(self, other): return Condition(self, other, lambda a, b: a < b) def __lte__(self, other): return Condition(self, other, lambda a, b: a <= b) def __gte__(self, other): return Condition(self, other, lambda a, b: a >= b) def __gt__(self, other): return Condition(self, other, lambda a, b: a > b)