construct_legacy-2.5.3/0000777000000000000000000000000013202307640013220 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/0000777000000000000000000000000013202307640016570 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/adapters.py0000666000000000000000000004174113202307143020752 0ustar 00000000000000from construct_legacy.core import Adapter, AdaptationError, Pass from construct_legacy.lib import int_to_bin, bin_to_int, swap_bytes from construct_legacy.lib import FlagsContainer, HexString from six import BytesIO import six try: bytes except NameError: bytes = str #=============================================================================== # exceptions #=============================================================================== class BitIntegerError(AdaptationError): pass class MappingError(AdaptationError): pass class ConstError(AdaptationError): pass class ValidationError(AdaptationError): pass class PaddingError(AdaptationError): pass #=============================================================================== # adapters #=============================================================================== class BitIntegerAdapter(Adapter): """ Adapter for bit-integers (converts bitstrings to integers, and vice versa). See BitField. :param subcon: the subcon to adapt :param width: the size of the subcon, in bits :param swapped: whether to swap byte order (little endian/big endian). default is False (big endian) :param signed: whether the value is signed (two's complement). the default is False (unsigned) :param bytesize: number of bits per byte, used for byte-swapping (if swapped). default is 8. """ __slots__ = ["width", "swapped", "signed", "bytesize"] def __init__(self, subcon, width, swapped = False, signed = False, bytesize = 8): super(BitIntegerAdapter, self).__init__(subcon) self.width = width self.swapped = swapped self.signed = signed self.bytesize = bytesize def _encode(self, obj, context): if obj < 0 and not self.signed: raise BitIntegerError("object is negative, but field is not signed", obj) obj2 = int_to_bin(obj, width = self.width(context) if callable(self.width) else self.width) if self.swapped: obj2 = swap_bytes(obj2, bytesize = self.bytesize) return obj2 def _decode(self, obj, context): if self.swapped: obj = swap_bytes(obj, bytesize = self.bytesize) return bin_to_int(obj, signed = self.signed) class MappingAdapter(Adapter): """ Adapter that maps objects to other objects. See SymmetricMapping and Enum. :param subcon: the subcon to map :param decoding: the decoding (parsing) mapping (a dict) :param encoding: the encoding (building) mapping (a dict) :param decdefault: the default return value when the object is not found in the decoding mapping. if no object is given, an exception is raised. if ``Pass`` is used, the unmapped object will be passed as-is :param encdefault: the default return value when the object is not found in the encoding mapping. if no object is given, an exception is raised. if ``Pass`` is used, the unmapped object will be passed as-is """ __slots__ = ["encoding", "decoding", "encdefault", "decdefault"] def __init__(self, subcon, decoding, encoding, decdefault = NotImplemented, encdefault = NotImplemented): super(MappingAdapter, self).__init__(subcon) self.decoding = decoding self.encoding = encoding self.decdefault = decdefault self.encdefault = encdefault def _encode(self, obj, context): try: return self.encoding[obj] except (KeyError, TypeError): if self.encdefault is NotImplemented: raise MappingError("no encoding mapping for %r [%s]" % ( obj, self.subcon.name)) if self.encdefault is Pass: return obj return self.encdefault def _decode(self, obj, context): try: return self.decoding[obj] except (KeyError, TypeError): if self.decdefault is NotImplemented: raise MappingError("no decoding mapping for %r [%s]" % ( obj, self.subcon.name)) if self.decdefault is Pass: return obj return self.decdefault class FlagsAdapter(Adapter): """ Adapter for flag fields. Each flag is extracted from the number, resulting in a FlagsContainer object. Not intended for direct usage. See FlagsEnum. :param subcon: the subcon to extract :param flags: a dictionary mapping flag-names to their value """ __slots__ = ["flags"] def __init__(self, subcon, flags): super(FlagsAdapter, self).__init__(subcon) self.flags = flags def _encode(self, obj, context): flags = 0 for name, value in self.flags.items(): if getattr(obj, name, False): flags |= value return flags def _decode(self, obj, context): obj2 = FlagsContainer() for name, value in self.flags.items(): setattr(obj2, name, bool(obj & value)) return obj2 class StringAdapter(Adapter): """ Adapter for strings. Converts a sequence of characters into a python string, and optionally handles character encoding. See String. :param subcon: the subcon to convert :param encoding: the character encoding name (e.g., "utf8"), or None to return raw bytes (usually 8-bit ASCII). """ __slots__ = ["encoding"] def __init__(self, subcon, encoding = None): super(StringAdapter, self).__init__(subcon) self.encoding = encoding def _encode(self, obj, context): if self.encoding: if isinstance(self.encoding, str): obj = obj.encode(self.encoding) else: obj = self.encoding.encode(obj) return obj def _decode(self, obj, context): if not isinstance(obj, bytes): obj = six.b("").join(obj) if self.encoding: if isinstance(self.encoding, str): obj = obj.decode(self.encoding) else: obj = self.encoding.decode(obj) return obj class PaddedStringAdapter(Adapter): r""" Adapter for padded strings. See String. :param subcon: the subcon to adapt :param padchar: the padding character. default is "\x00". :param paddir: the direction where padding is placed ("right", "left", or "center"). the default is "right". :param trimdir: the direction where trimming will take place ("right" or "left"). the default is "right". trimming is only meaningful for building, when the given string is too long. """ __slots__ = ["padchar", "paddir", "trimdir"] def __init__(self, subcon, padchar = six.b("\x00"), paddir = "right", trimdir = "right"): if paddir not in ("right", "left", "center"): raise ValueError("paddir must be 'right', 'left' or 'center'", paddir) if trimdir not in ("right", "left"): raise ValueError("trimdir must be 'right' or 'left'", trimdir) super(PaddedStringAdapter, self).__init__(subcon) self.padchar = padchar self.paddir = paddir self.trimdir = trimdir def _decode(self, obj, context): if self.paddir == "right": obj = obj.rstrip(self.padchar) elif self.paddir == "left": obj = obj.lstrip(self.padchar) else: obj = obj.strip(self.padchar) return obj def _encode(self, obj, context): size = self._sizeof(context) if self.paddir == "right": obj = obj.ljust(size, self.padchar) elif self.paddir == "left": obj = obj.rjust(size, self.padchar) else: obj = obj.center(size, self.padchar) if len(obj) > size: if self.trimdir == "right": obj = obj[:size] else: obj = obj[-size:] return obj class LengthValueAdapter(Adapter): """ Adapter for length-value pairs. It extracts only the value from the pair, and calculates the length based on the value. See PrefixedArray and PascalString. :param subcon: the subcon returning a length-value pair """ __slots__ = [] def _encode(self, obj, context): return (len(obj), obj) def _decode(self, obj, context): return obj[1] class CStringAdapter(StringAdapter): r""" Adapter for C-style strings (strings terminated by a terminator char). :param subcon: the subcon to convert :param terminators: a sequence of terminator chars. default is "\x00". :param encoding: the character encoding to use (e.g., "utf8"), or None to return raw-bytes. the terminator characters are not affected by the encoding. """ __slots__ = ["terminators"] def __init__(self, subcon, terminators = six.b("\x00"), encoding = None): super(CStringAdapter, self).__init__(subcon, encoding = encoding) self.terminators = terminators def _encode(self, obj, context): return StringAdapter._encode(self, obj, context) + self.terminators[0:1] def _decode(self, obj, context): return StringAdapter._decode(self, six.b('').join(obj[:-1]), context) class TunnelAdapter(Adapter): """ Adapter for tunneling (as in protocol tunneling). A tunnel is construct nested upon another (layering). For parsing, the lower layer first parses the data (note: it must return a string!), then the upper layer is called to parse that data (bottom-up). For building it works in a top-down manner; first the upper layer builds the data, then the lower layer takes it and writes it to the stream. :param subcon: the lower layer subcon :param inner_subcon: the upper layer (tunneled/nested) subcon Example:: # a pascal string containing compressed data (zlib encoding), so first # the string is read, decompressed, and finally re-parsed as an array # of UBInt16 TunnelAdapter( PascalString("data", encoding = "zlib"), GreedyRange(UBInt16("elements")) ) """ __slots__ = ["inner_subcon"] def __init__(self, subcon, inner_subcon): super(TunnelAdapter, self).__init__(subcon) self.inner_subcon = inner_subcon def _decode(self, obj, context): return self.inner_subcon._parse(BytesIO(obj), context) def _encode(self, obj, context): stream = BytesIO() self.inner_subcon._build(obj, stream, context) return stream.getvalue() class ExprAdapter(Adapter): """ A generic adapter that accepts 'encoder' and 'decoder' as parameters. You can use ExprAdapter instead of writing a full-blown class when only a simple expression is needed. :param subcon: the subcon to adapt :param encoder: a function that takes (obj, context) and returns an encoded version of obj :param decoder: a function that takes (obj, context) and returns an decoded version of obj Example:: ExprAdapter(UBInt8("foo"), encoder = lambda obj, ctx: obj / 4, decoder = lambda obj, ctx: obj * 4, ) """ __slots__ = ["_encode", "_decode"] def __init__(self, subcon, encoder, decoder): super(ExprAdapter, self).__init__(subcon) self._encode = encoder self._decode = decoder class HexDumpAdapter(Adapter): """ Adapter for hex-dumping strings. It returns a HexString, which is a string """ __slots__ = ["linesize"] def __init__(self, subcon, linesize = 16): super(HexDumpAdapter, self).__init__(subcon) self.linesize = linesize def _encode(self, obj, context): return obj def _decode(self, obj, context): return HexString(obj, linesize = self.linesize) class ConstAdapter(Adapter): """ Adapter for enforcing a constant value ("magic numbers"). When decoding, the return value is checked; when building, the value is substituted in. :param subcon: the subcon to validate :param value: the expected value Example:: Const(Field("signature", 2), "MZ") """ __slots__ = ["value"] def __init__(self, subcon, value): super(ConstAdapter, self).__init__(subcon) self.value = value def _encode(self, obj, context): if obj is None or obj == self.value: return self.value else: raise ConstError("expected %r, found %r" % (self.value, obj)) def _decode(self, obj, context): if obj != self.value: raise ConstError("expected %r, found %r" % (self.value, obj)) return obj class SlicingAdapter(Adapter): """ Adapter for slicing a list (getting a slice from that list) :param subcon: the subcon to slice :param start: start index :param stop: stop index (or None for up-to-end) :param step: step (or None for every element) """ __slots__ = ["start", "stop", "step"] def __init__(self, subcon, start, stop = None): super(SlicingAdapter, self).__init__(subcon) self.start = start self.stop = stop def _encode(self, obj, context): if self.start is None: return obj return [None] * self.start + obj def _decode(self, obj, context): return obj[self.start:self.stop] class IndexingAdapter(Adapter): """ Adapter for indexing a list (getting a single item from that list) :param subcon: the subcon to index :param index: the index of the list to get """ __slots__ = ["index"] def __init__(self, subcon, index): super(IndexingAdapter, self).__init__(subcon) if type(index) is not int: raise TypeError("index must be an integer", type(index)) self.index = index def _encode(self, obj, context): return [None] * self.index + [obj] def _decode(self, obj, context): return obj[self.index] class PaddingAdapter(Adapter): r""" Adapter for padding. :param subcon: the subcon to pad :param pattern: the padding pattern (character). default is "\x00" :param strict: whether or not to verify, during parsing, that the given padding matches the padding pattern. default is False (unstrict) """ __slots__ = ["pattern", "strict"] def __init__(self, subcon, pattern = six.b("\x00"), strict = False): super(PaddingAdapter, self).__init__(subcon) self.pattern = pattern self.strict = strict def _encode(self, obj, context): return self._sizeof(context) * self.pattern def _decode(self, obj, context): if self.strict: expected = self._sizeof(context) * self.pattern if obj != expected: raise PaddingError("expected %r, found %r" % (expected, obj)) return obj #=============================================================================== # validators #=============================================================================== class Validator(Adapter): """ Abstract class: validates a condition on the encoded/decoded object. Override _validate(obj, context) in deriving classes. :param subcon: the subcon to validate """ __slots__ = [] def _decode(self, obj, context): if not self._validate(obj, context): raise ValidationError("invalid object", obj) return obj def _encode(self, obj, context): return self._decode(obj, context) def _validate(self, obj, context): raise NotImplementedError() class OneOf(Validator): """ Validates that the object is one of the listed values. :param subcon: object to validate :param valids: a set of valid values Example:: >>> OneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x05") 5 >>> OneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x08") Traceback (most recent call last): ... construct.core.ValidationError: ('invalid object', 8) >>> >>> OneOf(UBInt8("foo"), [4,5,6,7]).build(5) '\\x05' >>> OneOf(UBInt8("foo"), [4,5,6,7]).build(9) Traceback (most recent call last): ... construct.core.ValidationError: ('invalid object', 9) """ __slots__ = ["valids"] def __init__(self, subcon, valids): super(OneOf, self).__init__(subcon) self.valids = valids def _validate(self, obj, context): return obj in self.valids class NoneOf(Validator): """ Validates that the object is none of the listed values. :param subcon: object to validate :param invalids: a set of invalid values Example:: >>> NoneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x08") 8 >>> NoneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x06") Traceback (most recent call last): ... construct.core.ValidationError: ('invalid object', 6) """ __slots__ = ["invalids"] def __init__(self, subcon, invalids): super(NoneOf, self).__init__(subcon) self.invalids = invalids def _validate(self, obj, context): return obj not in self.invalids construct_legacy-2.5.3/construct_legacy/core.py0000666000000000000000000013673113202307142020102 0ustar 00000000000000from struct import Struct as Packer from construct_legacy.lib.py3compat import BytesIO, advance_iterator, bchr from construct_legacy.lib import Container, ListContainer, LazyContainer import sys import six try: bytes except NameError: bytes = str #=============================================================================== # exceptions #=============================================================================== class ConstructError(Exception): pass class FieldError(ConstructError): pass class SizeofError(ConstructError): pass class AdaptationError(ConstructError): pass class ArrayError(ConstructError): pass class RangeError(ConstructError): pass class SwitchError(ConstructError): pass class SelectError(ConstructError): pass class TerminatorError(ConstructError): pass class OverwriteError(ValueError): pass #=============================================================================== # abstract constructs #=============================================================================== class Construct(object): """ The mother of all constructs. This object is generally not directly instantiated, and it does not directly implement parsing and building, so it is largely only of interest to subclass implementors. The external user API: * ``parse()`` * ``parse_stream()`` * ``build()`` * ``build_stream()`` * ``sizeof()`` Subclass authors should not override the external methods. Instead, another API is available: * ``_parse()`` * ``_build()`` * ``_sizeof()`` There is also a flag API: * ``_set_flag()`` * ``_clear_flag()`` * ``_inherit_flags()`` * ``_is_flag()`` And stateful copying: * ``__getstate__()`` * ``__setstate__()`` Attributes and Inheritance ========================== All constructs have a name and flags. The name is used for naming struct members and context dictionaries. Note that the name can either be a string, or None if the name is not needed. A single underscore ("_") is a reserved name, and so are names starting with a less-than character ("<"). The name should be descriptive, short, and valid as a Python identifier, although these rules are not enforced. The flags specify additional behavioral information about this construct. Flags are used by enclosing constructs to determine a proper course of action. Flags are inherited by default, from inner subconstructs to outer constructs. The enclosing construct may set new flags or clear existing ones, as necessary. For example, if ``FLAG_COPY_CONTEXT`` is set, repeaters will pass a copy of the context for each iteration, which is necessary for OnDemand parsing. """ FLAG_COPY_CONTEXT = 0x0001 FLAG_DYNAMIC = 0x0002 FLAG_EMBED = 0x0004 FLAG_NESTING = 0x0008 __slots__ = ["name", "conflags"] def __init__(self, name, flags = 0): if name is not None: if not isinstance(name, six.string_types): raise TypeError("name must be a string or None", name) if name == "_" or name.startswith("<"): raise ValueError("reserved name", name) self.name = name self.conflags = flags def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.name) def _set_flag(self, flag): """ Set the given flag or flags. :param int flag: flag to set; may be OR'd combination of flags """ self.conflags |= flag def _clear_flag(self, flag): """ Clear the given flag or flags. :param int flag: flag to clear; may be OR'd combination of flags """ self.conflags &= ~flag def _inherit_flags(self, *subcons): """ Pull flags from subconstructs. """ for sc in subcons: self._set_flag(sc.conflags) def _is_flag(self, flag): """ Check whether a given flag is set. :param int flag: flag to check """ return bool(self.conflags & flag) def __getstate__(self): """ Obtain a dictionary representing this construct's state. """ attrs = {} if hasattr(self, "__dict__"): attrs.update(self.__dict__) slots = [] c = self.__class__ while c is not None: if hasattr(c, "__slots__"): slots.extend(c.__slots__) c = c.__base__ for name in slots: if hasattr(self, name): attrs[name] = getattr(self, name) return attrs def __setstate__(self, attrs): """ Set this construct's state to a given state. """ for name, value in attrs.items(): setattr(self, name, value) def __copy__(self): """returns a copy of this construct""" self2 = object.__new__(self.__class__) self2.__setstate__(self, self.__getstate__()) return self2 def parse(self, data): """ Parse an in-memory buffer. Strings, buffers, memoryviews, and other complete buffers can be parsed with this method. """ return self.parse_stream(BytesIO(data)) def parse_stream(self, stream): """ Parse a stream. Files, pipes, sockets, and other streaming sources of data are handled by this method. """ return self._parse(stream, Container()) def _parse(self, stream, context): """ Override me in your subclass. """ raise NotImplementedError() def build(self, obj): """ Build an object in memory. """ stream = BytesIO() self.build_stream(obj, stream) return stream.getvalue() def build_stream(self, obj, stream): """ Build an object directly into a stream. """ self._build(obj, stream, Container()) def _build(self, obj, stream, context): """ Override me in your subclass. """ raise NotImplementedError() def sizeof(self, context=None): """ Calculate the size of this object, optionally using a context. Some constructs have no fixed size and can only know their size for a given hunk of data; these constructs will raise an error if they are not passed a context. :param context: contextual data :returns: int of the length of this construct :raises SizeofError: the size could not be determined """ if context is None: context = Container() try: return self._sizeof(context) except Exception: raise SizeofError(sys.exc_info()[1]) def _sizeof(self, context): """ Override me in your subclass. """ raise SizeofError("Raw Constructs have no size!") class Subconstruct(Construct): """ Abstract subconstruct (wraps an inner construct, inheriting its name and flags). Subconstructs wrap an inner Construct, inheriting its name and flags. :param subcon: the construct to wrap """ __slots__ = ["subcon"] def __init__(self, subcon): super(Subconstruct, self).__init__(subcon.name, subcon.conflags) self.subcon = subcon def _parse(self, stream, context): return self.subcon._parse(stream, context) def _build(self, obj, stream, context): self.subcon._build(obj, stream, context) def _sizeof(self, context): return self.subcon._sizeof(context) class Adapter(Subconstruct): """ Abstract adapter parent class. Adapters should implement ``_decode()`` and ``_encode()``. :param subcon: the construct to wrap """ __slots__ = [] def _parse(self, stream, context): return self._decode(self.subcon._parse(stream, context), context) def _build(self, obj, stream, context): self.subcon._build(self._encode(obj, context), stream, context) def _decode(self, obj, context): raise NotImplementedError() def _encode(self, obj, context): raise NotImplementedError() #=============================================================================== # Fields #=============================================================================== def _read_stream(stream, length): if length < 0: raise ValueError("length must be >= 0", length) data = stream.read(length) if len(data) != length: raise FieldError("expected %d, found %d" % (length, len(data))) return data def _write_stream(stream, length, data): if length < 0: raise ValueError("length must be >= 0", length) if len(data) != length: raise FieldError("expected %d, found %d" % (length, len(data))) stream.write(data) class StaticField(Construct): """ A fixed-size byte field. :param name: field name :param length: number of bytes in the field """ __slots__ = ["length"] def __init__(self, name, length): super(StaticField, self).__init__(name) self.length = length def _parse(self, stream, context): return _read_stream(stream, self.length) def _build(self, obj, stream, context): _write_stream(stream, self.length, bchr(obj) if isinstance(obj, int) else obj) def _sizeof(self, context): return self.length class FormatField(StaticField): """ A field that uses ``struct`` to pack and unpack data. See ``struct`` documentation for instructions on crafting format strings. :param name: name of the field :param endianness: format endianness string; one of "<", ">", or "=" :param format: a single format character """ __slots__ = ["packer"] def __init__(self, name, endianity, format): if endianity not in (">", "<", "="): raise ValueError("endianity must be be '=', '<', or '>'", endianity) if len(format) != 1: raise ValueError("must specify one and only one format char") self.packer = Packer(endianity + format) super(FormatField, self).__init__(name, self.packer.size) def __getstate__(self): attrs = super(FormatField, self).__getstate__() attrs["packer"] = attrs["packer"].format return attrs def __setstate__(self, attrs): attrs["packer"] = Packer(attrs["packer"]) return super(FormatField, self).__setstate__(attrs) def _parse(self, stream, context): try: return self.packer.unpack(_read_stream(stream, self.length))[0] except Exception: raise FieldError(sys.exc_info()[1]) def _build(self, obj, stream, context): try: _write_stream(stream, self.length, self.packer.pack(obj)) except Exception: raise FieldError(sys.exc_info()[1]) class MetaField(Construct): r""" A variable-length field. The length is obtained at runtime from a function. :param name: name of the field :param lengthfunc: callable that takes a context and returns length as an int Example:: >>> foo = Struct("foo", ... Byte("length"), ... MetaField("data", lambda ctx: ctx["length"]) ... ) >>> foo.parse("\x03ABC") Container(data = 'ABC', length = 3) >>> foo.parse("\x04ABCD") Container(data = 'ABCD', length = 4) """ __slots__ = ["lengthfunc"] def __init__(self, name, lengthfunc): super(MetaField, self).__init__(name) self.lengthfunc = lengthfunc self._set_flag(self.FLAG_DYNAMIC) def _parse(self, stream, context): return _read_stream(stream, self.lengthfunc(context)) def _build(self, obj, stream, context): _write_stream(stream, self.lengthfunc(context), obj) def _sizeof(self, context): return self.lengthfunc(context) #=============================================================================== # arrays and repeaters #=============================================================================== class MetaArray(Subconstruct): """ An array (repeater) of a meta-count. The array will iterate exactly ``countfunc()`` times. Will raise ArrayError if less elements are found. .. seealso:: The :func:`~construct.macros.Array` macro, :func:`Range` and :func:`RepeatUntil`. :param countfunc: a function that takes the context as a parameter and returns the number of elements of the array (count) :param subcon: the subcon to repeat ``countfunc()`` times Example:: MetaArray(lambda ctx: 5, UBInt8("foo")) """ __slots__ = ["countfunc"] def __init__(self, countfunc, subcon): super(MetaArray, self).__init__(subcon) self.countfunc = countfunc self._clear_flag(self.FLAG_COPY_CONTEXT) self._set_flag(self.FLAG_DYNAMIC) def _parse(self, stream, context): obj = ListContainer() c = 0 count = self.countfunc(context) try: if self.subcon.conflags & self.FLAG_COPY_CONTEXT: while c < count: obj.append(self.subcon._parse(stream, context.__copy__())) c += 1 else: while c < count: obj.append(self.subcon._parse(stream, context)) c += 1 except ConstructError: raise ArrayError("expected %d, found %d" % (count, c), sys.exc_info()[1]) return obj def _build(self, obj, stream, context): count = self.countfunc(context) if len(obj) != count: raise ArrayError("expected %d, found %d" % (count, len(obj))) if self.subcon.conflags & self.FLAG_COPY_CONTEXT: for subobj in obj: self.subcon._build(subobj, stream, context.__copy__()) else: for subobj in obj: self.subcon._build(subobj, stream, context) def _sizeof(self, context): return self.subcon._sizeof(context) * self.countfunc(context) class Range(Subconstruct): r""" A range-array. The subcon will iterate between ``mincount`` to ``maxcount`` times. If less than ``mincount`` elements are found, raises RangeError. .. seealso:: The :func:`~construct.macros.GreedyRange` and :func:`~construct.macros.OptionalGreedyRange` macros. The general-case repeater. Repeats the given unit for at least ``mincount`` times, and up to ``maxcount`` times. If an exception occurs (EOF, validation error), the repeater exits. If less than ``mincount`` units have been successfully parsed, a RangeError is raised. .. note:: This object requires a seekable stream for parsing. :param mincount: the minimal count :param maxcount: the maximal count :param subcon: the subcon to repeat Example:: >>> c = Range(3, 7, UBInt8("foo")) >>> c.parse("\x01\x02") Traceback (most recent call last): ... construct.core.RangeError: expected 3..7, found 2 >>> c.parse("\x01\x02\x03") [1, 2, 3] >>> c.parse("\x01\x02\x03\x04\x05\x06") [1, 2, 3, 4, 5, 6] >>> c.parse("\x01\x02\x03\x04\x05\x06\x07") [1, 2, 3, 4, 5, 6, 7] >>> c.parse("\x01\x02\x03\x04\x05\x06\x07\x08\x09") [1, 2, 3, 4, 5, 6, 7] >>> c.build([1,2]) Traceback (most recent call last): ... construct.core.RangeError: expected 3..7, found 2 >>> c.build([1,2,3,4]) '\x01\x02\x03\x04' >>> c.build([1,2,3,4,5,6,7,8]) Traceback (most recent call last): ... construct.core.RangeError: expected 3..7, found 8 """ __slots__ = ["mincount", "maxcout"] def __init__(self, mincount, maxcout, subcon): super(Range, self).__init__(subcon) self.mincount = mincount self.maxcout = maxcout self._clear_flag(self.FLAG_COPY_CONTEXT) self._set_flag(self.FLAG_DYNAMIC) def _parse(self, stream, context): obj = ListContainer() c = 0 try: if self.subcon.conflags & self.FLAG_COPY_CONTEXT: while c < self.maxcout: pos = stream.tell() obj.append(self.subcon._parse(stream, context.__copy__())) c += 1 else: while c < self.maxcout: pos = stream.tell() obj.append(self.subcon._parse(stream, context)) c += 1 except ConstructError: if c < self.mincount: raise RangeError("expected %d to %d, found %d" % (self.mincount, self.maxcout, c), sys.exc_info()[1]) stream.seek(pos) return obj def _build(self, obj, stream, context): if len(obj) < self.mincount or len(obj) > self.maxcout: raise RangeError("expected %d to %d, found %d" % (self.mincount, self.maxcout, len(obj))) cnt = 0 try: if self.subcon.conflags & self.FLAG_COPY_CONTEXT: for subobj in obj: if isinstance(obj, bytes): subobj = bchr(subobj) self.subcon._build(subobj, stream, context.__copy__()) cnt += 1 else: for subobj in obj: if isinstance(obj, bytes): subobj = bchr(subobj) self.subcon._build(subobj, stream, context) cnt += 1 except ConstructError: if cnt < self.mincount: raise RangeError("expected %d to %d, found %d" % (self.mincount, self.maxcout, len(obj)), sys.exc_info()[1]) def _sizeof(self, context): raise SizeofError("can't calculate size") class RepeatUntil(Subconstruct): r""" An array that repeats until the predicate indicates it to stop. Note that the last element (which caused the repeat to exit) is included in the return value. :param predicate: a predicate function that takes (obj, context) and returns True if the stop-condition is met, or False to continue. :param subcon: the subcon to repeat. Example:: # will read chars until '\x00' (inclusive) RepeatUntil(lambda obj, ctx: obj == b"\x00", Field("chars", 1) ) """ __slots__ = ["predicate"] def __init__(self, predicate, subcon): super(RepeatUntil, self).__init__(subcon) self.predicate = predicate self._clear_flag(self.FLAG_COPY_CONTEXT) self._set_flag(self.FLAG_DYNAMIC) def _parse(self, stream, context): obj = [] try: if self.subcon.conflags & self.FLAG_COPY_CONTEXT: while True: subobj = self.subcon._parse(stream, context.__copy__()) obj.append(subobj) if self.predicate(subobj, context): break else: while True: subobj = self.subcon._parse(stream, context) obj.append(subobj) if self.predicate(subobj, context): break except ConstructError: raise ArrayError("missing terminator", sys.exc_info()[1]) return obj def _build(self, obj, stream, context): terminated = False if self.subcon.conflags & self.FLAG_COPY_CONTEXT: for subobj in obj: self.subcon._build(subobj, stream, context.__copy__()) if self.predicate(subobj, context): terminated = True break else: for subobj in obj: #subobj = bchr(subobj) -- WTF is that for?! self.subcon._build(subobj, stream, context.__copy__()) if self.predicate(subobj, context): terminated = True break if not terminated: raise ArrayError("missing terminator") def _sizeof(self, context): raise SizeofError("can't calculate size") #=============================================================================== # structures and sequences #=============================================================================== class Struct(Construct): """ A sequence of named constructs, similar to structs in C. The elements are parsed and built in the order they are defined. .. seealso:: The :func:`~construct.macros.Embedded` macro. :param name: the name of the structure :param subcons: a sequence of subconstructs that make up this structure. :param nested: a keyword-only argument that indicates whether this struct creates a nested context. The default is True. This parameter is considered "advanced usage", and may be removed in the future. Example:: Struct("foo", UBInt8("first_element"), UBInt16("second_element"), Padding(2), UBInt8("third_element"), ) """ __slots__ = ["subcons", "nested", "allow_overwrite"] def __init__(self, name, *subcons, **kw): self.nested = kw.pop("nested", True) self.allow_overwrite = kw.pop("allow_overwrite", False) if kw: raise TypeError("the only keyword argument accepted is 'nested'", kw) super(Struct, self).__init__(name) self.subcons = subcons self._inherit_flags(*subcons) self._clear_flag(self.FLAG_EMBED) def _parse(self, stream, context): if "" in context: obj = context[""] del context[""] else: obj = Container() if self.nested: context = Container(_ = context) for sc in self.subcons: if sc.conflags & self.FLAG_EMBED: context[""] = obj sc._parse(stream, context) else: subobj = sc._parse(stream, context) if sc.name is not None: if sc.name in obj and not self.allow_overwrite: raise OverwriteError("%r would be overwritten but allow_overwrite is False" % (sc.name,)) obj[sc.name] = subobj context[sc.name] = subobj return obj def _build(self, obj, stream, context): if "" in context: del context[""] elif self.nested: context = Container(_ = context) for sc in self.subcons: if sc.conflags & self.FLAG_EMBED: context[""] = True subobj = obj elif sc.name is None: subobj = None else: subobj = obj[sc.name] context[sc.name] = subobj sc._build(subobj, stream, context) def _sizeof(self, context): #if self.nested: # context = Container(_ = context) return sum(sc._sizeof(context) for sc in self.subcons) class Sequence(Struct): """ A sequence of unnamed constructs. The elements are parsed and built in the order they are defined. .. seealso:: The :func:`~construct.macros.Embedded` macro. :param name: the name of the structure :param subcons: a sequence of subconstructs that make up this structure. :param nested: a keyword-only argument that indicates whether this struct creates a nested context. The default is True. This parameter is considered "advanced usage", and may be removed in the future. Example:: Sequence("foo", UBInt8("first_element"), UBInt16("second_element"), Padding(2), UBInt8("third_element"), ) """ __slots__ = [] def _parse(self, stream, context): if "" in context: obj = context[""] del context[""] else: obj = ListContainer() if self.nested: context = Container(_ = context) for sc in self.subcons: if sc.conflags & self.FLAG_EMBED: context[""] = obj sc._parse(stream, context) else: subobj = sc._parse(stream, context) if sc.name is not None: obj.append(subobj) context[sc.name] = subobj return obj def _build(self, obj, stream, context): if "" in context: del context[""] elif self.nested: context = Container(_ = context) objiter = iter(obj) for sc in self.subcons: if sc.conflags & self.FLAG_EMBED: context[""] = True subobj = objiter elif sc.name is None: subobj = None else: subobj = advance_iterator(objiter) context[sc.name] = subobj sc._build(subobj, stream, context) class Union(Construct): """ a set of overlapping fields (like unions in C). when parsing, all fields read the same data; when building, only the first subcon (called "master") is used. :param name: the name of the union :param master: the master subcon, i.e., the subcon used for building and calculating the total size :param subcons: additional subcons Example:: Union("what_are_four_bytes", UBInt32("one_dword"), Struct("two_words", UBInt16("first"), UBInt16("second")), Struct("four_bytes", UBInt8("a"), UBInt8("b"), UBInt8("c"), UBInt8("d") ), ) """ __slots__ = ["parser", "builder"] def __init__(self, name, master, *subcons, **kw): super(Union, self).__init__(name) args = [Peek(sc) for sc in subcons] args.append(MetaField(None, lambda ctx: master._sizeof(ctx))) self.parser = Struct(name, Peek(master, perform_build = True), *args) self.builder = Struct(name, master) def _parse(self, stream, context): return self.parser._parse(stream, context) def _build(self, obj, stream, context): return self.builder._build(obj, stream, context) def _sizeof(self, context): return self.builder._sizeof(context) #=============================================================================== # conditional #=============================================================================== class Switch(Construct): """ A conditional branch. Switch will choose the case to follow based on the return value of keyfunc. If no case is matched, and no default value is given, SwitchError will be raised. .. seealso:: :func:`Pass`. :param name: the name of the construct :param keyfunc: a function that takes the context and returns a key, which will be used to choose the relevant case. :param cases: a dictionary mapping keys to constructs. the keys can be any values that may be returned by keyfunc. :param default: a default value to use when the key is not found in the cases. if not supplied, an exception will be raised when the key is not found. You can use the builtin construct Pass for 'do-nothing'. :param include_key: whether or not to include the key in the return value of parsing. defualt is False. Example:: Struct("foo", UBInt8("type"), Switch("value", lambda ctx: ctx.type, { 1 : UBInt8("spam"), 2 : UBInt16("spam"), 3 : UBInt32("spam"), 4 : UBInt64("spam"), } ), ) """ class NoDefault(Construct): def _parse(self, stream, context): raise SwitchError("no default case defined") def _build(self, obj, stream, context): raise SwitchError("no default case defined") def _sizeof(self, context): raise SwitchError("no default case defined") NoDefault = NoDefault("No default value specified") __slots__ = ["subcons", "keyfunc", "cases", "default", "include_key"] def __init__(self, name, keyfunc, cases, default = NoDefault, include_key = False): super(Switch, self).__init__(name) self._inherit_flags(*cases.values()) self.keyfunc = keyfunc self.cases = cases self.default = default self.include_key = include_key self._inherit_flags(*cases.values()) self._set_flag(self.FLAG_DYNAMIC) def _parse(self, stream, context): key = self.keyfunc(context) obj = self.cases.get(key, self.default)._parse(stream, context) if self.include_key: return key, obj else: return obj def _build(self, obj, stream, context): if self.include_key: key, obj = obj else: key = self.keyfunc(context) case = self.cases.get(key, self.default) case._build(obj, stream, context) def _sizeof(self, context): case = self.cases.get(self.keyfunc(context), self.default) return case._sizeof(context) class Select(Construct): """ Selects the first matching subconstruct. It will literally try each of the subconstructs, until one matches. .. note:: Requires a seekable stream. :param name: the name of the construct :param subcons: the subcons to try (order-sensitive) :param include_name: a keyword only argument, indicating whether to include the name of the selected subcon in the return value of parsing. default is false. Example:: Select("foo", UBInt64("large"), UBInt32("medium"), UBInt16("small"), UBInt8("tiny"), ) """ __slots__ = ["subcons", "include_name"] def __init__(self, name, *subcons, **kw): include_name = kw.pop("include_name", False) if kw: raise TypeError("the only keyword argument accepted " "is 'include_name'", kw) super(Select, self).__init__(name) self.subcons = subcons self.include_name = include_name self._inherit_flags(*subcons) self._set_flag(self.FLAG_DYNAMIC) def _parse(self, stream, context): for sc in self.subcons: pos = stream.tell() context2 = context.__copy__() try: obj = sc._parse(stream, context2) except ConstructError: stream.seek(pos) else: context.__update__(context2) if self.include_name: return sc.name, obj else: return obj raise SelectError("no subconstruct matched") def _build(self, obj, stream, context): if self.include_name: name, obj = obj for sc in self.subcons: if sc.name == name: sc._build(obj, stream, context) return else: for sc in self.subcons: stream2 = BytesIO() context2 = context.__copy__() try: sc._build(obj, stream2, context2) except Exception: pass else: context.__update__(context2) stream.write(stream2.getvalue()) return raise SelectError("no subconstruct matched", obj) def _sizeof(self, context): raise SizeofError("can't calculate size") #=============================================================================== # stream manipulation #=============================================================================== class Pointer(Subconstruct): """ Changes the stream position to a given offset, where the construction should take place, and restores the stream position when finished. .. seealso:: :func:`Anchor`, :func:`OnDemand` and the :func:`~construct.macros.OnDemandPointer` macro. .. note:: Requires a seekable stream. :param offsetfunc: a function that takes the context and returns an absolute stream position, where the construction would take place :param subcon: the subcon to use at ``offsetfunc()`` Example:: Struct("foo", UBInt32("spam_pointer"), Pointer(lambda ctx: ctx.spam_pointer, Array(5, UBInt8("spam")) ) ) """ __slots__ = ["offsetfunc"] def __init__(self, offsetfunc, subcon): super(Pointer, self).__init__(subcon) self.offsetfunc = offsetfunc def _parse(self, stream, context): newpos = self.offsetfunc(context) origpos = stream.tell() stream.seek(newpos, 2 if newpos < 0 else 0) obj = self.subcon._parse(stream, context) stream.seek(origpos) return obj def _build(self, obj, stream, context): newpos = self.offsetfunc(context) origpos = stream.tell() stream.seek(newpos, 2 if newpos < 0 else 0) self.subcon._build(obj, stream, context) stream.seek(origpos) def _sizeof(self, context): return 0 class Peek(Subconstruct): """ Peeks at the stream: parses without changing the stream position. See also Union. If the end of the stream is reached when peeking, returns None. .. note:: Requires a seekable stream. :param subcon: the subcon to peek at :param perform_build: whether or not to perform building. by default this parameter is set to False, meaning building is a no-op. Example:: Peek(UBInt8("foo")) """ __slots__ = ["perform_build"] def __init__(self, subcon, perform_build = False): super(Peek, self).__init__(subcon) self.perform_build = perform_build def _parse(self, stream, context): pos = stream.tell() try: return self.subcon._parse(stream, context) except FieldError: pass finally: stream.seek(pos) def _build(self, obj, stream, context): if self.perform_build: self.subcon._build(obj, stream, context) def _sizeof(self, context): return 0 class OnDemand(Subconstruct): """ Allows for on-demand (lazy) parsing. When parsing, it will return a LazyContainer that represents a pointer to the data, but does not actually parses it from stream until it's "demanded". By accessing the 'value' property of LazyContainers, you will demand the data from the stream. The data will be parsed and cached for later use. You can use the 'has_value' property to know whether the data has already been demanded. .. seealso:: The :func:`~construct.macros.OnDemandPointer` macro. .. note:: Requires a seekable stream. :param subcon: the subcon to read/write on demand :param advance_stream: whether or not to advance the stream position. by default this is True, but if subcon is a pointer, this should be False. :param force_build: whether or not to force build. If set to False, and the LazyContainer has not been demaned, building is a no-op. Example:: OnDemand(Array(10000, UBInt8("foo")) """ __slots__ = ["advance_stream", "force_build"] def __init__(self, subcon, advance_stream = True, force_build = True): super(OnDemand, self).__init__(subcon) self.advance_stream = advance_stream self.force_build = force_build def _parse(self, stream, context): obj = LazyContainer(self.subcon, stream, stream.tell(), context) if self.advance_stream: stream.seek(self.subcon._sizeof(context), 1) return obj def _build(self, obj, stream, context): if not isinstance(obj, LazyContainer): self.subcon._build(obj, stream, context) elif self.force_build or obj.has_value: self.subcon._build(obj.value, stream, context) elif self.advance_stream: stream.seek(self.subcon._sizeof(context), 1) class Buffered(Subconstruct): """ Creates an in-memory buffered stream, which can undergo encoding and decoding prior to being passed on to the subconstruct. .. seealso:: The :func:`~construct.macros.Bitwise` macro. .. warning:: Do not use pointers inside ``Buffered``. :param subcon: the subcon which will operate on the buffer :param encoder: a function that takes a string and returns an encoded string (used after building) :param decoder: a function that takes a string and returns a decoded string (used before parsing) :param resizer: a function that takes the size of the subcon and "adjusts" or "resizes" it according to the encoding/decoding process. Example:: Buffered(BitField("foo", 16), encoder = decode_bin, decoder = encode_bin, resizer = lambda size: size / 8, ) """ __slots__ = ["encoder", "decoder", "resizer"] def __init__(self, subcon, decoder, encoder, resizer): super(Buffered, self).__init__(subcon) self.encoder = encoder self.decoder = decoder self.resizer = resizer def _parse(self, stream, context): data = _read_stream(stream, self._sizeof(context)) stream2 = BytesIO(self.decoder(data)) return self.subcon._parse(stream2, context) def _build(self, obj, stream, context): size = self._sizeof(context) stream2 = BytesIO() self.subcon._build(obj, stream2, context) data = self.encoder(stream2.getvalue()) assert len(data) == size _write_stream(stream, self._sizeof(context), data) def _sizeof(self, context): return self.resizer(self.subcon._sizeof(context)) class Restream(Subconstruct): """ Wraps the stream with a read-wrapper (for parsing) or a write-wrapper (for building). The stream wrapper can buffer the data internally, reading it from- or writing it to the underlying stream as needed. For example, BitStreamReader reads whole bytes from the underlying stream, but returns them as individual bits. .. seealso:: The :func:`~construct.macros.Bitwise` macro. When the parsing or building is done, the stream's close method will be invoked. It can perform any finalization needed for the stream wrapper, but it must not close the underlying stream. .. warning:: Do not use pointers inside ``Restream``. :param subcon: the subcon :param stream_reader: the read-wrapper :param stream_writer: the write wrapper :param resizer: a function that takes the size of the subcon and "adjusts" or "resizes" it according to the encoding/decoding process. Example:: Restream(BitField("foo", 16), stream_reader = BitStreamReader, stream_writer = BitStreamWriter, resizer = lambda size: size / 8, ) """ __slots__ = ["stream_reader", "stream_writer", "resizer"] def __init__(self, subcon, stream_reader, stream_writer, resizer): super(Restream, self).__init__(subcon) self.stream_reader = stream_reader self.stream_writer = stream_writer self.resizer = resizer def _parse(self, stream, context): stream2 = self.stream_reader(stream) obj = self.subcon._parse(stream2, context) stream2.close() return obj def _build(self, obj, stream, context): stream2 = self.stream_writer(stream) self.subcon._build(obj, stream2, context) stream2.close() def _sizeof(self, context): return self.resizer(self.subcon._sizeof(context)) #=============================================================================== # miscellaneous #=============================================================================== class Reconfig(Subconstruct): """ Reconfigures a subconstruct. Reconfig can be used to change the name and set and clear flags of the inner subcon. :param name: the new name :param subcon: the subcon to reconfigure :param setflags: the flags to set (default is 0) :param clearflags: the flags to clear (default is 0) Example:: Reconfig("foo", UBInt8("bar")) """ __slots__ = [] def __init__(self, name, subcon, setflags = 0, clearflags = 0): subcon.name = name super(Reconfig, self).__init__(subcon) self._set_flag(setflags) self._clear_flag(clearflags) class Anchor(Construct): """ Gets the *anchor* (stream position) at a point in a Construct. Anchors are useful for adjusting relative offsets to absolute positions, or to measure sizes of Constructs. To get an absolute pointer, use an Anchor plus a relative offset. To get a size, place two Anchors and measure their difference. :param name: the name of the anchor .. note:: Anchor Requires a seekable stream, or at least a tellable stream; it is implemented using the ``tell()`` method of file-like objects. .. seealso:: :func:`Pointer` """ __slots__ = [] def _parse(self, stream, context): return stream.tell() def _build(self, obj, stream, context): context[self.name] = stream.tell() def _sizeof(self, context): return 0 class Value(Construct): """ A computed value. :param name: the name of the value :param func: a function that takes the context and return the computed value Example:: Struct("foo", UBInt8("width"), UBInt8("height"), Value("total_pixels", lambda ctx: ctx.width * ctx.height), ) """ __slots__ = ["func"] def __init__(self, name, func): super(Value, self).__init__(name) self.func = func self._set_flag(self.FLAG_DYNAMIC) def _parse(self, stream, context): return self.func(context) def _build(self, obj, stream, context): context[self.name] = self.func(context) def _sizeof(self, context): return 0 #class Dynamic(Construct): # """ # Dynamically creates a construct and uses it for parsing and building. # This allows you to create change the construction tree on the fly. # Deprecated. # # Parameters: # * name - the name of the construct # * factoryfunc - a function that takes the context and returns a new # construct object which will be used for parsing and building. # # Example: # def factory(ctx): # if ctx.bar == 8: # return UBInt8("spam") # if ctx.bar == 9: # return String("spam", 9) # # Struct("foo", # UBInt8("bar"), # Dynamic("spam", factory), # ) # """ # __slots__ = ["factoryfunc"] # def __init__(self, name, factoryfunc): # super(Dynamic, self).__init__(name, self.FLAG_COPY_CONTEXT) # self.factoryfunc = factoryfunc # self._set_flag(self.FLAG_DYNAMIC) # def _parse(self, stream, context): # return self.factoryfunc(context)._parse(stream, context) # def _build(self, obj, stream, context): # return self.factoryfunc(context)._build(obj, stream, context) # def _sizeof(self, context): # return self.factoryfunc(context)._sizeof(context) class LazyBound(Construct): """ Lazily bound construct, useful for constructs that need to make cyclic references (linked-lists, expression trees, etc.). :param name: the name of the construct :param bindfunc: the function (called without arguments) returning the bound construct Example:: foo = Struct("foo", UBInt8("bar"), LazyBound("next", lambda: foo), ) """ __slots__ = ["bindfunc", "bound"] def __init__(self, name, bindfunc): super(LazyBound, self).__init__(name) self.bound = None self.bindfunc = bindfunc def _parse(self, stream, context): if self.bound is None: self.bound = self.bindfunc() return self.bound._parse(stream, context) def _build(self, obj, stream, context): if self.bound is None: self.bound = self.bindfunc() self.bound._build(obj, stream, context) def _sizeof(self, context): if self.bound is None: self.bound = self.bindfunc() return self.bound._sizeof(context) class Pass(Construct): """ A do-nothing construct, useful as the default case for Switch, or to indicate Enums. .. seealso:: :func:`Switch` and the :func:`~construct.macros.Enum` macro. .. note:: This construct is a singleton. Do not try to instatiate it, as it will not work. Example:: Pass """ __slots__ = [] def _parse(self, stream, context): pass def _build(self, obj, stream, context): assert obj is None def _sizeof(self, context): return 0 Pass = Pass(None) """ A do-nothing construct, useful as the default case for Switch, or to indicate Enums. .. seealso:: :func:`Switch` and the :func:`~construct.macros.Enum` macro. .. note:: This construct is a singleton. Do not try to instatiate it, as it will not work. Example:: Pass """ class Terminator(Construct): """ Asserts the end of the stream has been reached at the point it's placed. You can use this to ensure no more unparsed data follows. .. note:: * This construct is only meaningful for parsing. For building, it's a no-op. * This construct is a singleton. Do not try to instatiate it, as it will not work. Example:: Terminator """ __slots__ = [] def _parse(self, stream, context): if stream.read(1): raise TerminatorError("expected end of stream") def _build(self, obj, stream, context): assert obj is None def _sizeof(self, context): return 0 Terminator = Terminator(None) """ Asserts the end of the stream has been reached at the point it's placed. You can use this to ensure no more unparsed data follows. .. note:: * This construct is only meaningful for parsing. For building, it's a no-op. * This construct is a singleton. Do not try to instatiate it, as it will not work. Example:: Terminator """ #======================================================================================================================= # Extra #======================================================================================================================= class ULInt24(StaticField): """ A custom made construct for handling 3-byte types as used in ancient file formats. A better implementation would be writing a more flexable version of FormatField, rather then specifically implementing it for this case """ __slots__ = ["packer"] def __init__(self, name): self.packer = Packer("> 8) _write_stream(stream, self.length, self.packer.pack(vals)) except Exception: ex = sys.exc_info()[1] raise FieldError(ex) construct_legacy-2.5.3/construct_legacy/debug.py0000666000000000000000000001010613202307142020223 0ustar 00000000000000""" Debugging utilities for constructs """ import sys import traceback import pdb import inspect from construct_legacy.core import Construct, Subconstruct from construct_legacy.lib import HexString, Container, ListContainer class Probe(Construct): """ A probe: dumps the context, stack frames, and stream content to the screen to aid the debugging process. .. seealso:: :class:`Debugger`. :param name: the display name :param show_stream: whether or not to show stream contents. default is True. the stream must be seekable. :param show_context: whether or not to show the context. default is True. :param show_stack: whether or not to show the upper stack frames. default is True. :param stream_lookahead: the number of bytes to dump when show_stack is set. default is 100. Example:: Struct("foo", UBInt8("a"), Probe("between a and b"), UBInt8("b"), ) """ __slots__ = [ "printname", "show_stream", "show_context", "show_stack", "stream_lookahead" ] counter = 0 def __init__(self, name = None, show_stream = True, show_context = True, show_stack = True, stream_lookahead = 100): super(Probe, self).__init__(None) if name is None: Probe.counter += 1 name = "" % (Probe.counter,) self.printname = name self.show_stream = show_stream self.show_context = show_context self.show_stack = show_stack self.stream_lookahead = stream_lookahead def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.printname) def _parse(self, stream, context): self.printout(stream, context) def _build(self, obj, stream, context): self.printout(stream, context) def _sizeof(self, context): return 0 def printout(self, stream, context): obj = Container() if self.show_stream: obj.stream_position = stream.tell() follows = stream.read(self.stream_lookahead) if not follows: obj.following_stream_data = "EOF reached" else: stream.seek(-len(follows), 1) obj.following_stream_data = HexString(follows) print("") if self.show_context: obj.context = context if self.show_stack: obj.stack = ListContainer() frames = [s[0] for s in inspect.stack()][1:-1] frames.reverse() for f in frames: a = Container() a.__update__(f.f_locals) obj.stack.append(a) print("=" * 80) print("Probe %s" % (self.printname,)) print(obj) print("=" * 80) class Debugger(Subconstruct): """ A pdb-based debugger. When an exception occurs in the subcon, a debugger will appear and allow you to debug the error (and even fix on-the-fly). :param subcon: the subcon to debug Example:: Debugger( Enum(UBInt8("foo"), a = 1, b = 2, c = 3 ) ) """ __slots__ = ["retval"] def _parse(self, stream, context): try: return self.subcon._parse(stream, context) except Exception: self.retval = NotImplemented self.handle_exc("(you can set the value of 'self.retval', " "which will be returned)") if self.retval is NotImplemented: raise else: return self.retval def _build(self, obj, stream, context): try: self.subcon._build(obj, stream, context) except Exception: self.handle_exc() def handle_exc(self, msg = None): print("=" * 80) print("Debugging exception of %s:" % (self.subcon,)) print("".join(traceback.format_exception(*sys.exc_info())[1:])) if msg: print(msg) pdb.post_mortem(sys.exc_info()[2]) print("=" * 80) construct_legacy-2.5.3/construct_legacy/formats/0000777000000000000000000000000013202307640020243 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/formats/data/0000777000000000000000000000000013202307640021154 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/formats/data/cap.py0000666000000000000000000000162013202307142022265 0ustar 00000000000000""" tcpdump capture file """ from construct_legacy import * import time from datetime import datetime class MicrosecAdapter(Adapter): def _decode(self, obj, context): return datetime.fromtimestamp(obj[0] + (obj[1] / 1000000.0)) def _encode(self, obj, context): offset = time.mktime(*obj.timetuple()) sec = int(offset) usec = (offset - sec) * 1000000 return (sec, usec) packet = Struct("packet", MicrosecAdapter( Sequence("time", ULInt32("time"), ULInt32("usec"), ) ), ULInt32("length"), Padding(4), HexDumpAdapter(Field("data", lambda ctx: ctx.length)), ) cap_file = Struct("cap_file", Padding(24), Rename("packets", OptionalGreedyRange(packet)), ) if __name__ == "__main__": obj = cap_file.parse_stream(open("../../tests/cap2.cap", "rb")) print(len(obj.packets)) construct_legacy-2.5.3/construct_legacy/formats/data/snoop.py0000666000000000000000000000273113202307142022664 0ustar 00000000000000""" what : snoop v2 capture file. how : http://tools.ietf.org/html/rfc1761 who : jesse @ housejunkie . ca """ import time from construct_legacy import (Adapter, Enum, Field, HexDumpAdapter, Magic, OptionalGreedyRange, Padding, Struct, UBInt32) class EpochTimeStampAdapter(Adapter): """ Convert epoch timestamp <-> localtime """ def _decode(self, obj, context): return time.ctime(obj) def _encode(self, obj, context): return int(time.mktime(time.strptime(obj))) packet_record = Struct("packet_record", UBInt32("original_length"), UBInt32("included_length"), UBInt32("record_length"), UBInt32("cumulative_drops"), EpochTimeStampAdapter(UBInt32("timestamp_seconds")), UBInt32("timestamp_microseconds"), HexDumpAdapter(Field("data", lambda ctx: ctx.included_length)), # 24 being the static length of the packet_record header Padding(lambda ctx: ctx.record_length - ctx.included_length - 24), ) datalink_type = Enum(UBInt32("datalink"), IEEE802dot3 = 0, IEEE802dot4 = 1, IEEE802dot5 = 2, IEEE802dot6 = 3, ETHERNET = 4, HDLC = 5, CHARSYNC = 6, IBMCHANNEL = 7, FDDI = 8, OTHER = 9, UNASSIGNED = 10, ) snoop_file = Struct("snoop", Magic("snoop\x00\x00\x00"), UBInt32("version"), # snoop v1 is deprecated datalink_type, OptionalGreedyRange(packet_record), ) construct_legacy-2.5.3/construct_legacy/formats/data/__init__.py0000666000000000000000000000011212756674033023277 0ustar 00000000000000""" all sorts of raw data serialization (tcpdump capture files, etc.) """ construct_legacy-2.5.3/construct_legacy/formats/executable/0000777000000000000000000000000013202307640022364 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/formats/executable/elf32.py0000666000000000000000000001006113202307141023643 0ustar 00000000000000""" Executable and Linkable Format (ELF), 32 bit, big or little endian Used on *nix systems as a replacement of the older a.out format Big-endian support kindly submitted by Craig McQueen (mcqueen-c#edsrd1!yzk!co!jp) """ from construct_legacy import * import six def elf32_body(ElfInt16, ElfInt32): elf32_program_header = Struct("program_header", Enum(ElfInt32("type"), NULL = 0, LOAD = 1, DYNAMIC = 2, INTERP = 3, NOTE = 4, SHLIB = 5, PHDR = 6, _default_ = Pass, ), ElfInt32("offset"), ElfInt32("vaddr"), ElfInt32("paddr"), ElfInt32("file_size"), ElfInt32("mem_size"), ElfInt32("flags"), ElfInt32("align"), ) elf32_section_header = Struct("section_header", ElfInt32("name_offset"), Pointer(lambda ctx: ctx._.strtab_data_offset + ctx.name_offset, CString("name") ), Enum(ElfInt32("type"), NULL = 0, PROGBITS = 1, SYMTAB = 2, STRTAB = 3, RELA = 4, HASH = 5, DYNAMIC = 6, NOTE = 7, NOBITS = 8, REL = 9, SHLIB = 10, DYNSYM = 11, _default_ = Pass, ), ElfInt32("flags"), ElfInt32("addr"), ElfInt32("offset"), ElfInt32("size"), ElfInt32("link"), ElfInt32("info"), ElfInt32("align"), ElfInt32("entry_size"), OnDemandPointer(lambda ctx: ctx.offset, HexDumpAdapter(Field("data", lambda ctx: ctx.size)) ), ) return Struct("body", Enum(ElfInt16("type"), NONE = 0, RELOCATABLE = 1, EXECUTABLE = 2, SHARED = 3, CORE = 4, ), Enum(ElfInt16("machine"), NONE = 0, M32 = 1, SPARC = 2, I386 = 3, Motorolla68K = 4, Motorolla88K = 5, Intel860 = 7, MIPS = 8, _default_ = Pass ), ElfInt32("version"), ElfInt32("entry"), ElfInt32("ph_offset"), ElfInt32("sh_offset"), ElfInt32("flags"), ElfInt16("header_size"), ElfInt16("ph_entry_size"), ElfInt16("ph_count"), ElfInt16("sh_entry_size"), ElfInt16("sh_count"), ElfInt16("strtab_section_index"), # calculate the string table data offset (pointer arithmetics) # ugh... anyway, we need it in order to read the section names, later on Pointer(lambda ctx: ctx.sh_offset + ctx.strtab_section_index * ctx.sh_entry_size + 16, ElfInt32("strtab_data_offset"), ), # program header table Rename("program_table", Pointer(lambda ctx: ctx.ph_offset, Array(lambda ctx: ctx.ph_count, elf32_program_header ) ) ), # section table Rename("sections", Pointer(lambda ctx: ctx.sh_offset, Array(lambda ctx: ctx.sh_count, elf32_section_header ) ) ), ) elf32_body_little_endian = elf32_body(ULInt16, ULInt32) elf32_body_big_endian = elf32_body(UBInt16, UBInt32) elf32_file = Struct("elf32_file", Struct("identifier", Magic(six.b("\x7fELF")), Enum(Byte("file_class"), NONE = 0, CLASS32 = 1, CLASS64 = 2, ), Enum(Byte("encoding"), NONE = 0, LSB = 1, MSB = 2, ), Byte("version"), Padding(9), ), Embedded(IfThenElse("body", lambda ctx: ctx.identifier.encoding == "LSB", elf32_body_little_endian, elf32_body_big_endian, )), ) if __name__ == "__main__": obj = elf32_file.parse_stream(open("../../../tests/_ctypes_test.so", "rb")) #[s.data.value for s in obj.sections] print(obj) construct_legacy-2.5.3/construct_legacy/formats/executable/pe32.py0000666000000000000000000002673413202307141023517 0ustar 00000000000000""" Portable Executable (PE) 32 bit, little endian Used on MSWindows systems (including DOS) for EXEs and DLLs 1999 paper: http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/pecoff.doc 2006 with updates relevant for .NET: http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/pecoff_v8.doc """ from construct_legacy import * import time import six class UTCTimeStampAdapter(Adapter): def _decode(self, obj, context): return time.ctime(obj) def _encode(self, obj, context): return int(time.mktime(time.strptime(obj))) def UTCTimeStamp(name): return UTCTimeStampAdapter(ULInt32(name)) class NamedSequence(Adapter): """ creates a mapping between the elements of a sequence and their respective names. this is useful for sequences of a variable length, where each element in the sequence has a name (as is the case with the data directories of the PE header) """ __slots__ = ["mapping", "rev_mapping"] prefix = "unnamed_" def __init__(self, subcon, mapping): super(NamedSequence, self).__init__(subcon) self.mapping = mapping self.rev_mapping = dict((v, k) for k, v in mapping.items()) def _encode(self, obj, context): d = obj.__dict__ obj2 = [None] * len(d) for name, value in d.items(): if name in self.rev_mapping: index = self.rev_mapping[name] elif name.startswith("__"): obj2.pop(-1) continue elif name.startswith(self.prefix): index = int(name.split(self.prefix)[1]) else: raise ValueError("no mapping defined for %r" % (name,)) obj2[index] = value return obj2 def _decode(self, obj, context): obj2 = Container() for i, item in enumerate(obj): if i in self.mapping: name = self.mapping[i] else: name = "%s%d" % (self.prefix, i) setattr(obj2, name, item) return obj2 msdos_header = Struct("msdos_header", Magic("MZ"), ULInt16("partPag"), ULInt16("page_count"), ULInt16("relocation_count"), ULInt16("header_size"), ULInt16("minmem"), ULInt16("maxmem"), ULInt16("relocation_stackseg"), ULInt16("exe_stackptr"), ULInt16("checksum"), ULInt16("exe_ip"), ULInt16("relocation_codeseg"), ULInt16("table_offset"), ULInt16("overlay"), Padding(8), ULInt16("oem_id"), ULInt16("oem_info"), Padding(20), ULInt32("coff_header_pointer"), Anchor("_assembly_start"), OnDemand( HexDumpAdapter( Field("code", lambda ctx: ctx.coff_header_pointer - ctx._assembly_start ) ) ), ) symbol_table = Struct("symbol_table", String("name", 8, padchar = six.b("\x00")), ULInt32("value"), Enum(ExprAdapter(SLInt16("section_number"), encoder = lambda obj, ctx: obj + 1, decoder = lambda obj, ctx: obj - 1, ), UNDEFINED = -1, ABSOLUTE = -2, DEBUG = -3, _default_ = Pass, ), Enum(ULInt8("complex_type"), NULL = 0, POINTER = 1, FUNCTION = 2, ARRAY = 3, ), Enum(ULInt8("base_type"), NULL = 0, VOID = 1, CHAR = 2, SHORT = 3, INT = 4, LONG = 5, FLOAT = 6, DOUBLE = 7, STRUCT = 8, UNION = 9, ENUM = 10, MOE = 11, BYTE = 12, WORD = 13, UINT = 14, DWORD = 15, ), Enum(ULInt8("storage_class"), END_OF_FUNCTION = 255, NULL = 0, AUTOMATIC = 1, EXTERNAL = 2, STATIC = 3, REGISTER = 4, EXTERNAL_DEF = 5, LABEL = 6, UNDEFINED_LABEL = 7, MEMBER_OF_STRUCT = 8, ARGUMENT = 9, STRUCT_TAG = 10, MEMBER_OF_UNION = 11, UNION_TAG = 12, TYPE_DEFINITION = 13, UNDEFINED_STATIC = 14, ENUM_TAG = 15, MEMBER_OF_ENUM = 16, REGISTER_PARAM = 17, BIT_FIELD = 18, BLOCK = 100, FUNCTION = 101, END_OF_STRUCT = 102, FILE = 103, SECTION = 104, WEAK_EXTERNAL = 105, ), ULInt8("number_of_aux_symbols"), Array(lambda ctx: ctx.number_of_aux_symbols, Bytes("aux_symbols", 18) ) ) coff_header = Struct("coff_header", Magic("PE\x00\x00"), Enum(ULInt16("machine_type"), UNKNOWN = 0x0, AM33 = 0x1d3, AMD64 = 0x8664, ARM = 0x1c0, EBC = 0xebc, I386 = 0x14c, IA64 = 0x200, M32R = 0x9041, MIPS16 = 0x266, MIPSFPU = 0x366, MIPSFPU16 = 0x466, POWERPC = 0x1f0, POWERPCFP = 0x1f1, R4000 = 0x166, SH3 = 0x1a2, SH3DSP = 0x1a3, SH4 = 0x1a6, SH5= 0x1a8, THUMB = 0x1c2, WCEMIPSV2 = 0x169, _default_ = Pass ), ULInt16("number_of_sections"), UTCTimeStamp("time_stamp"), ULInt32("symbol_table_pointer"), ULInt32("number_of_symbols"), ULInt16("optional_header_size"), FlagsEnum(ULInt16("characteristics"), RELOCS_STRIPPED = 0x0001, EXECUTABLE_IMAGE = 0x0002, LINE_NUMS_STRIPPED = 0x0004, LOCAL_SYMS_STRIPPED = 0x0008, AGGRESSIVE_WS_TRIM = 0x0010, LARGE_ADDRESS_AWARE = 0x0020, MACHINE_16BIT = 0x0040, BYTES_REVERSED_LO = 0x0080, MACHINE_32BIT = 0x0100, DEBUG_STRIPPED = 0x0200, REMOVABLE_RUN_FROM_SWAP = 0x0400, SYSTEM = 0x1000, DLL = 0x2000, UNIPROCESSOR_ONLY = 0x4000, BIG_ENDIAN_MACHINE = 0x8000, ), # symbol table Pointer(lambda ctx: ctx.symbol_table_pointer, Array(lambda ctx: ctx.number_of_symbols, symbol_table) ) ) def PEPlusField(name): return IfThenElse(name, lambda ctx: ctx.pe_type == "PE32_plus", ULInt64(None), ULInt32(None), ) optional_header = Struct("optional_header", # standard fields Enum(ULInt16("pe_type"), PE32 = 0x10b, PE32_plus = 0x20b, ), ULInt8("major_linker_version"), ULInt8("minor_linker_version"), ULInt32("code_size"), ULInt32("initialized_data_size"), ULInt32("uninitialized_data_size"), ULInt32("entry_point_pointer"), ULInt32("base_of_code"), # only in PE32 files If(lambda ctx: ctx.pe_type == "PE32", ULInt32("base_of_data") ), # WinNT-specific fields PEPlusField("image_base"), ULInt32("section_aligment"), ULInt32("file_alignment"), ULInt16("major_os_version"), ULInt16("minor_os_version"), ULInt16("major_image_version"), ULInt16("minor_image_version"), ULInt16("major_subsystem_version"), ULInt16("minor_subsystem_version"), Padding(4), ULInt32("image_size"), ULInt32("headers_size"), ULInt32("checksum"), Enum(ULInt16("subsystem"), UNKNOWN = 0, NATIVE = 1, WINDOWS_GUI = 2, WINDOWS_CUI = 3, POSIX_CIU = 7, WINDOWS_CE_GUI = 9, EFI_APPLICATION = 10, EFI_BOOT_SERVICE_DRIVER = 11, EFI_RUNTIME_DRIVER = 12, EFI_ROM = 13, XBOX = 14, _default_ = Pass ), FlagsEnum(ULInt16("dll_characteristics"), NO_BIND = 0x0800, WDM_DRIVER = 0x2000, TERMINAL_SERVER_AWARE = 0x8000, ), PEPlusField("reserved_stack_size"), PEPlusField("stack_commit_size"), PEPlusField("reserved_heap_size"), PEPlusField("heap_commit_size"), ULInt32("loader_flags"), ULInt32("number_of_data_directories"), NamedSequence( Array(lambda ctx: ctx.number_of_data_directories, Struct("data_directories", ULInt32("address"), ULInt32("size"), ) ), mapping = { 0 : 'export_table', 1 : 'import_table', 2 : 'resource_table', 3 : 'exception_table', 4 : 'certificate_table', 5 : 'base_relocation_table', 6 : 'debug', 7 : 'architecture', 8 : 'global_ptr', 9 : 'tls_table', 10 : 'load_config_table', 11 : 'bound_import', 12 : 'import_address_table', 13 : 'delay_import_descriptor', 14 : 'complus_runtime_header', } ), ) section = Struct("section", String("name", 8, padchar = six.b("\x00")), ULInt32("virtual_size"), ULInt32("virtual_address"), ULInt32("raw_data_size"), ULInt32("raw_data_pointer"), ULInt32("relocations_pointer"), ULInt32("line_numbers_pointer"), ULInt16("number_of_relocations"), ULInt16("number_of_line_numbers"), FlagsEnum(ULInt32("characteristics"), TYPE_REG = 0x00000000, TYPE_DSECT = 0x00000001, TYPE_NOLOAD = 0x00000002, TYPE_GROUP = 0x00000004, TYPE_NO_PAD = 0x00000008, TYPE_COPY = 0x00000010, CNT_CODE = 0x00000020, CNT_INITIALIZED_DATA = 0x00000040, CNT_UNINITIALIZED_DATA = 0x00000080, LNK_OTHER = 0x00000100, LNK_INFO = 0x00000200, TYPE_OVER = 0x00000400, LNK_REMOVE = 0x00000800, LNK_COMDAT = 0x00001000, MEM_FARDATA = 0x00008000, MEM_PURGEABLE = 0x00020000, MEM_16BIT = 0x00020000, MEM_LOCKED = 0x00040000, MEM_PRELOAD = 0x00080000, ALIGN_1BYTES = 0x00100000, ALIGN_2BYTES = 0x00200000, ALIGN_4BYTES = 0x00300000, ALIGN_8BYTES = 0x00400000, ALIGN_16BYTES = 0x00500000, ALIGN_32BYTES = 0x00600000, ALIGN_64BYTES = 0x00700000, ALIGN_128BYTES = 0x00800000, ALIGN_256BYTES = 0x00900000, ALIGN_512BYTES = 0x00A00000, ALIGN_1024BYTES = 0x00B00000, ALIGN_2048BYTES = 0x00C00000, ALIGN_4096BYTES = 0x00D00000, ALIGN_8192BYTES = 0x00E00000, LNK_NRELOC_OVFL = 0x01000000, MEM_DISCARDABLE = 0x02000000, MEM_NOT_CACHED = 0x04000000, MEM_NOT_PAGED = 0x08000000, MEM_SHARED = 0x10000000, MEM_EXECUTE = 0x20000000, MEM_READ = 0x40000000, MEM_WRITE = 0x80000000, ), OnDemandPointer(lambda ctx: ctx.raw_data_pointer, HexDumpAdapter(Field("raw_data", lambda ctx: ctx.raw_data_size)) ), OnDemandPointer(lambda ctx: ctx.line_numbers_pointer, Array(lambda ctx: ctx.number_of_line_numbers, Struct("line_numbers", ULInt32("type"), ULInt16("line_number"), ) ) ), OnDemandPointer(lambda ctx: ctx.relocations_pointer, Array(lambda ctx: ctx.number_of_relocations, Struct("relocations", ULInt32("virtual_address"), ULInt32("symbol_table_index"), ULInt16("type"), ) ) ), ) pe32_file = Struct("pe32_file", # headers msdos_header, coff_header, Anchor("_start_of_optional_header"), optional_header, Anchor("_end_of_optional_header"), Padding(lambda ctx: min(0, ctx.coff_header.optional_header_size - ctx._end_of_optional_header + ctx._start_of_optional_header ) ), # sections Array(lambda ctx: ctx.coff_header.number_of_sections, section) ) if __name__ == "__main__": print (pe32_file.parse_stream(open("../../../tests/NOTEPAD.EXE", "rb"))) print (pe32_file.parse_stream(open("../../../tests/sqlite3.dll", "rb"))) construct_legacy-2.5.3/construct_legacy/formats/executable/__init__.py0000666000000000000000000000000012756674033024503 0ustar 00000000000000construct_legacy-2.5.3/construct_legacy/formats/filesystem/0000777000000000000000000000000013202307640022427 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/formats/filesystem/ext2.py0000666000000000000000000000574013202307141023665 0ustar 00000000000000""" Extension 2 (ext2) Used in Linux systems """ from construct_legacy import * Char = SLInt8 UChar = ULInt8 Short = SLInt16 UShort = ULInt16 Long = SLInt32 ULong = ULInt32 def BlockPointer(name): return Struct(name, ULong("block_number"), OnDemandPointer(lambda ctx: ctx["block_number"]), ) superblock = Struct("superblock", ULong('inodes_count'), ULong('blocks_count'), ULong('reserved_blocks_count'), ULong('free_blocks_count'), ULong('free_inodes_count'), ULong('first_data_block'), Enum(ULong('log_block_size'), OneKB = 0, TwoKB = 1, FourKB = 2, ), Long('log_frag_size'), ULong('blocks_per_group'), ULong('frags_per_group'), ULong('inodes_per_group'), ULong('mtime'), ULong('wtime'), UShort('mnt_count'), Short('max_mnt_count'), Const(UShort('magic'), 0xEF53), UShort('state'), UShort('errors'), Padding(2), ULong('lastcheck'), ULong('checkinterval'), ULong('creator_os'), ULong('rev_level'), Padding(235 * 4), ) group_descriptor = Struct("group_descriptor", ULong('block_bitmap'), ULong('inode_bitmap'), ULong('inode_table'), UShort('free_blocks_count'), UShort('free_inodes_count'), UShort('used_dirs_count'), Padding(14), ) inode = Struct("inode", FlagsEnum(UShort('mode'), IXOTH = 0x0001, IWOTH = 0x0002, IROTH = 0x0004, IRWXO = 0x0007, IXGRP = 0x0008, IWGRP = 0x0010, IRGRP = 0x0020, IRWXG = 0x0038, IXUSR = 0x0040, IWUSR = 0x0080, IRUSR = 0x0100, IRWXU = 0x01C0, ISVTX = 0x0200, ISGID = 0x0400, ISUID = 0x0800, IFIFO = 0x1000, IFCHR = 0x2000, IFDIR = 0x4000, IFBLK = 0x6000, IFREG = 0x8000, IFLNK = 0xC000, IFSOCK = 0xA000, IFMT = 0xF000, ), UShort('uid'), ULong('size'), ULong('atime'), ULong('ctime'), ULong('mtime'), ULong('dtime'), UShort('gid'), UShort('links_count'), ULong('blocks'), FlagsEnum(ULong('flags'), SecureDelete = 0x0001, AllowUndelete = 0x0002, Compressed = 0x0004, Synchronous = 0x0008, ), Padding(4), Array(12, ULong('blocks')), ULong("indirect1_block"), ULong("indirect2_block"), ULong("indirect3_block"), ULong('version'), ULong('file_acl'), ULong('dir_acl'), ULong('faddr'), UChar('frag'), Byte('fsize'), Padding(10) , ) # special inodes EXT2_BAD_INO = 1 EXT2_ROOT_INO = 2 EXT2_ACL_IDX_INO = 3 EXT2_ACL_DATA_INO = 4 EXT2_BOOT_LOADER_INO = 5 EXT2_UNDEL_DIR_INO = 6 EXT2_FIRST_INO = 11 directory_record = Struct("directory_entry", ULong("inode"), UShort("rec_length"), UShort("name_length"), Field("name", lambda ctx: ctx["name_length"]), Padding(lambda ctx: ctx["rec_length"] - ctx["name_length"]) ) if __name__ == "__main__": print (superblock.sizeof()) construct_legacy-2.5.3/construct_legacy/formats/filesystem/fat16.py0000666000000000000000000001651713202307141023730 0ustar 00000000000000# fat.py; ad-hoc fat16 reader # by Bram Westerbaan # # references: # http://en.wikipedia.org/wiki/File_Allocation_Table # http://www.ecma-international.org/publications/standards/Ecma-107.htm # # example: # with open("/dev/sdc1") as file: # fs = FatFs(file) # for rootdir in fs: # print rootdir import numbers from io import BytesIO, BufferedReader from construct_legacy import Struct, Byte, Bytes, ULInt16, ULInt32, Enum, \ Array, Padding, Embed, Pass, BitStruct, Flag, Const def Fat16Header(name): return Struct(name, Bytes("jumpInstruction", 3), Bytes("creatingSystemId", 8), ULInt16("sectorSize"), Byte("sectorsPerCluster"), ULInt16("reservedSectorCount"), Byte("fatCount"), ULInt16("rootdirEntryCount"), ULInt16("sectorCount_small"), Byte("mediaId"), ULInt16("sectorsPerFat"), ULInt16("sectorsPerTrack"), ULInt16("sideCount"), ULInt32("hiddenSectorCount"), ULInt32("sectorCount_large"), Byte("physicalDriveNumber"), Byte("currentHead"), Byte("extendedBootSignature"), Bytes("volumeId", 4), Bytes("volumeLabel", 11), Const(Bytes("fsType", 8), "FAT16 "), Bytes("bootCode", 448), Const(Bytes("bootSectorSignature", 2), "\x55\xaa")) def BootSector(name): header = Fat16Header("header") return Struct(name, Embed(header), Padding(lambda ctx: ctx.sectorSize - header.sizeof())) def FatEntry(name): return Enum(ULInt16(name), free_cluster = 0x0000, bad_cluster = 0xfff7, last_cluster = 0xffff, _default_ = Pass) def DirEntry(name): return Struct(name, Bytes("name", 8), Bytes("extension", 3), BitStruct("attributes", Flag("unused"), Flag("device"), Flag("archive"), Flag("subDirectory"), Flag("volumeLabel"), Flag("system"), Flag("hidden"), Flag("readonly")), # reserved Padding(10), ULInt16("timeRecorded"), ULInt16("dateRecorded"), ULInt16("firstCluster"), ULInt32("fileSize")) def PreDataRegion(name): rde = DirEntry("rootdirs") fe = FatEntry("fats") return Struct(name, Embed(BootSector("bootSector")), # the remaining reserved sectors Padding(lambda ctx: (ctx.reservedSectorCount - 1) * ctx.sectorSize), # file allocation tables Array(lambda ctx: (ctx.fatCount), Array(lambda ctx: ctx.sectorsPerFat * ctx.sectorSize / fe.sizeof(), fe)), # root directories Array(lambda ctx: (ctx.rootdirEntryCount*rde.sizeof()) / ctx.sectorSize, rde)) class File(object): def __init__(self, dirEntry, fs): self.fs = fs self.dirEntry = dirEntry @classmethod def fromDirEntry(cls, dirEntry, fs): if dirEntry.name[0] in "\x00\xe5\x2e": return None a = dirEntry.attributes #Long file name directory entry if a.volumeLabel and a.system and a.hidden and a.readonly: return None if a.subDirectory: return Directory(dirEntry, fs) return File(dirEntry, fs) @classmethod def fromDirEntries(cls, dirEntries, fs): return filter(None, [cls.fromDirEntry(de, fs) for de in dirEntries]) def toStream(self, stream): self.fs.fileToStream(self.dirEntry.firstCluster, stream) @property def name(self): return "%s.%s" % (self.dirEntry.name.rstrip(), self.dirEntry.extension) def __str__(self): return "&%s %s" % (self.dirEntry.firstCluster, self.name) class Directory(File): def __init__(self, dirEntry, fs, children=None): super(Directory, self).__init__(dirEntry, fs) self.children = children if not self.children: self.children = File.fromDirEntries(\ self.fs.getDirEntries(\ self.dirEntry.firstCluster), fs) @property def name(self): return self.dirEntry.name.rstrip() def __str__(self): return "&%s %s/" % (self.dirEntry.firstCluster, self.name) def __getitem__(self, name): for file in self.children: if file.name == name: return file def __iter__(self): return iter(self.children) class FatFs(Directory): def __init__(self, stream): self.stream = stream self.pdr = PreDataRegion("pdr").parse_stream(stream) super(FatFs, self).__init__(dirEntry = None, fs = self, children = File.fromDirEntries( self.pdr.rootdirs, self)) def fileToStream(self, clidx, stream): for clidx in self.getLinkedClusters(clidx): self.clusterToStream(clidx, stream) def clusterToStream(self, clidx, stream): start, todo = self.getClusterSlice(clidx) self.stream.seek(start) while todo > 0: read = self.stream.read(todo) if not len(read): print("failed to read %s bytes at %s" % (todo, self.stream.tell())) raise EOFError() todo -= len(read) stream.write(read) def getClusterSlice(self, clidx): startSector = self.pdr.reservedSectorCount \ + self.pdr.fatCount * self.pdr.sectorsPerFat \ + (self.pdr.rootdirEntryCount * 32) \ / self.pdr.sectorSize \ + (clidx-2) * self.pdr.sectorsPerCluster start = startSector * self.pdr.sectorSize length = self.pdr.sectorSize * self.pdr.sectorsPerCluster return (start, length) def getLinkedClusters(self, clidx): res = [] while clidx != "last_cluster": if not isinstance(clidx, numbers.Real): print(clidx) assert False assert 2 <= clidx <= 0xffef res.append(clidx) clidx = self.getNextCluster(clidx) assert clidx not in res return res def getNextCluster(self, clidx): ress = set([fat[clidx] for fat in self.pdr.fats]) if len(ress)==1: return ress.pop() print("inconsistencie between FATs: %s points to" % clidx) for i,fat in enumerate(self.pdr.fats): print("\t%s according to fat #%s" % (fat[clidx], i)) res = ress.pop() print ("assuming %s" % res) return res def getDirEntries(self, clidx): try: for de in self._getDirEntries(clidx): yield de except IOError: print("failed to read directory entries at %s" % clidx) def _getDirEntries(self, clidx): de = DirEntry("dirEntry") with BytesIO() as mem: self.fileToStream(clidx, mem) mem.seek(0) with BufferedReader(mem) as reader: while reader.peek(1): yield de.parse_stream(reader) def __str__(self): return "/" @property def name(self): return "" construct_legacy-2.5.3/construct_legacy/formats/filesystem/mbr.py0000666000000000000000000000512013202307140023552 0ustar 00000000000000""" Master Boot Record The first sector on disk, contains the partition table, bootloader, et al. http://www.win.tue.nl/~aeb/partitions/partition_types-1.html """ from construct_legacy import * from binascii import unhexlify import six mbr = Struct("mbr", HexDumpAdapter(Bytes("bootloader_code", 446)), Array(4, Struct("partitions", Enum(Byte("state"), INACTIVE = 0x00, ACTIVE = 0x80, ), BitStruct("beginning", Octet("head"), Bits("sect", 6), Bits("cyl", 10), ), Enum(UBInt8("type"), Nothing = 0x00, FAT12 = 0x01, XENIX_ROOT = 0x02, XENIX_USR = 0x03, FAT16_old = 0x04, Extended_DOS = 0x05, FAT16 = 0x06, FAT32 = 0x0b, FAT32_LBA = 0x0c, NTFS = 0x07, LINUX_SWAP = 0x82, LINUX_NATIVE = 0x83, _default_ = Pass, ), BitStruct("ending", Octet("head"), Bits("sect", 6), Bits("cyl", 10), ), UBInt32("sector_offset"), # offset from MBR in sectors UBInt32("size"), # in sectors ) ), Const(Bytes("signature", 2), six.b("\x55\xAA")), ) if __name__ == "__main__": cap1 = unhexlify(six.b( "33C08ED0BC007CFB5007501FFCBE1B7CBF1B065057B9E501F3A4CBBDBE07B104386E00" "7C09751383C510E2F4CD188BF583C610497419382C74F6A0B507B4078BF0AC3C0074FC" "BB0700B40ECD10EBF2884E10E84600732AFE4610807E040B740B807E040C7405A0B607" "75D2804602068346080683560A00E821007305A0B607EBBC813EFE7D55AA740B807E10" "0074C8A0B707EBA98BFC1E578BF5CBBF05008A5600B408CD1372238AC1243F988ADE8A" "FC43F7E38BD186D6B106D2EE42F7E239560A77237205394608731CB80102BB007C8B4E" "028B5600CD1373514F744E32E48A5600CD13EBE48A560060BBAA55B441CD13723681FB" "55AA7530F6C101742B61606A006A00FF760AFF76086A0068007C6A016A10B4428BF4CD" "136161730E4F740B32E48A5600CD13EBD661F9C3496E76616C69642070617274697469" "6F6E207461626C65004572726F72206C6F6164696E67206F7065726174696E67207379" "7374656D004D697373696E67206F7065726174696E672073797374656D000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "00000000000000000000000000000000002C4463B7BDB7BD00008001010007FEFFFF3F" "000000371671020000C1FF0FFEFFFF761671028A8FDF06000000000000000000000000" "000000000000000000000000000000000000000055AA")) print(mbr.parse(cap1)) construct_legacy-2.5.3/construct_legacy/formats/filesystem/__init__.py0000666000000000000000000000014312756674033024556 0ustar 00000000000000""" file systems on-disk formats (ext2, fat32, ntfs, ...) and related disk formats (mbr, ...) """ construct_legacy-2.5.3/construct_legacy/formats/graphics/0000777000000000000000000000000013202307640022043 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/formats/graphics/bmp.py0000666000000000000000000000640113202307140023167 0ustar 00000000000000""" Windows/OS2 Bitmap (BMP) this could have been a perfect show-case file format, but they had to make it ugly (all sorts of alignment or """ from construct_legacy import * #=============================================================================== # pixels: uncompressed #=============================================================================== def UncompressedRows(subcon, align_to_byte = False): """argh! lines must be aligned to a 4-byte boundary, and bit-pixel lines must be aligned to full bytes...""" if align_to_byte: line_pixels = Bitwise( Aligned(Array(lambda ctx: ctx.width, subcon), modulus = 8) ) else: line_pixels = Array(lambda ctx: ctx.width, subcon) return Array(lambda ctx: ctx.height, Aligned(line_pixels, modulus = 4) ) uncompressed_pixels = Switch("uncompressed", lambda ctx: ctx.bpp, { 1 : UncompressedRows(Bit("index"), align_to_byte = True), 4 : UncompressedRows(Nibble("index"), align_to_byte = True), 8 : UncompressedRows(Byte("index")), 24 : UncompressedRows( Sequence("rgb", Byte("red"), Byte("green"), Byte("blue")) ), } ) #=============================================================================== # pixels: Run Length Encoding (RLE) 8 bit #=============================================================================== class RunLengthAdapter(Adapter): def _encode(self, obj): return len(obj), obj[0] def _decode(self, obj): length, value = obj return [value] * length rle8pixel = RunLengthAdapter( Sequence("rle8pixel", Byte("length"), Byte("value") ) ) #=============================================================================== # file structure #=============================================================================== bitmap_file = Struct("bitmap_file", # header Const(String("signature", 2), "BM"), ULInt32("file_size"), Padding(4), ULInt32("data_offset"), ULInt32("header_size"), Enum(Alias("version", "header_size"), v2 = 12, v3 = 40, v4 = 108, ), ULInt32("width"), ULInt32("height"), Value("number_of_pixels", lambda ctx: ctx.width * ctx.height), ULInt16("planes"), ULInt16("bpp"), # bits per pixel Enum(ULInt32("compression"), Uncompressed = 0, RLE8 = 1, RLE4 = 2, Bitfields = 3, JPEG = 4, PNG = 5, ), ULInt32("image_data_size"), # in bytes ULInt32("horizontal_dpi"), ULInt32("vertical_dpi"), ULInt32("colors_used"), ULInt32("important_colors"), # palette (24 bit has no palette) OnDemand( Array(lambda ctx: 2 ** ctx.bpp if ctx.bpp <= 8 else 0, Struct("palette", Byte("blue"), Byte("green"), Byte("red"), Padding(1), ) ) ), # pixels OnDemandPointer(lambda ctx: ctx.data_offset, Switch("pixels", lambda ctx: ctx.compression, { "Uncompressed" : uncompressed_pixels, } ), ), ) if __name__ == "__main__": obj = bitmap_file.parse_stream(open("../../../tests/bitmap8.bmp", "rb")) print (obj) print (repr(obj.pixels.value)) construct_legacy-2.5.3/construct_legacy/formats/graphics/emf.py0000666000000000000000000001136113202307140023161 0ustar 00000000000000""" Enhanced Meta File """ from construct_legacy import * record_type = Enum(ULInt32("record_type"), ABORTPATH = 68, ANGLEARC = 41, ARC = 45, ARCTO = 55, BEGINPATH = 59, BITBLT = 76, CHORD = 46, CLOSEFIGURE = 61, CREATEBRUSHINDIRECT = 39, CREATEDIBPATTERNBRUSHPT = 94, CREATEMONOBRUSH = 93, CREATEPALETTE = 49, CREATEPEN = 38, DELETEOBJECT = 40, ELLIPSE = 42, ENDPATH = 60, EOF = 14, EXCLUDECLIPRECT = 29, EXTCREATEFONTINDIRECTW = 82, EXTCREATEPEN = 95, EXTFLOODFILL = 53, EXTSELECTCLIPRGN = 75, EXTTEXTOUTA = 83, EXTTEXTOUTW = 84, FILLPATH = 62, FILLRGN = 71, FLATTENPATH = 65, FRAMERGN = 72, GDICOMMENT = 70, HEADER = 1, INTERSECTCLIPRECT = 30, INVERTRGN = 73, LINETO = 54, MASKBLT = 78, MODIFYWORLDTRANSFORM = 36, MOVETOEX = 27, OFFSETCLIPRGN = 26, PAINTRGN = 74, PIE = 47, PLGBLT = 79, POLYBEZIER = 2, POLYBEZIER16 = 85, POLYBEZIERTO = 5, POLYBEZIERTO16 = 88, POLYDRAW = 56, POLYDRAW16 = 92, POLYGON = 3, POLYGON16 = 86, POLYLINE = 4, POLYLINE16 = 87, POLYLINETO = 6, POLYLINETO16 = 89, POLYPOLYGON = 8, POLYPOLYGON16 = 91, POLYPOLYLINE = 7, POLYPOLYLINE16 = 90, POLYTEXTOUTA = 96, POLYTEXTOUTW = 97, REALIZEPALETTE = 52, RECTANGLE = 43, RESIZEPALETTE = 51, RESTOREDC = 34, ROUNDRECT = 44, SAVEDC = 33, SCALEVIEWPORTEXTEX = 31, SCALEWINDOWEXTEX = 32, SELECTCLIPPATH = 67, SELECTOBJECT = 37, SELECTPALETTE = 48, SETARCDIRECTION = 57, SETBKCOLOR = 25, SETBKMODE = 18, SETBRUSHORGEX = 13, SETCOLORADJUSTMENT = 23, SETDIBITSTODEVICE = 80, SETMAPMODE = 17, SETMAPPERFLAGS = 16, SETMETARGN = 28, SETMITERLIMIT = 58, SETPALETTEENTRIES = 50, SETPIXELV = 15, SETPOLYFILLMODE = 19, SETROP2 = 20, SETSTRETCHBLTMODE = 21, SETTEXTALIGN = 22, SETTEXTCOLOR = 24, SETVIEWPORTEXTEX = 11, SETVIEWPORTORGEX = 12, SETWINDOWEXTEX = 9, SETWINDOWORGEX = 10, SETWORLDTRANSFORM = 35, STRETCHBLT = 77, STRETCHDIBITS = 81, STROKEANDFILLPATH = 63, STROKEPATH = 64, WIDENPATH = 66, _default_ = Pass, ) generic_record = Struct("records", record_type, ULInt32("record_size"), # Size of the record in bytes Union("params", # Parameters Field("raw", lambda ctx: ctx._.record_size - 8), Array(lambda ctx: (ctx._.record_size - 8) // 4, ULInt32("params")) ), ) header_record = Struct("header_record", Const(record_type, "HEADER"), ULInt32("record_size"), # Size of the record in bytes SLInt32("bounds_left"), # Left inclusive bounds SLInt32("bounds_right"), # Right inclusive bounds SLInt32("bounds_top"), # Top inclusive bounds SLInt32("bounds_bottom"), # Bottom inclusive bounds SLInt32("frame_left"), # Left side of inclusive picture frame SLInt32("frame_right"), # Right side of inclusive picture frame SLInt32("frame_top"), # Top side of inclusive picture frame SLInt32("frame_bottom"), # Bottom side of inclusive picture frame Const(ULInt32("signature"), 0x464D4520), ULInt32("version"), # Version of the metafile ULInt32("size"), # Size of the metafile in bytes ULInt32("num_of_records"), # Number of records in the metafile ULInt16("num_of_handles"), # Number of handles in the handle table Padding(2), ULInt32("description_size"), # Size of description string in WORDs ULInt32("description_offset"), # Offset of description string in metafile ULInt32("num_of_palette_entries"), # Number of color palette entries SLInt32("device_width_pixels"), # Width of reference device in pixels SLInt32("device_height_pixels"), # Height of reference device in pixels SLInt32("device_width_mm"), # Width of reference device in millimeters SLInt32("device_height_mm"), # Height of reference device in millimeters # description string Pointer(lambda ctx: ctx.description_offset, StringAdapter( Array(lambda ctx: ctx.description_size, Field("description", 2) ) ) ), # padding up to end of record Padding(lambda ctx: ctx.record_size - 88), ) emf_file = Struct("emf_file", header_record, Array(lambda ctx: ctx.header_record.num_of_records - 1, generic_record ), ) if __name__ == "__main__": obj = emf_file.parse_stream(open("../../../tests/emf1.emf", "rb")) print (obj) construct_legacy-2.5.3/construct_legacy/formats/graphics/gif.py0000666000000000000000000001000613202307140023152 0ustar 00000000000000# Contributed by # Dany Zatuchna (danzat at gmail) """ Implementation of the following grammar for the GIF89a file format ::= Header * Trailer ::= Logical Screen Descriptor [Global Color Table] ::= | ::= [Graphic Control Extension] ::= | Plain Text Extension ::= Image Descriptor [Local Color Table] Image Data ::= Application Extension | Comment Extension """ from construct_legacy import * import six data_sub_block = Struct("data_sub_block", ULInt8("size"), String("data", lambda ctx: ctx["size"]) ) gif_logical_screen = Struct("logical_screen", ULInt16("width"), ULInt16("height"), BitStruct("flags", Bit("global_color_table"), BitField("color_resolution", 3), Bit("sort_flag"), BitField("global_color_table_bpp", 3) ), ULInt8("bgcolor_index"), ULInt8("pixel_aspect_ratio"), If(lambda ctx: ctx["flags"]["global_color_table"], Array(lambda ctx: 2**(ctx["flags"]["global_color_table_bpp"] + 1), Struct("palette", ULInt8("R"), ULInt8("G"), ULInt8("B") ) ) ) ) gif_header = Struct("gif_header", Const(String("signature", 3), six.b("GIF")), Const(String("version", 3), six.b("89a")), ) application_extension = Struct("application_extension", Const(ULInt8("block_size"), 11), String("application_identifier", 8), String("application_auth_code", 3), data_sub_block, ULInt8("block_terminator") ) comment_extension = Struct("comment_extension", data_sub_block, ULInt8("block_terminator") ) graphic_control_extension = Struct("graphic_control_extension", Const(ULInt8("block_size"), 4), BitStruct("flags", BitField("reserved", 3), BitField("disposal_method", 3), Bit("user_input_flag"), Bit("transparent_color_flag"), ), ULInt16("delay"), ULInt8("transparent_color_index"), ULInt8("block_terminator") ) plain_text_extension = Struct("plain_text_extension", Const(ULInt8("block_size"), 12), ULInt16("text_left"), ULInt16("text_top"), ULInt16("text_width"), ULInt16("text_height"), ULInt8("cell_width"), ULInt8("cell_height"), ULInt8("foreground_index"), ULInt8("background_index"), data_sub_block, ULInt8("block_terminator") ) extension = Struct("extension", ULInt8("label"), Switch("ext", lambda ctx: ctx["label"], { 0xFF: application_extension, 0xFE: comment_extension, 0xF9: graphic_control_extension, 0x01: plain_text_extension }) ) image_descriptor = Struct("image_descriptor", ULInt16("left"), ULInt16("top"), ULInt16("width"), ULInt16("height"), BitStruct("flags", Bit("local_color_table"), Bit("interlace"), Bit("sort"), BitField("reserved", 2), BitField("local_color_table_bpp", 3) ), If(lambda ctx: ctx["flags"]["local_color_table"], Array(lambda ctx: 2**(ctx["flags"]["local_color_table_bpp"] + 1), Struct("palette", ULInt8("R"), ULInt8("G"), ULInt8("B") ) ) ), ULInt8("LZW_minimum_code_size"), RepeatUntil(lambda obj, ctx: obj.size == 0, data_sub_block) ) gif_data = Struct("gif_data", ULInt8("introducer"), Switch("dat", lambda ctx: ctx["introducer"], { 0x21: extension, 0x2C: image_descriptor }) ) gif_file = Struct("gif_file", gif_header, gif_logical_screen, OptionalGreedyRange(gif_data), #Const(ULInt8("trailer"), 0x3B) ) if __name__ == "__main__": f = open("../../../tests/sample.gif", "rb") s = f.read() f.close() print(gif_file.parse(s)) construct_legacy-2.5.3/construct_legacy/formats/graphics/png.py0000666000000000000000000002642113202307140023201 0ustar 00000000000000""" Portable Network Graphics (PNG) file format Official spec: http://www.w3.org/TR/PNG Original code contributed by Robin Munn (rmunn at pobox dot com) (although the code has been extensively reorganized to meet Construct's coding conventions) """ from construct_legacy import * #=============================================================================== # utils #=============================================================================== def Coord(name, field=UBInt8): return Struct(name, field("x"), field("y"), ) compression_method = Enum(UBInt8("compression_method"), deflate = 0, _default_ = Pass ) #=============================================================================== # 11.2.3: PLTE - Palette #=============================================================================== plte_info = Struct("plte_info", Value("num_entries", lambda ctx: ctx._.length / 3), Array(lambda ctx: ctx.num_entries, Struct("palette_entries", UBInt8("red"), UBInt8("green"), UBInt8("blue"), ), ), ) #=============================================================================== # 11.2.4: IDAT - Image data #=============================================================================== idat_info = OnDemand( Field("idat_info", lambda ctx: ctx.length), ) #=============================================================================== # 11.3.2.1: tRNS - Transparency #=============================================================================== trns_info = Switch("trns_info", lambda ctx: ctx._.image_header.color_type, { "greyscale": Struct("data", UBInt16("grey_sample") ), "truecolor": Struct("data", UBInt16("red_sample"), UBInt16("blue_sample"), UBInt16("green_sample"), ), "indexed": Array(lambda ctx: ctx.length, UBInt8("alpha"), ), } ) #=============================================================================== # 11.3.3.1: cHRM - Primary chromacities and white point #=============================================================================== chrm_info = Struct("chrm_info", Coord("white_point", UBInt32), Coord("red", UBInt32), Coord("green", UBInt32), Coord("blue", UBInt32), ) #=============================================================================== # 11.3.3.2: gAMA - Image gamma #=============================================================================== gama_info = Struct("gama_info", UBInt32("gamma"), ) #=============================================================================== # 11.3.3.3: iCCP - Embedded ICC profile #=============================================================================== iccp_info = Struct("iccp_info", CString("name"), compression_method, Field("compressed_profile", lambda ctx: ctx._.length - (len(ctx.name) + 2) ), ) #=============================================================================== # 11.3.3.4: sBIT - Significant bits #=============================================================================== sbit_info = Switch("sbit_info", lambda ctx: ctx._.image_header.color_type, { "greyscale": Struct("data", UBInt8("significant_grey_bits"), ), "truecolor": Struct("data", UBInt8("significant_red_bits"), UBInt8("significant_green_bits"), UBInt8("significant_blue_bits"), ), "indexed": Struct("data", UBInt8("significant_red_bits"), UBInt8("significant_green_bits"), UBInt8("significant_blue_bits"), ), "greywithalpha": Struct("data", UBInt8("significant_grey_bits"), UBInt8("significant_alpha_bits"), ), "truewithalpha": Struct("data", UBInt8("significant_red_bits"), UBInt8("significant_green_bits"), UBInt8("significant_blue_bits"), UBInt8("significant_alpha_bits"), ), } ) #=============================================================================== # 11.3.3.5: sRGB - Standard RPG color space #=============================================================================== srgb_info = Struct("srgb_info", Enum(UBInt8("rendering_intent"), perceptual = 0, relative_colorimetric = 1, saturation = 2, absolute_colorimetric = 3, _default_ = Pass, ), ) #=============================================================================== # 11.3.4.3: tEXt - Textual data #=============================================================================== text_info = Struct("text_info", CString("keyword"), Field("text", lambda ctx: ctx._.length - (len(ctx.keyword) + 1)), ) #=============================================================================== # 11.3.4.4: zTXt - Compressed textual data #=============================================================================== ztxt_info = Struct("ztxt_info", CString("keyword"), compression_method, OnDemand( Field("compressed_text", # As with iCCP, length is chunk length, minus length of # keyword, minus two: one byte for the null terminator, # and one byte for the compression method. lambda ctx: ctx._.length - (len(ctx.keyword) + 2), ), ), ) #=============================================================================== # 11.3.4.5: iTXt - International textual data #=============================================================================== itxt_info = Struct("itxt_info", CString("keyword"), UBInt8("compression_flag"), compression_method, CString("language_tag"), CString("translated_keyword"), OnDemand( Field("text", lambda ctx: ctx._.length - (len(ctx.keyword) + len(ctx.language_tag) + len(ctx.translated_keyword) + 5), ), ), ) #=============================================================================== # 11.3.5.1: bKGD - Background color #=============================================================================== bkgd_info = Switch("bkgd_info", lambda ctx: ctx._.image_header.color_type, { "greyscale": Struct("data", UBInt16("background_greyscale_value"), Alias("grey", "background_greyscale_value"), ), "greywithalpha": Struct("data", UBInt16("background_greyscale_value"), Alias("grey", "background_greyscale_value"), ), "truecolor": Struct("data", UBInt16("background_red_value"), UBInt16("background_green_value"), UBInt16("background_blue_value"), Alias("red", "background_red_value"), Alias("green", "background_green_value"), Alias("blue", "background_blue_value"), ), "truewithalpha": Struct("data", UBInt16("background_red_value"), UBInt16("background_green_value"), UBInt16("background_blue_value"), Alias("red", "background_red_value"), Alias("green", "background_green_value"), Alias("blue", "background_blue_value"), ), "indexed": Struct("data", UBInt16("background_palette_index"), Alias("index", "background_palette_index"), ), } ) #=============================================================================== # 11.3.5.2: hIST - Image histogram #=============================================================================== hist_info = Array(lambda ctx: ctx._.length / 2, UBInt16("frequency"), ) #=============================================================================== # 11.3.5.3: pHYs - Physical pixel dimensions #=============================================================================== phys_info = Struct("phys_info", UBInt32("pixels_per_unit_x"), UBInt32("pixels_per_unit_y"), Enum(UBInt8("unit"), unknown = 0, meter = 1, _default_ = Pass ), ) #=============================================================================== # 11.3.5.4: sPLT - Suggested palette #=============================================================================== def splt_info_data_length(ctx): if ctx.sample_depth == 8: entry_size = 6 else: entry_size = 10 return (ctx._.length - len(ctx.name) - 2) / entry_size splt_info = Struct("data", CString("name"), UBInt8("sample_depth"), Array(lambda ctx: splt_info_data_length, IfThenElse("table", lambda ctx: ctx.sample_depth == 8, # Sample depth 8 Struct("table", UBInt8("red"), UBInt8("green"), UBInt8("blue"), UBInt8("alpha"), UBInt16("frequency"), ), # Sample depth 16 Struct("table", UBInt16("red"), UBInt16("green"), UBInt16("blue"), UBInt16("alpha"), UBInt16("frequency"), ), ), ), ) #=============================================================================== # 11.3.6.1: tIME - Image last-modification time #=============================================================================== time_info = Struct("data", UBInt16("year"), UBInt8("month"), UBInt8("day"), UBInt8("hour"), UBInt8("minute"), UBInt8("second"), ) #=============================================================================== # chunks #=============================================================================== default_chunk_info = OnDemand( HexDumpAdapter(Field(None, lambda ctx: ctx.length)) ) chunk = Struct("chunk", UBInt32("length"), String("type", 4), Switch("data", lambda ctx: ctx.type, { "PLTE" : plte_info, "IEND" : Pass, "IDAT" : idat_info, "tRNS" : trns_info, "cHRM" : chrm_info, "gAMA" : gama_info, "iCCP" : iccp_info, "sBIT" : sbit_info, "sRGB" : srgb_info, "tEXt" : text_info, "zTXt" : ztxt_info, "iTXt" : itxt_info, "bKGD" : bkgd_info, "hIST" : hist_info, "pHYs" : phys_info, "sPLT" : splt_info, "tIME" : time_info, }, default = default_chunk_info, ), UBInt32("crc"), ) image_header_chunk = Struct("image_header", UBInt32("length"), Magic(b"IHDR"), UBInt32("width"), UBInt32("height"), UBInt8("bit_depth"), Enum(UBInt8("color_type"), greyscale = 0, truecolor = 2, indexed = 3, greywithalpha = 4, truewithalpha = 6, _default_ = Pass, ), compression_method, Enum(UBInt8("filter_method"), # "adaptive filtering with five basic filter types" adaptive5 = 0, _default_ = Pass, ), Enum(UBInt8("interlace_method"), none = 0, adam7 = 1, _default_ = Pass, ), UBInt32("crc"), ) #=============================================================================== # the complete PNG file #=============================================================================== png_file = Struct("png", Magic(b"\x89PNG\r\n\x1a\n"), image_header_chunk, Rename("chunks", GreedyRange(chunk)), ) construct_legacy-2.5.3/construct_legacy/formats/graphics/wmf.py0000666000000000000000000000672613202307137023222 0ustar 00000000000000""" Windows Meta File """ from construct_legacy import * wmf_record = Struct("records", ULInt32("size"), # size in words, including the size, function and params Enum(ULInt16("function"), AbortDoc = 0x0052, Aldus_Header = 0x0001, AnimatePalette = 0x0436, Arc = 0x0817, BitBlt = 0x0922, Chord = 0x0830, CLP_Header16 = 0x0002, CLP_Header32 = 0x0003, CreateBitmap = 0x06FE, CreateBitmapIndirect = 0x02FD, CreateBrush = 0x00F8, CreateBrushIndirect = 0x02FC, CreateFontIndirect = 0x02FB, CreatePalette = 0x00F7, CreatePatternBrush = 0x01F9, CreatePenIndirect = 0x02FA, CreateRegion = 0x06FF, DeleteObject = 0x01F0, DibBitblt = 0x0940, DibCreatePatternBrush = 0x0142, DibStretchBlt = 0x0B41, DrawText = 0x062F, Ellipse = 0x0418, EndDoc = 0x005E, EndPage = 0x0050, EOF = 0x0000, Escape = 0x0626, ExcludeClipRect = 0x0415, ExtFloodFill = 0x0548, ExtTextOut = 0x0A32, FillRegion = 0x0228, FloodFill = 0x0419, FrameRegion = 0x0429, Header = 0x0004, IntersectClipRect = 0x0416, InvertRegion = 0x012A, LineTo = 0x0213, MoveTo = 0x0214, OffsetClipRgn = 0x0220, OffsetViewportOrg = 0x0211, OffsetWindowOrg = 0x020F, PaintRegion = 0x012B, PatBlt = 0x061D, Pie = 0x081A, Polygon = 0x0324, Polyline = 0x0325, PolyPolygon = 0x0538, RealizePalette = 0x0035, Rectangle = 0x041B, ResetDC = 0x014C, ResizePalette = 0x0139, RestoreDC = 0x0127, RoundRect = 0x061C, SaveDC = 0x001E, ScaleViewportExt = 0x0412, ScaleWindowExt = 0x0410, SelectClipRegion = 0x012C, SelectObject = 0x012D, SelectPalette = 0x0234, SetBKColor = 0x0201, SetBKMode = 0x0102, SetDibToDev = 0x0D33, SelLayout = 0x0149, SetMapMode = 0x0103, SetMapperFlags = 0x0231, SetPalEntries = 0x0037, SetPixel = 0x041F, SetPolyFillMode = 0x0106, SetReLabs = 0x0105, SetROP2 = 0x0104, SetStretchBltMode = 0x0107, SetTextAlign = 0x012E, SetTextCharExtra = 0x0108, SetTextColor = 0x0209, SetTextJustification = 0x020A, SetViewportExt = 0x020E, SetViewportOrg = 0x020D, SetWindowExt = 0x020C, SetWindowOrg = 0x020B, StartDoc = 0x014D, StartPage = 0x004F, StretchBlt = 0x0B23, StretchDIB = 0x0F43, TextOut = 0x0521, _default_ = Pass, ), Array(lambda ctx: ctx.size - 3, ULInt16("params")), ) wmf_placeable_header = Struct("placeable_header", Const(ULInt32("key"), 0x9AC6CDD7), ULInt16("handle"), SLInt16("left"), SLInt16("top"), SLInt16("right"), SLInt16("bottom"), ULInt16("units_per_inch"), Padding(4), ULInt16("checksum") ) wmf_file = Struct("wmf_file", # --- optional placeable header --- Optional(wmf_placeable_header), # --- header --- Enum(ULInt16("type"), InMemory = 0, File = 1, ), Const(ULInt16("header_size"), 9), ULInt16("version"), ULInt32("size"), # file size is in words ULInt16("number_of_objects"), ULInt32("size_of_largest_record"), ULInt16("number_of_params"), # --- records --- GreedyRange(wmf_record) ) construct_legacy-2.5.3/construct_legacy/formats/graphics/__init__.py0000666000000000000000000000014312756674033024172 0ustar 00000000000000""" graphic file formats, including imagery (bmp, jpg, gif, png, ...), models (3ds, ...), etc. """ construct_legacy-2.5.3/construct_legacy/formats/__init__.py0000666000000000000000000000000012756674033022362 0ustar 00000000000000construct_legacy-2.5.3/construct_legacy/lib/0000777000000000000000000000000013202307640017336 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/lib/binary.py0000666000000000000000000001352613202307137021204 0ustar 00000000000000import six from construct_legacy.lib.py3compat import int2byte if six.PY3: def int_to_bin(number, width = 32): r""" Convert an integer into its binary representation in a bytes object. Width is the amount of bits to generate. If width is larger than the actual amount of bits required to represent number in binary, sign-extension is used. If it's smaller, the representation is trimmed to width bits. Each "bit" is either '\x00' or '\x01'. The MSBit is first. Examples: >>> int_to_bin(19, 5) b'\x01\x00\x00\x01\x01' >>> int_to_bin(19, 8) b'\x00\x00\x00\x01\x00\x00\x01\x01' """ number = int(number) if number < 0: number += 1 << width i = width - 1 bits = bytearray(width) while number and i >= 0: bits[i] = number & 1 number >>= 1 i -= 1 return bytes(bits) # heavily optimized for performance def bin_to_int(bits, signed = False): r""" Logical opposite of int_to_bin. Both '0' and '\x00' are considered zero, and both '1' and '\x01' are considered one. Set sign to True to interpret the number as a 2-s complement signed integer. """ bits = "".join("01"[b & 1] for b in bits) if signed and bits[0] == "1": bits = bits[1:] bias = 1 << len(bits) else: bias = 0 return int(bits, 2) - bias _char_to_bin = [0] * 256 _bin_to_char = {} for i in range(256): ch = int2byte(i) bin = int_to_bin(i, 8) # Populate with for both keys i and ch, to support Python 2 & 3 _char_to_bin[i] = bin _bin_to_char[bin] = ord(ch) def encode_bin(data): """ Create a binary representation of the given b'' object. Assume 8-bit ASCII. Example: >>> encode_bin('ab') b"\x00\x01\x01\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x01\x00" """ return six.b("").join(_char_to_bin[int(ch)] for ch in data) def decode_bin(data): if len(data) & 7: raise ValueError("Data length must be a multiple of 8") i = 0 j = 0 l = len(data) // 8 arr = bytearray(l) while j < l: arr[j] = _bin_to_char[data[i:i+8]] i += 8 j += 1 return arr def swap_bytes(bits, bytesize=8): r""" Bits is a b'' object containing a binary representation. Assuming each bytesize bits constitute a bytes, perform a endianness byte swap. Example: >>> swap_bytes(b'00011011', 2) b'11100100' """ i = 0 l = len(bits) output = [six.b("")] * ((l // bytesize) + 1) j = len(output) - 1 while i < l: output[j] = bits[i : i + bytesize] i += bytesize j -= 1 return six.b("").join(output) else: def int_to_bin(number, width = 32): r""" Convert an integer into its binary representation in a bytes object. Width is the amount of bits to generate. If width is larger than the actual amount of bits required to represent number in binary, sign-extension is used. If it's smaller, the representation is trimmed to width bits. Each "bit" is either '\x00' or '\x01'. The MSBit is first. Examples: >>> int_to_bin(19, 5) '\x01\x00\x00\x01\x01' >>> int_to_bin(19, 8) '\x00\x00\x00\x01\x00\x00\x01\x01' """ if number < 0: number += 1 << width i = width - 1 bits = ["\x00"] * width while number and i >= 0: bits[i] = "\x00\x01"[number & 1] number >>= 1 i -= 1 return "".join(bits) # heavily optimized for performance def bin_to_int(bits, signed = False): r""" Logical opposite of int_to_bin. Both '0' and '\x00' are considered zero, and both '1' and '\x01' are considered one. Set sign to True to interpret the number as a 2-s complement signed integer. """ bits = "".join("01"[ord(b) & 1] for b in bits) if signed and bits[0] == "1": bits = bits[1:] bias = 1 << len(bits) else: bias = 0 return int(bits, 2) - bias _char_to_bin = [0] * 256 _bin_to_char = {} for i in range(256): ch = int2byte(i) bin = int_to_bin(i, 8) # Populate with for both keys i and ch, to support Python 2 & 3 _char_to_bin[i] = bin _bin_to_char[bin] = ch def encode_bin(data): """ Create a binary representation of the given b'' object. Assume 8-bit ASCII. Example: >>> encode_bin('ab') b"\x00\x01\x01\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x01\x00" """ return "".join(_char_to_bin[ord(ch)] for ch in data) def decode_bin(data): if len(data) & 7: raise ValueError("Data length must be a multiple of 8") i = 0 j = 0 l = len(data) // 8 chars = [""] * l while j < l: chars[j] = _bin_to_char[data[i:i+8]] i += 8 j += 1 return "".join(chars) def swap_bytes(bits, bytesize=8): r""" Bits is a b'' object containing a binary representation. Assuming each bytesize bits constitute a bytes, perform a endianness byte swap. Example: >>> swap_bytes(b'00011011', 2) b'11100100' """ i = 0 l = len(bits) output = [""] * ((l // bytesize) + 1) j = len(output) - 1 while i < l: output[j] = bits[i : i + bytesize] i += bytesize j -= 1 return "".join(output) construct_legacy-2.5.3/construct_legacy/lib/bitstream.py0000666000000000000000000000414613202307137021710 0ustar 00000000000000import six from construct_legacy.lib.binary import encode_bin, decode_bin try: bytes except NameError: bytes = str class BitStreamReader(object): __slots__ = ["substream", "buffer", "total_size"] def __init__(self, substream): self.substream = substream self.total_size = 0 self.buffer = six.b("") def close(self): if self.total_size % 8 != 0: raise ValueError("total size of read data must be a multiple of 8", self.total_size) def tell(self): return self.substream.tell() def seek(self, pos, whence = 0): self.buffer = six.b("") self.total_size = 0 self.substream.seek(pos, whence) def read(self, count): if count < 0: raise ValueError("count cannot be negative") l = len(self.buffer) if count == 0: data = six.b("") elif count <= l: data = self.buffer[:count] self.buffer = self.buffer[count:] else: data = self.buffer count -= l count_bytes = count // 8 if count & 7: count_bytes += 1 buf = encode_bin(self.substream.read(count_bytes)) data += buf[:count] self.buffer = buf[count:] self.total_size += len(data) return data class BitStreamWriter(object): __slots__ = ["substream", "buffer", "pos"] def __init__(self, substream): self.substream = substream self.buffer = [] self.pos = 0 def close(self): self.flush() def flush(self): raw = decode_bin(six.b("").join(self.buffer)) self.substream.write(raw) self.buffer = [] self.pos = 0 def tell(self): return self.substream.tell() + self.pos // 8 def seek(self, pos, whence = 0): self.flush() self.substream.seek(pos, whence) def write(self, data): if not data: return if not isinstance(data, bytes): raise TypeError("data must be a string, not %r" % (type(data),)) self.buffer.append(data) construct_legacy-2.5.3/construct_legacy/lib/container.py0000666000000000000000000001675712756674033021732 0ustar 00000000000000""" Various containers. """ def recursion_lock(retval, lock_name = "__recursion_lock__"): def decorator(func): def wrapper(self, *args, **kw): if getattr(self, lock_name, False): return retval setattr(self, lock_name, True) try: return func(self, *args, **kw) finally: setattr(self, lock_name, False) wrapper.__name__ = func.__name__ return wrapper return decorator class Container(dict): """ A generic container of attributes. Containers are the common way to express parsed data. """ __slots__ = ["__keys_order__"] def __init__(self, **kw): object.__setattr__(self, "__keys_order__", []) for k, v in kw.items(): self[k] = v def __getattr__(self, name): try: return self[name] except KeyError: raise AttributeError(name) def __setitem__(self, key, val): if key not in self: self.__keys_order__.append(key) dict.__setitem__(self, key, val) def __delitem__(self, key): dict.__delitem__(self, key) self.__keys_order__.remove(key) __delattr__ = __delitem__ __setattr__ = __setitem__ def clear(self): dict.clear(self) del self.__keys_order__[:] def pop(self, key, *default): val = dict.pop(self, key, *default) self.__keys_order__.remove(key) return val def popitem(self): k, v = dict.popitem(self) self.__keys_order__.remove(k) return k, v def update(self, seq, **kw): if hasattr(seq, "keys"): for k in seq.keys(): self[k] = seq[k] else: for k, v in seq: self[k] = v dict.update(self, kw) def copy(self): inst = self.__class__() inst.update(self.iteritems()) return inst def _search(self, name, search_all): items = [] for key in self.keys(): try: if key == name: if search_all: items.append(self[key]) else: return self[key] if type(self[key]) == Container or type(self[key]) == ListContainer: ret = self[key]._search(name, search_all) if ret is not None: if search_all: items.extend(ret) else: return ret except: pass if search_all: return items else: return None def search(self, name): return self._search(name, False) def search_all(self, name): return self._search(name, True) __update__ = update __copy__ = copy def __iter__(self): return iter(self.__keys_order__) iterkeys = __iter__ def itervalues(self): return (self[k] for k in self.__keys_order__) def iteritems(self): return ((k, self[k]) for k in self.__keys_order__) def keys(self): return self.__keys_order__ def values(self): return list(self.itervalues()) def items(self): return list(self.iteritems()) def __repr__(self): return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self)) @recursion_lock("<...>") def __pretty_str__(self, nesting = 1, indentation = " "): attrs = [] ind = indentation * nesting for k, v in self.iteritems(): if not k.startswith("_"): text = [ind, k, " = "] if hasattr(v, "__pretty_str__"): text.append(v.__pretty_str__(nesting + 1, indentation)) else: text.append(repr(v)) attrs.append("".join(text)) if not attrs: return "%s()" % (self.__class__.__name__,) attrs.insert(0, self.__class__.__name__ + ":") return "\n".join(attrs) __str__ = __pretty_str__ class FlagsContainer(Container): """ A container providing pretty-printing for flags. Only set flags are displayed. """ @recursion_lock("<...>") def __pretty_str__(self, nesting = 1, indentation = " "): attrs = [] ind = indentation * nesting for k in self.keys(): v = self[k] if not k.startswith("_") and v: attrs.append(ind + k) if not attrs: return "%s()" % (self.__class__.__name__,) attrs.insert(0, self.__class__.__name__+ ":") return "\n".join(attrs) class ListContainer(list): """ A container for lists. """ __slots__ = ["__recursion_lock__"] def __str__(self): return self.__pretty_str__() @recursion_lock("[...]") def __pretty_str__(self, nesting = 1, indentation = " "): if not self: return "[]" ind = indentation * nesting lines = ["["] for elem in self: lines.append("\n") lines.append(ind) if hasattr(elem, "__pretty_str__"): lines.append(elem.__pretty_str__(nesting + 1, indentation)) else: lines.append(repr(elem)) lines.append("\n") lines.append(indentation * (nesting - 1)) lines.append("]") return "".join(lines) def _search(self, name, search_all): items = [] for item in self: try: ret = item._search(name, search_all) except: continue if ret is not None: if search_all: items.extend(ret) else: return ret if search_all: return items else: return None def search(self, name): return self._search(name, False) def search_all(self, name): return self._search(name, True) class LazyContainer(object): __slots__ = ["subcon", "stream", "pos", "context", "_value"] def __init__(self, subcon, stream, pos, context): self.subcon = subcon self.stream = stream self.pos = pos self.context = context self._value = NotImplemented def __eq__(self, other): try: return self._value == other._value except AttributeError: return False def __ne__(self, other): return not (self == other) def __str__(self): return self.__pretty_str__() def __pretty_str__(self, nesting = 1, indentation = " "): if self._value is NotImplemented: text = "" elif hasattr(self._value, "__pretty_str__"): text = self._value.__pretty_str__(nesting, indentation) else: text = str(self._value) return "%s: %s" % (self.__class__.__name__, text) def read(self): self.stream.seek(self.pos) return self.subcon._parse(self.stream, self.context) def dispose(self): self.subcon = None self.stream = None self.context = None self.pos = None def _get_value(self): if self._value is NotImplemented: self._value = self.read() return self._value value = property(_get_value) has_value = property(lambda self: self._value is not NotImplemented) if __name__ == "__main__": c = Container(x=5) c.y = 8 c.z = 9 c.w = 10 c.foo = 5 print (c) construct_legacy-2.5.3/construct_legacy/lib/expr.py0000666000000000000000000001160512756674033020711 0ustar 00000000000000import operator if not hasattr(operator, "div"): operator.div = operator.truediv opnames = { operator.add : "+", operator.sub : "-", operator.mul : "*", operator.div : "/", operator.floordiv : "//", operator.mod : "%", operator.pow : "**", operator.xor : "^", operator.lshift : "<<", operator.rshift : ">>", operator.and_ : "and", operator.or_ : "or", operator.not_ : "not", operator.neg : "-", operator.pos : "+", operator.contains : "in", operator.gt : ">", operator.ge : ">=", operator.lt : "<", operator.le : "<=", operator.eq : "==", operator.ne : "!=", } class ExprMixin(object): __slots__ = () def __add__(self, other): return BinExpr(operator.add, self, other) def __sub__(self, other): return BinExpr(operator.sub, self, other) def __mul__(self, other): return BinExpr(operator.mul, self, other) def __floordiv__(self, other): return BinExpr(operator.floordiv, self, other) def __truediv__(self, other): return BinExpr(operator.div, self, other) __div__ = __floordiv__ def __mod__(self, other): return BinExpr(operator.mod, self, other) def __pow__(self, other): return BinExpr(operator.pow, self, other) def __xor__(self, other): return BinExpr(operator.xor, self, other) def __rshift__(self, other): return BinExpr(operator.rshift, self, other) def __lshift__(self, other): return BinExpr(operator.rshift, self, other) def __and__(self, other): return BinExpr(operator.and_, self, other) def __or__(self, other): return BinExpr(operator.or_, self, other) def __radd__(self, other): return BinExpr(operator.add, other, self) def __rsub__(self, other): return BinExpr(operator.sub, other, self) def __rmul__(self, other): return BinExpr(operator.mul, other, self) def __rfloordiv__(self, other): return BinExpr(operator.floordiv, other, self) def __rtruediv__(self, other): return BinExpr(operator.div, other, self) __rdiv__ = __rfloordiv__ def __rmod__(self, other): return BinExpr(operator.mod, other, self) def __rpow__(self, other): return BinExpr(operator.pow, other, self) def __rxor__(self, other): return BinExpr(operator.xor, other, self) def __rrshift__(self, other): return BinExpr(operator.rshift, other, self) def __rlshift__(self, other): return BinExpr(operator.rshift, other, self) def __rand__(self, other): return BinExpr(operator.and_, other, self) def __ror__(self, other): return BinExpr(operator.or_, other, self) def __neg__(self): return UniExpr(operator.neg, self) def __pos__(self): return UniExpr(operator.pos, self) def __invert__(self): return UniExpr(operator.not_, self) __inv__ = __invert__ def __contains__(self, other): return BinExpr(operator.contains, self, other) def __gt__(self, other): return BinExpr(operator.gt, self, other) def __ge__(self, other): return BinExpr(operator.ge, self, other) def __lt__(self, other): return BinExpr(operator.lt, self, other) def __le__(self, other): return BinExpr(operator.le, self, other) def __eq__(self, other): return BinExpr(operator.eq, self, other) def __ne__(self, other): return BinExpr(operator.ne, self, other) class UniExpr(ExprMixin): __slots__ = ["op", "operand"] def __init__(self, op, operand): self.op = op self.operand = operand def __repr__(self): return "%s %r" % (opnames[self.op], self.operand) def __call__(self, context): operand = self.operand(context) if callable(self.operand) else self.operand return self.op(operand) class BinExpr(ExprMixin): __slots__ = ["op", "lhs", "rhs"] def __init__(self, op, lhs, rhs): self.op = op self.lhs = lhs self.rhs = rhs def __repr__(self): return "(%r %s %r)" % (self.lhs, opnames[self.op], self.rhs) def __call__(self, context): lhs = self.lhs(context) if callable(self.lhs) else self.lhs rhs = self.rhs(context) if callable(self.rhs) else self.rhs return self.op(lhs, rhs) class Path(ExprMixin): __slots__ = ["__name", "__parent"] def __init__(self, name, parent = None): self.__name = name self.__parent = parent def __repr__(self): if self.__parent is None: return self.__name return "%r.%s" % (self.__parent, self.__name) def __call__(self, context): if self.__parent is None: return context context2 = self.__parent(context) return context2[self.__name] def __getattr__(self, name): return Path(name, self) # where is `this` being referenced from? this = Path("this") construct_legacy-2.5.3/construct_legacy/lib/hex.py0000666000000000000000000000251513202307136020477 0ustar 00000000000000from construct_legacy.lib.py3compat import byte2int, int2byte, bytes2str # Map an integer in the inclusive range 0-255 to its string byte representation _printable = dict((i, ".") for i in range(256)) _printable.update((i, bytes2str(int2byte(i))) for i in range(32, 128)) def hexdump(data, linesize): """ data is a bytes object. The returned result is a string. """ prettylines = [] if len(data) < 65536: fmt = "%%04X %%-%ds %%s" else: fmt = "%%08X %%-%ds %%s" fmt = fmt % (3 * linesize - 1,) for i in range(0, len(data), linesize): line = data[i : i + linesize] hextext = " ".join('%02x' % byte2int(b) for b in line) rawtext = "".join(_printable[byte2int(b)] for b in line) prettylines.append(fmt % (i, str(hextext), str(rawtext))) return prettylines try: basecls = bytes except NameError: basecls = str class HexString(basecls): """ Represents bytes that will be hex-dumped to a string when its string representation is requested. """ def __init__(self, data, linesize = 16): self.linesize = linesize def __new__(cls, data, *args, **kwargs): return basecls.__new__(cls, data) def __str__(self): if not self: return "''" return "\n" + "\n".join(hexdump(self, self.linesize)) construct_legacy-2.5.3/construct_legacy/lib/py3compat.py0000666000000000000000000000262512756674033021654 0ustar 00000000000000#------------------------------------------------------------------------------- # py3compat.py # # Some Python2&3 compatibility code #------------------------------------------------------------------------------- import sys PY3 = sys.version_info[0] == 3 if PY3: import io StringIO = io.StringIO BytesIO = io.BytesIO def bchr(i): """ When iterating over b'...' in Python 2 you get single b'_' chars and in Python 3 you get integers. Call bchr to always turn this to single b'_' chars. """ return bytes((i,)) def u(s): return s def int2byte(i): return bytes((i,)) def byte2int(b): return b def str2bytes(s): return s.encode("latin-1") def str2unicode(s): return s def bytes2str(b): return b.decode('latin-1') def decodebytes(b, encoding): return bytes(b, encoding) advance_iterator = next else: import cStringIO StringIO = BytesIO = cStringIO.StringIO int2byte = chr byte2int = ord bchr = lambda i: i def u(s): return unicode(s, "unicode_escape") def str2bytes(s): return s def str2unicode(s): return unicode(s, "unicode_escape") def bytes2str(b): return b def decodebytes(b, encoding): return b.decode(encoding) def advance_iterator(it): return it.next() construct_legacy-2.5.3/construct_legacy/lib/__init__.py0000666000000000000000000000054313202307137021452 0ustar 00000000000000from construct_legacy.lib.binary import int_to_bin, bin_to_int, swap_bytes, encode_bin, decode_bin from construct_legacy.lib.bitstream import BitStreamReader, BitStreamWriter from construct_legacy.lib.container import (Container, FlagsContainer, ListContainer, LazyContainer) from construct_legacy.lib.hex import HexString, hexdump construct_legacy-2.5.3/construct_legacy/macros.py0000666000000000000000000005514613202307136020441 0ustar 00000000000000import six from construct_legacy.lib.py3compat import int2byte from construct_legacy.lib import (BitStreamReader, BitStreamWriter, encode_bin, decode_bin) from construct_legacy.core import (Struct, MetaField, StaticField, FormatField, OnDemand, Pointer, Switch, Value, RepeatUntil, MetaArray, Sequence, Range, Select, Pass, SizeofError, Buffered, Restream, Reconfig) from construct_legacy.adapters import (BitIntegerAdapter, PaddingAdapter, ConstAdapter, CStringAdapter, LengthValueAdapter, IndexingAdapter, PaddedStringAdapter, FlagsAdapter, StringAdapter, MappingAdapter) try: from sys import maxsize except ImportError: from sys import maxint as maxsize #=============================================================================== # fields #=============================================================================== def Field(name, length): """ A field consisting of a specified number of bytes. :param name: the name of the field :param length: the length of the field. the length can be either an integer (StaticField), or a function that takes the context as an argument and returns the length (MetaField) """ if callable(length): return MetaField(name, length) else: return StaticField(name, length) def BitField(name, length, swapped = False, signed = False, bytesize = 8): r""" BitFields, as the name suggests, are fields that operate on raw, unaligned bits, and therefore must be enclosed in a BitStruct. Using them is very similar to all normal fields: they take a name and a length (in bits). :param name: name of the field :param length: number of bits in the field, or a function that takes the context as its argument and returns the length :param swapped: whether the value is byte-swapped :param signed: whether the value is signed :param bytesize: number of bits per byte, for byte-swapping Example:: >>> foo = BitStruct("foo", ... BitField("a", 3), ... Flag("b"), ... Padding(3), ... Nibble("c"), ... BitField("d", 5), ... ) >>> foo.parse("\xe1\x1f") Container(a = 7, b = False, c = 8, d = 31) >>> foo = BitStruct("foo", ... BitField("a", 3), ... Flag("b"), ... Padding(3), ... Nibble("c"), ... Struct("bar", ... Nibble("d"), ... Bit("e"), ... ) ... ) >>> foo.parse("\xe1\x1f") Container(a = 7, b = False, bar = Container(d = 15, e = 1), c = 8) """ return BitIntegerAdapter(Field(name, length), length, swapped=swapped, signed=signed, bytesize=bytesize ) def Padding(length, pattern = six.b("\x00"), strict = False): r"""A padding field (value is discarded) :param length: the length of the field. the length can be either an integer, or a function that takes the context as an argument and returns the length :param pattern: the padding pattern (character) to use. default is "\x00" :param strict: whether or not to raise an exception is the actual padding pattern mismatches the desired pattern. default is False. """ return PaddingAdapter(Field(None, length), pattern = pattern, strict = strict, ) def Flag(name, truth = 1, falsehood = 0, default = False): """ A flag. Flags are usually used to signify a Boolean value, and this construct maps values onto the ``bool`` type. .. note:: This construct works with both bit and byte contexts. .. warning:: Flags default to False, not True. This is different from the C and Python way of thinking about truth, and may be subject to change in the future. :param name: field name :param truth: value of truth (default 1) :param falsehood: value of falsehood (default 0) :param default: default value (default False) """ return SymmetricMapping(Field(name, 1), {True : int2byte(truth), False : int2byte(falsehood)}, default = default, ) #=============================================================================== # field shortcuts #=============================================================================== def Bit(name): """A 1-bit BitField; must be enclosed in a BitStruct""" return BitField(name, 1) def Nibble(name): """A 4-bit BitField; must be enclosed in a BitStruct""" return BitField(name, 4) def Octet(name): """An 8-bit BitField; must be enclosed in a BitStruct""" return BitField(name, 8) def UBInt8(name): """Unsigned, big endian 8-bit integer""" return FormatField(name, ">", "B") def UBInt16(name): """Unsigned, big endian 16-bit integer""" return FormatField(name, ">", "H") def UBInt32(name): """Unsigned, big endian 32-bit integer""" return FormatField(name, ">", "L") def UBInt64(name): """Unsigned, big endian 64-bit integer""" return FormatField(name, ">", "Q") def SBInt8(name): """Signed, big endian 8-bit integer""" return FormatField(name, ">", "b") def SBInt16(name): """Signed, big endian 16-bit integer""" return FormatField(name, ">", "h") def SBInt32(name): """Signed, big endian 32-bit integer""" return FormatField(name, ">", "l") def SBInt64(name): """Signed, big endian 64-bit integer""" return FormatField(name, ">", "q") def ULInt8(name): """Unsigned, little endian 8-bit integer""" return FormatField(name, "<", "B") def ULInt16(name): """Unsigned, little endian 16-bit integer""" return FormatField(name, "<", "H") def ULInt32(name): """Unsigned, little endian 32-bit integer""" return FormatField(name, "<", "L") def ULInt64(name): """Unsigned, little endian 64-bit integer""" return FormatField(name, "<", "Q") def SLInt8(name): """Signed, little endian 8-bit integer""" return FormatField(name, "<", "b") def SLInt16(name): """Signed, little endian 16-bit integer""" return FormatField(name, "<", "h") def SLInt32(name): """Signed, little endian 32-bit integer""" return FormatField(name, "<", "l") def SLInt64(name): """Signed, little endian 64-bit integer""" return FormatField(name, "<", "q") def UNInt8(name): """Unsigned, native endianity 8-bit integer""" return FormatField(name, "=", "B") def UNInt16(name): """Unsigned, native endianity 16-bit integer""" return FormatField(name, "=", "H") def UNInt32(name): """Unsigned, native endianity 32-bit integer""" return FormatField(name, "=", "L") def UNInt64(name): """Unsigned, native endianity 64-bit integer""" return FormatField(name, "=", "Q") def SNInt8(name): """Signed, native endianity 8-bit integer""" return FormatField(name, "=", "b") def SNInt16(name): """Signed, native endianity 16-bit integer""" return FormatField(name, "=", "h") def SNInt32(name): """Signed, native endianity 32-bit integer""" return FormatField(name, "=", "l") def SNInt64(name): """Signed, native endianity 64-bit integer""" return FormatField(name, "=", "q") def BFloat32(name): """Big endian, 32-bit IEEE floating point number""" return FormatField(name, ">", "f") def LFloat32(name): """Little endian, 32-bit IEEE floating point number""" return FormatField(name, "<", "f") def NFloat32(name): """Native endianity, 32-bit IEEE floating point number""" return FormatField(name, "=", "f") def BFloat64(name): """Big endian, 64-bit IEEE floating point number""" return FormatField(name, ">", "d") def LFloat64(name): """Little endian, 64-bit IEEE floating point number""" return FormatField(name, "<", "d") def NFloat64(name): """Native endianity, 64-bit IEEE floating point number""" return FormatField(name, "=", "d") #=============================================================================== # arrays #=============================================================================== def Array(count, subcon): r""" Repeats the given unit a fixed number of times. :param count: number of times to repeat :param subcon: construct to repeat Example:: >>> c = Array(4, UBInt8("foo")) >>> c.parse("\x01\x02\x03\x04") [1, 2, 3, 4] >>> c.parse("\x01\x02\x03\x04\x05\x06") [1, 2, 3, 4] >>> c.build([5,6,7,8]) '\x05\x06\x07\x08' >>> c.build([5,6,7,8,9]) Traceback (most recent call last): ... construct.core.RangeError: expected 4..4, found 5 """ if callable(count): con = MetaArray(count, subcon) else: con = MetaArray(lambda ctx: count, subcon) con._clear_flag(con.FLAG_DYNAMIC) return con def PrefixedArray(subcon, length_field = UBInt8("length")): """An array prefixed by a length field. :param subcon: the subcon to be repeated :param length_field: a construct returning an integer """ def _length(ctx): if issubclass(ctx.__class__, (list, tuple)): return len(ctx) return ctx[length_field.name] return LengthValueAdapter( Sequence(subcon.name, length_field, Array(_length, subcon), nested = False ) ) def OpenRange(mincount, subcon): return Range(mincount, maxsize, subcon) def GreedyRange(subcon): r""" Repeats the given unit one or more times. :param subcon: construct to repeat Example:: >>> from construct_legacy import GreedyRange, UBInt8 >>> c = GreedyRange(UBInt8("foo")) >>> c.parse("\x01") [1] >>> c.parse("\x01\x02\x03") [1, 2, 3] >>> c.parse("\x01\x02\x03\x04\x05\x06") [1, 2, 3, 4, 5, 6] >>> c.parse("") Traceback (most recent call last): ... construct.core.RangeError: expected 1..2147483647, found 0 >>> c.build([1,2]) '\x01\x02' >>> c.build([]) Traceback (most recent call last): ... construct.core.RangeError: expected 1..2147483647, found 0 """ return OpenRange(1, subcon) def OptionalGreedyRange(subcon): r""" Repeats the given unit zero or more times. This repeater can't fail, as it accepts lists of any length. :param subcon: construct to repeat Example:: >>> from construct_legacy import OptionalGreedyRange, UBInt8 >>> c = OptionalGreedyRange(UBInt8("foo")) >>> c.parse("") [] >>> c.parse("\x01\x02") [1, 2] >>> c.build([]) '' >>> c.build([1,2]) '\x01\x02' """ return OpenRange(0, subcon) #=============================================================================== # subconstructs #=============================================================================== def Optional(subcon): """An optional construct. if parsing fails, returns None. :param subcon: the subcon to optionally parse or build """ return Select(subcon.name, subcon, Pass) def Bitwise(subcon): """Converts the stream to bits, and passes the bitstream to subcon :param subcon: a bitwise construct (usually BitField) """ # subcons larger than MAX_BUFFER will be wrapped by Restream instead # of Buffered. implementation details, don't stick your nose in :) MAX_BUFFER = 1024 * 8 def resizer(length): if length & 7: raise SizeofError("size must be a multiple of 8", length) return length >> 3 if not subcon._is_flag(subcon.FLAG_DYNAMIC) and subcon.sizeof() < MAX_BUFFER: con = Buffered(subcon, encoder = decode_bin, decoder = encode_bin, resizer = resizer ) else: con = Restream(subcon, stream_reader = BitStreamReader, stream_writer = BitStreamWriter, resizer = resizer) return con def Aligned(subcon, modulus = 4, pattern = six.b("\x00")): r"""Aligns subcon to modulus boundary using padding pattern :param subcon: the subcon to align :param modulus: the modulus boundary (default is 4) :param pattern: the padding pattern (default is \x00) """ if modulus < 2: raise ValueError("modulus must be >= 2", modulus) def padlength(ctx): return (modulus - (subcon._sizeof(ctx) % modulus)) % modulus return SeqOfOne(subcon.name, subcon, # ?????? # ?????? # ?????? # ?????? Padding(padlength, pattern = pattern), nested = False, ) def SeqOfOne(name, *args, **kw): r"""A sequence of one element. only the first element is meaningful, the rest are discarded :param name: the name of the sequence :param \*args: subconstructs :param \*\*kw: any keyword arguments to Sequence """ return IndexingAdapter(Sequence(name, *args, **kw), index = 0) def Embedded(subcon): """Embeds a struct into the enclosing struct. :param subcon: the struct to embed """ return Reconfig(subcon.name, subcon, subcon.FLAG_EMBED) def Rename(newname, subcon): """Renames an existing construct :param newname: the new name :param subcon: the subcon to rename """ return Reconfig(newname, subcon) def Alias(newname, oldname): """Creates an alias for an existing element in a struct :param newname: the new name :param oldname: the name of an existing element """ return Value(newname, lambda ctx: ctx[oldname]) #=============================================================================== # mapping #=============================================================================== def SymmetricMapping(subcon, mapping, default = NotImplemented): """Defines a symmetrical mapping: a->b, b->a. :param subcon: the subcon to map :param mapping: the encoding mapping (a dict); the decoding mapping is achieved by reversing this mapping :param default: the default value to use when no mapping is found. if no default value is given, and exception is raised. setting to Pass would return the value "as is" (unmapped) """ reversed_mapping = dict((v, k) for k, v in mapping.items()) return MappingAdapter(subcon, encoding = mapping, decoding = reversed_mapping, encdefault = default, decdefault = default, ) def Enum(subcon, **kw): r"""A set of named values mapping. :param subcon: the subcon to map :param \*\*kw: keyword arguments which serve as the encoding mapping :param _default_: an optional, keyword-only argument that specifies the default value to use when the mapping is undefined. if not given, and exception is raised when the mapping is undefined. use `Pass` to pass the unmapped value as-is """ return SymmetricMapping(subcon, kw, kw.pop("_default_", NotImplemented)) def FlagsEnum(subcon, **kw): r"""A set of flag values mapping. :param subcon: the subcon to map :param \*\*kw: keyword arguments which serve as the encoding mapping """ return FlagsAdapter(subcon, kw) #=============================================================================== # structs #=============================================================================== def AlignedStruct(name, *subcons, **kw): r"""A struct of aligned fields :param name: the name of the struct :param \*subcons: the subcons that make up this structure :param \*\*kw: keyword arguments to pass to Aligned: 'modulus' and 'pattern' """ return Struct(name, *(Aligned(sc, **kw) for sc in subcons)) def BitStruct(name, *subcons): r"""A struct of bitwise fields :param name: the name of the struct :param \*subcons: the subcons that make up this structure """ return Bitwise(Struct(name, *subcons)) def EmbeddedBitStruct(*subcons): r"""An embedded BitStruct. no name is necessary. :param \*subcons: the subcons that make up this structure """ return Bitwise(Embedded(Struct(None, *subcons))) #=============================================================================== # strings #=============================================================================== def String(name, length, encoding=None, padchar=None, paddir="right", trimdir="right"): r""" A configurable, fixed-length string field. The padding character must be specified for padding and trimming to work. :param name: name :param length: length, in bytes :param encoding: encoding (e.g. "utf8") or None for no encoding :param padchar: optional character to pad out strings :param paddir: direction to pad out strings; one of "right", "left", or "both" :param str trim: direction to trim strings; one of "right", "left" Example:: >>> from construct_legacy import String >>> String("foo", 5).parse("hello") 'hello' >>> >>> String("foo", 12, encoding = "utf8").parse("hello joh\xd4\x83n") u'hello joh\u0503n' >>> >>> foo = String("foo", 10, padchar = "X", paddir = "right") >>> foo.parse("helloXXXXX") 'hello' >>> foo.build("hello") 'helloXXXXX' """ con = StringAdapter(Field(name, length), encoding=encoding) if padchar is not None: con = PaddedStringAdapter(con, padchar=padchar, paddir=paddir, trimdir=trimdir) return con def PascalString(name, length_field=UBInt8("length"), encoding=None): r""" A length-prefixed string. ``PascalString`` is named after the string types of Pascal, which are length-prefixed. Lisp strings also follow this convention. The length field will appear in the same ``Container`` as the ``PascalString``, with the given name. :param name: name :param length_field: a field which will store the length of the string :param encoding: encoding (e.g. "utf8") or None for no encoding Example:: >>> foo = PascalString("foo") >>> foo.parse("\x05hello") 'hello' >>> foo.build("hello world") '\x0bhello world' >>> >>> foo = PascalString("foo", length_field = UBInt16("length")) >>> foo.parse("\x00\x05hello") 'hello' >>> foo.build("hello") '\x00\x05hello' """ return StringAdapter( LengthValueAdapter( Sequence(name, length_field, Field("data", lambda ctx: ctx[length_field.name]), ) ), encoding=encoding, ) def CString(name, terminators=six.b("\x00"), encoding=None, char_field=Field(None, 1)): r""" A string ending in a terminator. ``CString`` is similar to the strings of C, C++, and other related programming languages. By default, the terminator is the NULL byte (b``0x00``). :param name: name :param terminators: sequence of valid terminators, in order of preference :param encoding: encoding (e.g. "utf8") or None for no encoding :param char_field: construct representing a single character Example:: >>> foo = CString("foo") >>> foo.parse(b"hello\x00") b'hello' >>> foo.build(b"hello") b'hello\x00' >>> foo = CString("foo", terminators = b"XYZ") >>> foo.parse(b"helloX") b'hello' >>> foo.parse(b"helloY") b'hello' >>> foo.parse(b"helloZ") b'hello' >>> foo.build(b"hello") b'helloX' """ return Rename(name, CStringAdapter( RepeatUntil(lambda obj, ctx: obj in terminators, char_field), terminators=terminators, encoding=encoding, ) ) def GreedyString(name, encoding=None, char_field=Field(None, 1)): r""" A configurable, variable-length string field. :param name: name :param encoding: encoding (e.g. "utf8") or None for no encoding :param char_field: construct representing a single character Example:: >>> foo = GreedyString("foo") >>> foo.parse(b"hello\x00") b'hello\x00' >>> foo.build(b"hello\x00") b'hello\x00' >>> foo.parse(b"hello") b'hello' >>> foo.build(b"hello") b'hello' """ return Rename(name, StringAdapter( OptionalGreedyRange(char_field), encoding=encoding, ) ) #=============================================================================== # conditional #=============================================================================== def IfThenElse(name, predicate, then_subcon, else_subcon): """An if-then-else conditional construct: if the predicate indicates True, `then_subcon` will be used; otherwise `else_subcon` :param name: the name of the construct :param predicate: a function taking the context as an argument and returning True or False :param then_subcon: the subcon that will be used if the predicate returns True :param else_subcon: the subcon that will be used if the predicate returns False """ return Switch(name, lambda ctx: bool(predicate(ctx)), { True : then_subcon, False : else_subcon, } ) def If(predicate, subcon, elsevalue = None): """An if-then conditional construct: if the predicate indicates True, subcon will be used; otherwise, `elsevalue` will be returned instead. :param predicate: a function taking the context as an argument and returning True or False :param subcon: the subcon that will be used if the predicate returns True :param elsevalue: the value that will be used should the predicate return False. by default this value is None. """ return IfThenElse(subcon.name, predicate, subcon, Value("elsevalue", lambda ctx: elsevalue) ) #=============================================================================== # misc #=============================================================================== def OnDemandPointer(offsetfunc, subcon, force_build = True): """An on-demand pointer. :param offsetfunc: a function taking the context as an argument and returning the absolute stream position :param subcon: the subcon that will be parsed from the `offsetfunc()` stream position on demand :param force_build: see OnDemand. by default True. """ return OnDemand(Pointer(offsetfunc, subcon), advance_stream = False, force_build = force_build ) def Magic(data): """A 'magic number' construct. it is used for file signatures, etc., to validate that the given pattern exists. Example:: elf_header = Struct("elf_header", Magic("\x7fELF"), # ... ) """ return ConstAdapter(Field(None, len(data)), data) construct_legacy-2.5.3/construct_legacy/protocols/0000777000000000000000000000000013202307640020614 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/protocols/application/0000777000000000000000000000000013202307640023117 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/protocols/application/dns.py0000666000000000000000000000747413202307136024271 0ustar 00000000000000""" Domain Name System (TCP/IP protocol stack) """ from construct_legacy import * from construct_legacy.protocols.layer3.ipv4 import IpAddressAdapter from binascii import unhexlify import six class DnsStringAdapter(Adapter): def _encode(self, obj, context): parts = obj.split(".") parts.append("") return parts def _decode(self, obj, context): return ".".join(obj[:-1]) dns_record_class = Enum(UBInt16("class"), RESERVED = 0, INTERNET = 1, CHAOS = 3, HESIOD = 4, NONE = 254, ANY = 255, ) dns_record_type = Enum(UBInt16("type"), IPv4 = 1, AUTHORITIVE_NAME_SERVER = 2, CANONICAL_NAME = 5, NULL = 10, MAIL_EXCHANGE = 15, TEXT = 16, X25 = 19, ISDN = 20, IPv6 = 28, UNSPECIFIED = 103, ALL = 255, ) query_record = Struct("query_record", DnsStringAdapter( RepeatUntil(lambda obj, ctx: obj == "", PascalString("name") ) ), dns_record_type, dns_record_class, ) rdata = Field("rdata", lambda ctx: ctx.rdata_length) resource_record = Struct("resource_record", CString("name", terminators = six.b("\xc0\x00")), Padding(1), dns_record_type, dns_record_class, UBInt32("ttl"), UBInt16("rdata_length"), IfThenElse("data", lambda ctx: ctx.type == "IPv4", IpAddressAdapter(rdata), rdata ) ) dns = Struct("dns", UBInt16("id"), BitStruct("flags", Enum(Bit("type"), QUERY = 0, RESPONSE = 1, ), Enum(Nibble("opcode"), STANDARD_QUERY = 0, INVERSE_QUERY = 1, SERVER_STATUS_REQUEST = 2, NOTIFY = 4, UPDATE = 5, ), Flag("authoritive_answer"), Flag("truncation"), Flag("recurssion_desired"), Flag("recursion_available"), Padding(1), Flag("authenticated_data"), Flag("checking_disabled"), Enum(Nibble("response_code"), SUCCESS = 0, FORMAT_ERROR = 1, SERVER_FAILURE = 2, NAME_DOES_NOT_EXIST = 3, NOT_IMPLEMENTED = 4, REFUSED = 5, NAME_SHOULD_NOT_EXIST = 6, RR_SHOULD_NOT_EXIST = 7, RR_SHOULD_EXIST = 8, NOT_AUTHORITIVE = 9, NOT_ZONE = 10, ), ), UBInt16("question_count"), UBInt16("answer_count"), UBInt16("authority_count"), UBInt16("additional_count"), Array(lambda ctx: ctx.question_count, Rename("questions", query_record), ), Rename("answers", Array(lambda ctx: ctx.answer_count, resource_record) ), Rename("authorities", Array(lambda ctx: ctx.authority_count, resource_record) ), Array(lambda ctx: ctx.additional_count, Rename("additionals", resource_record), ), ) if __name__ == "__main__": cap1 = unhexlify(six.b("2624010000010000000000000377777706676f6f676c6503636f6d0000010001")) cap2 = unhexlify(six.b( "2624818000010005000600060377777706676f6f676c6503636f6d0000010001c00c00" "05000100089065000803777777016cc010c02c0001000100000004000440e9b768c02c" "0001000100000004000440e9b793c02c0001000100000004000440e9b763c02c000100" "0100000004000440e9b767c030000200010000a88600040163c030c030000200010000" "a88600040164c030c030000200010000a88600040165c030c030000200010000a88600" "040167c030c030000200010000a88600040161c030c030000200010000a88600040162" "c030c0c00001000100011d0c0004d8ef3509c0d0000100010000ca7c000440e9b309c0" "80000100010000c4c5000440e9a109c0900001000100004391000440e9b709c0a00001" "00010000ca7c000442660b09c0b00001000100000266000440e9a709" )) obj = dns.parse(cap1) print (obj) print (repr(dns.build(obj))) print ("-" * 80) obj = dns.parse(cap2) print (obj) print (repr(dns.build(obj))) construct_legacy-2.5.3/construct_legacy/protocols/application/__init__.py0000666000000000000000000000005712756674033025252 0ustar 00000000000000""" application layer (various) protocols """ construct_legacy-2.5.3/construct_legacy/protocols/ipstack.py0000666000000000000000000001415113202307136022626 0ustar 00000000000000""" TCP/IP Protocol Stack Note: before parsing the application layer over a TCP stream, you must first combine all the TCP frames into a stream. See utils.tcpip for some solutions """ from construct_legacy import Struct, Rename, HexDumpAdapter, Field, Switch, Pass from construct_legacy.protocols.layer2.ethernet import ethernet_header from construct_legacy.protocols.layer3.ipv4 import ipv4_header from construct_legacy.protocols.layer3.ipv6 import ipv6_header from construct_legacy.protocols.layer4.tcp import tcp_header from construct_legacy.protocols.layer4.udp import udp_header from binascii import unhexlify import six layer4_tcp = Struct("layer4_tcp", Rename("header", tcp_header), HexDumpAdapter( Field("next", lambda ctx: ctx["_"]["header"].payload_length - ctx["header"].header_length ) ), ) layer4_udp = Struct("layer4_udp", Rename("header", udp_header), HexDumpAdapter( Field("next", lambda ctx: ctx["header"].payload_length) ), ) layer3_payload = Switch("next", lambda ctx: ctx["header"].protocol, { "TCP" : layer4_tcp, "UDP" : layer4_udp, }, default = Pass ) layer3_ipv4 = Struct("layer3_ipv4", Rename("header", ipv4_header), layer3_payload, ) layer3_ipv6 = Struct("layer3_ipv6", Rename("header", ipv6_header), layer3_payload, ) layer2_ethernet = Struct("layer2_ethernet", Rename("header", ethernet_header), Switch("next", lambda ctx: ctx["header"].type, { "IPv4" : layer3_ipv4, "IPv6" : layer3_ipv6, }, default = Pass, ) ) ip_stack = Rename("ip_stack", layer2_ethernet) if __name__ == "__main__": cap1 = unhexlify(six.b( "0011508c283c001150886b570800450001e971474000800684e4c0a80202525eedda11" "2a0050d98ec61d54fe977d501844705dcc0000474554202f20485454502f312e310d0a" "486f73743a207777772e707974686f6e2e6f72670d0a557365722d4167656e743a204d" "6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420" "352e313b20656e2d55533b2072763a312e382e302e3129204765636b6f2f3230303630" "3131312046697265666f782f312e352e302e310d0a4163636570743a20746578742f78" "6d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d" "6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d" "302e382c696d6167652f706e672c2a2f2a3b713d302e350d0a4163636570742d4c616e" "67756167653a20656e2d75732c656e3b713d302e350d0a4163636570742d456e636f64" "696e673a20677a69702c6465666c6174650d0a4163636570742d436861727365743a20" "49534f2d383835392d312c7574662d383b713d302e372c2a3b713d302e370d0a4b6565" "702d416c6976653a203330300d0a436f6e6e656374696f6e3a206b6565702d616c6976" "650d0a507261676d613a206e6f2d63616368650d0a43616368652d436f6e74726f6c3a" "206e6f2d63616368650d0a0d0a" )) cap2 = unhexlify(six.b( "0002e3426009001150f2c280080045900598fd22000036063291d149baeec0a8023c00" "500cc33b8aa7dcc4e588065010ffffcecd0000485454502f312e3120323030204f4b0d" "0a446174653a204672692c2031352044656320323030362032313a32363a323520474d" "540d0a5033503a20706f6c6963797265663d22687474703a2f2f7033702e7961686f6f" "2e636f6d2f7733632f7033702e786d6c222c2043503d2243414f2044535020434f5220" "4355522041444d20444556205441492050534120505344204956416920495644692043" "4f4e692054454c6f204f545069204f55522044454c692053414d69204f54526920554e" "5269205055426920494e4420504859204f4e4c20554e49205055522046494e20434f4d" "204e415620494e542044454d20434e542053544120504f4c204845412050524520474f" "56220d0a43616368652d436f6e74726f6c3a20707269766174650d0a566172793a2055" "7365722d4167656e740d0a5365742d436f6f6b69653a20443d5f796c683d58336f444d" "54466b64476c6f5a7a567842463954417a49334d5459784e446b4563476c6b417a4578" "4e6a59794d5463314e5463456447567a64414d7742485274634777446157356b5a5867" "7462412d2d3b20706174683d2f3b20646f6d61696e3d2e7961686f6f2e636f6d0d0a43" "6f6e6e656374696f6e3a20636c6f73650d0a5472616e736665722d456e636f64696e67" "3a206368756e6b65640d0a436f6e74656e742d547970653a20746578742f68746d6c3b" "20636861727365743d7574662d380d0a436f6e74656e742d456e636f64696e673a2067" "7a69700d0a0d0a366263382020200d0a1f8b0800000000000003dcbd6977db38b200fa" "f9fa9cf90f88326dd9b1169212b5d891739cd84ed2936d1277a7d3cbf1a1484a624c91" "0c4979893bbfec7d7bbfec556121012eb29d65e6be7be7762c9240a1502854150a85c2" "c37b87af9f9c7c7873449e9dbc7c41defcf2f8c5f327a4d1ee76dff79e74bb872787ec" "43bfa3e9ddeed1ab06692cd234daed762f2e2e3a17bd4e18cfbb276fbb8b74e9f7bb49" "1a7b76da7152a7b1bff110dfed3f5cb896030f4b37b508566dbb9f56def9a4f1240c52" "3748db275791db20367b9a3452f732a5d0f688bdb0e2c44d27bf9c1cb7470830b1632f" "4a490a3578c18fd6b9c5dec2f7732b2641783109dc0b7268a56e2bd527a931497b93b4" "3f49cd493a98a4c3493a9aa4e349aa6bf01f7cd78d89d6b2ed49b3d9baf223f8b307b5" "004a67eea627ded2dddadedb78d8656de428f856305f5973779223b0fff05ebbbde1db" "67082a499289ae0f06863e1c8f4c0639eaccbdd9a3547abf798a1f0ec6c73fafd2e4f1" "51ffd5f1c9e2f9e37ff74e74fbddd941b375eadb0942b3e3d5723a69f6060373a6cff4" "9e6df586dac8b11c4d1f1afd81319b0df45e6fd4925a6cee6db4dbfb19e225bc1b12e5" "6a098aed9309715c3b74dc5fde3e7f122ea3308061dac22f4018a4f8878367af5f4f2e" "bcc001a2d187bfffbefeb2477f75026be9269165bb93d92ab0532f0cb68264fbda9b6d" "dd0b92bfff867f3abe1bccd3c5f675eca6ab3820c1caf7f7be20e05363029f93c8f7d2" "ad46a7b1bd475ff62614f2de2c8cb7f08537d93a35fed0fe9a4c1af44363fb91beabed" "790f4f0d0e7a6f67c7dbbe3eedfd01e5bcbffe9a64bf289e00307bb1f7852371dadb13" "3df0c3798efba9d93a1db44e87dbd7d8b4cf50e95c780e304be745389fbbf11ef4cddf" "dcf4b162d629fa94d7defbe2fa892b3ece2c78d8fb221a84517003476a73dc3ad535d6" "e22c7fbd0db8cf3a511ca6211d3e28933fed9d8ea54f381f66c0c7f2cb0e4c3898ad2b" "3b0de3c9e918bf25abc88d6ddf02d65581418f94174addc9ebe94717e67ce557207b6d" "45f892773ae393adc62af57c18ecd27b46e5aa2feea5b58c7c173e6d94be1d3bd5afa3" "fcf571d409ded9b1eb06ef3d275d00c36f25f4916c6ed2a911cef88b0e4c0ecfa7a5b6" "27936600b3d28d9bdbe411" )) obj = ip_stack.parse(cap1) print (obj) print (repr(ip_stack.build(obj))) print ("-" * 80) obj = ip_stack.parse(cap2) print (obj) print (repr(ip_stack.build(obj))) construct_legacy-2.5.3/construct_legacy/protocols/layer2/0000777000000000000000000000000013202307640022012 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/protocols/layer2/arp.py0000666000000000000000000000417513202307135023154 0ustar 00000000000000""" Ethernet (TCP/IP protocol stack) """ from construct_legacy import * from ethernet import MacAddressAdapter from construct_legacy.protocols.layer3.ipv4 import IpAddressAdapter from binascii import unhexlify import six def HwAddress(name): return IfThenElse(name, lambda ctx: ctx.hardware_type == "ETHERNET", MacAddressAdapter(Field("data", lambda ctx: ctx.hwaddr_length)), Field("data", lambda ctx: ctx.hwaddr_length) ) def ProtoAddress(name): return IfThenElse(name, lambda ctx: ctx.protocol_type == "IP", IpAddressAdapter(Field("data", lambda ctx: ctx.protoaddr_length)), Field("data", lambda ctx: ctx.protoaddr_length) ) arp_header = Struct("arp_header", Enum(UBInt16("hardware_type"), ETHERNET = 1, EXPERIMENTAL_ETHERNET = 2, ProNET_TOKEN_RING = 4, CHAOS = 5, IEEE802 = 6, ARCNET = 7, HYPERCHANNEL = 8, ULTRALINK = 13, FRAME_RELAY = 15, FIBRE_CHANNEL = 18, IEEE1394 = 24, HIPARP = 28, ISO7816_3 = 29, ARPSEC = 30, IPSEC_TUNNEL = 31, INFINIBAND = 32, ), Enum(UBInt16("protocol_type"), IP = 0x0800, ), UBInt8("hwaddr_length"), UBInt8("protoaddr_length"), Enum(UBInt16("opcode"), REQUEST = 1, REPLY = 2, REQUEST_REVERSE = 3, REPLY_REVERSE = 4, DRARP_REQUEST = 5, DRARP_REPLY = 6, DRARP_ERROR = 7, InARP_REQUEST = 8, InARP_REPLY = 9, ARP_NAK = 10 ), HwAddress("source_hwaddr"), ProtoAddress("source_protoaddr"), HwAddress("dest_hwaddr"), ProtoAddress("dest_protoaddr"), ) rarp_header = Rename("rarp_header", arp_header) if __name__ == "__main__": cap1 = unhexlify(six.b("00010800060400010002e3426009c0a80204000000000000c0a80201")) obj = arp_header.parse(cap1) print (obj) print (repr(arp_header.build(obj))) print ("-" * 80) cap2 = unhexlify(six.b("00010800060400020011508c283cc0a802010002e3426009c0a80204")) obj = arp_header.parse(cap2) print (obj) print (repr(arp_header.build(obj))) construct_legacy-2.5.3/construct_legacy/protocols/layer2/ethernet.py0000666000000000000000000000156213202307135024205 0ustar 00000000000000""" Ethernet (TCP/IP protocol stack) """ from construct_legacy import * from binascii import hexlify, unhexlify import six class MacAddressAdapter(Adapter): def _encode(self, obj, context): return unhexlify(obj.replace("-", "")) def _decode(self, obj, context): return "-".join(hexlify(b) for b in obj) def MacAddress(name): return MacAddressAdapter(Bytes(name, 6)) ethernet_header = Struct("ethernet_header", MacAddress("destination"), MacAddress("source"), Enum(UBInt16("type"), IPv4 = 0x0800, ARP = 0x0806, RARP = 0x8035, X25 = 0x0805, IPX = 0x8137, IPv6 = 0x86DD, _default_ = Pass, ), ) if __name__ == "__main__": cap = unhexlify(six.b("0011508c283c0002e34260090800")) obj = ethernet_header.parse(cap) print (obj) print (repr(ethernet_header.build(obj))) construct_legacy-2.5.3/construct_legacy/protocols/layer2/mtp2.py0000666000000000000000000000054013202307135023244 0ustar 00000000000000""" Message Transport Part 2 (SS7 protocol stack) (untested) """ from construct_legacy import * mtp2_header = BitStruct("mtp2_header", Octet("flag1"), Bits("bsn", 7), Bit("bib"), Bits("fsn", 7), Bit("sib"), Octet("length"), Octet("service_info"), Octet("signalling_info"), Bits("crc", 16), Octet("flag2"), ) construct_legacy-2.5.3/construct_legacy/protocols/layer2/__init__.py0000666000000000000000000000004712756674033024144 0ustar 00000000000000""" layer 2 (data link) protocols """ construct_legacy-2.5.3/construct_legacy/protocols/layer3/0000777000000000000000000000000013202307640022013 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/protocols/layer3/dhcpv4.py0000666000000000000000000001452713202307135023565 0ustar 00000000000000""" Dynamic Host Configuration Protocol for IPv4 http://www.networksorcery.com/enp/protocol/dhcp.htm http://www.networksorcery.com/enp/protocol/bootp/options.htm """ from construct_legacy import * from ipv4 import IpAddress from binascii import unhexlify import six dhcp_option = Struct("dhcp_option", Enum(Byte("code"), Pad = 0, Subnet_Mask = 1, Time_Offset = 2, Router = 3, Time_Server = 4, Name_Server = 5, Domain_Name_Server = 6, Log_Server = 7, Quote_Server = 8, LPR_Server = 9, Impress_Server = 10, Resource_Location_Server = 11, Host_Name = 12, Boot_File_Size = 13, Merit_Dump_File = 14, Domain_Name = 15, Swap_Server = 16, Root_Path = 17, Extensions_Path = 18, IP_Forwarding_enabledisable = 19, Nonlocal_Source_Routing_enabledisable = 20, Policy_Filter = 21, Maximum_Datagram_Reassembly_Size = 22, Default_IP_TTL = 23, Path_MTU_Aging_Timeout = 24, Path_MTU_Plateau_Table = 25, Interface_MTU = 26, All_Subnets_are_Local = 27, Broadcast_Address = 28, Perform_Mask_Discovery = 29, Mask_supplier = 30, Perform_router_discovery = 31, Router_solicitation_address = 32, Static_routing_table = 33, Trailer_encapsulation = 34, ARP_cache_timeout = 35, Ethernet_encapsulation = 36, Default_TCP_TTL = 37, TCP_keepalive_interval = 38, TCP_keepalive_garbage = 39, Network_Information_Service_domain = 40, Network_Information_Servers = 41, NTP_servers = 42, Vendor_specific_information = 43, NetBIOS_over_TCPIP_name_server = 44, NetBIOS_over_TCPIP_Datagram_Distribution_Server = 45, NetBIOS_over_TCPIP_Node_Type = 46, NetBIOS_over_TCPIP_Scope = 47, X_Window_System_Font_Server = 48, X_Window_System_Display_Manager = 49, Requested_IP_Address = 50, IP_address_lease_time = 51, Option_overload = 52, DHCP_message_type = 53, Server_identifier = 54, Parameter_request_list = 55, Message = 56, Maximum_DHCP_message_size = 57, Renew_time_value = 58, Rebinding_time_value = 59, Class_identifier = 60, Client_identifier = 61, NetWareIP_Domain_Name = 62, NetWareIP_information = 63, Network_Information_Service_Domain = 64, Network_Information_Service_Servers = 65, TFTP_server_name = 66, Bootfile_name = 67, Mobile_IP_Home_Agent = 68, Simple_Mail_Transport_Protocol_Server = 69, Post_Office_Protocol_Server = 70, Network_News_Transport_Protocol_Server = 71, Default_World_Wide_Web_Server = 72, Default_Finger_Server = 73, Default_Internet_Relay_Chat_Server = 74, StreetTalk_Server = 75, StreetTalk_Directory_Assistance_Server = 76, User_Class_Information = 77, SLP_Directory_Agent = 78, SLP_Service_Scope = 79, Rapid_Commit = 80, Fully_Qualified_Domain_Name = 81, Relay_Agent_Information = 82, Internet_Storage_Name_Service = 83, NDS_servers = 85, NDS_tree_name = 86, NDS_context = 87, BCMCS_Controller_Domain_Name_list = 88, BCMCS_Controller_IPv4_address_list = 89, Authentication = 90, Client_last_transaction_time = 91, Associated_ip = 92, Client_System_Architecture_Type = 93, Client_Network_Interface_Identifier = 94, Lightweight_Directory_Access_Protocol = 95, Client_Machine_Identifier = 97, Open_Group_User_Authentication = 98, Autonomous_System_Number = 109, NetInfo_Parent_Server_Address = 112, NetInfo_Parent_Server_Tag = 113, URL = 114, Auto_Configure = 116, Name_Service_Search = 117, Subnet_Selection = 118, DNS_domain_search_list = 119, SIP_Servers_DHCP_Option = 120, Classless_Static_Route_Option = 121, CableLabs_Client_Configuration = 122, GeoConf = 123, ), Switch("value", lambda ctx: ctx.code, { # codes without any value "Pad" : Pass, }, # codes followed by length and value fields default = Struct("value", Byte("length"), Field("data", lambda ctx: ctx.length), ) ) ) dhcp_header = Struct("dhcp_header", Enum(Byte("opcode"), BootRequest = 1, BootReply = 2, ), Enum(Byte("hardware_type"), Ethernet = 1, Experimental_Ethernet = 2, ProNET_Token_Ring = 4, Chaos = 5, IEEE_802 = 6, ARCNET = 7, Hyperchannel = 8, Lanstar = 9, ), Byte("hardware_address_length"), Byte("hop_count"), UBInt32("transaction_id"), UBInt16("elapsed_time"), BitStruct("flags", Flag("boardcast"), Padding(15), ), IpAddress("client_addr"), IpAddress("your_addr"), IpAddress("server_addr"), IpAddress("relay_addr"), Bytes("client_hardware_addr", 16), Bytes("server_host_name", 64), Bytes("boot_filename", 128), # BOOTP/DHCP options # "The first four bytes contain the (decimal) values 99, 130, 83 and 99" Const(Bytes("magic", 4), six.b("\x63\x82\x53\x63")), Rename("options", OptionalGreedyRange(dhcp_option)), ) if __name__ == "__main__": test = unhexlify(six.b( "0101060167c05f5a00000000" "0102030405060708090a0b0c" "0d0e0f10" "DEADBEEFBEEF" "000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000" "00000000000000000000000000" "63825363" "3501083d0701DEADBEEFBEEF0c04417375733c084d53465420352e" "30370d010f03062c2e2f1f2179f92bfc52210117566c616e333338" "382b45746865726e6574312f302f32340206f8f0827348f9ff" )) print (dhcp_header.parse(test)) construct_legacy-2.5.3/construct_legacy/protocols/layer3/dhcpv6.py0000666000000000000000000000545513202307135023567 0ustar 00000000000000""" the Dynamic Host Configuration Protocol (DHCP) for IPv6 http://www.networksorcery.com/enp/rfc/rfc3315.txt """ from construct_legacy import * from ipv6 import Ipv6Address import six dhcp_option = Struct("dhcp_option", Enum(UBInt16("code"), OPTION_CLIENTID = 1, OPTION_SERVERID = 2, OPTION_IA_NA = 3, OPTION_IA_TA = 4, OPTION_IAADDR = 5, OPTION_ORO = 6, OPTION_PREFERENCE = 7, OPTION_ELAPSED_TIME = 8, OPTION_RELAY_MSG = 9, OPTION_AUTH = 11, OPTION_UNICAST = 12, OPTION_STATUS_CODE = 13, OPTION_RAPID_COMMIT = 14, OPTION_USER_CLASS = 15, OPTION_VENDOR_CLASS = 16, OPTION_VENDOR_OPTS = 17, OPTION_INTERFACE_ID = 18, OPTION_RECONF_MSG = 19, OPTION_RECONF_ACCEPT = 20, SIP_SERVERS_DOMAIN_NAME_LIST = 21, SIP_SERVERS_IPV6_ADDRESS_LIST = 22, DNS_RECURSIVE_NAME_SERVER = 23, DOMAIN_SEARCH_LIST = 24, OPTION_IA_PD = 25, OPTION_IAPREFIX = 26, OPTION_NIS_SERVERS = 27, OPTION_NISP_SERVERS = 28, OPTION_NIS_DOMAIN_NAME = 29, OPTION_NISP_DOMAIN_NAME = 30, SNTP_SERVER_LIST = 31, INFORMATION_REFRESH_TIME = 32, BCMCS_CONTROLLER_DOMAIN_NAME_LIST = 33, BCMCS_CONTROLLER_IPV6_ADDRESS_LIST = 34, OPTION_GEOCONF_CIVIC = 36, OPTION_REMOTE_ID = 37, RELAY_AGENT_SUBSCRIBER_ID = 38, OPTION_CLIENT_FQDN = 39, ), UBInt16("length"), Field("data", lambda ctx: ctx.length), ) client_message = Struct("client_message", Bitwise(BitField("transaction_id", 24)), ) relay_message = Struct("relay_message", Byte("hop_count"), Ipv6Address("linkaddr"), Ipv6Address("peeraddr"), ) dhcp_message = Struct("dhcp_message", Enum(Byte("msgtype"), # these are client-server messages SOLICIT = 1, ADVERTISE = 2, REQUEST = 3, CONFIRM = 4, RENEW = 5, REBIND = 6, REPLY = 7, RELEASE_ = 8, DECLINE_ = 9, RECONFIGURE = 10, INFORMATION_REQUEST = 11, # these two are relay messages RELAY_FORW = 12, RELAY_REPL = 13, ), # relay messages have a different structure from client-server messages Switch("params", lambda ctx: ctx.msgtype, { "RELAY_FORW" : relay_message, "RELAY_REPL" : relay_message, }, default = client_message, ), Rename("options", GreedyRange(dhcp_option)), ) if __name__ == "__main__": test1 = six.b("\x03\x11\x22\x33\x00\x17\x00\x03ABC\x00\x05\x00\x05HELLO") test2 = six.b("\x0c\x040123456789abcdef0123456789abcdef\x00\x09\x00\x0bhello world\x00\x01\x00\x00") print (dhcp_message.parse(test1)) print (dhcp_message.parse(test2)) construct_legacy-2.5.3/construct_legacy/protocols/layer3/icmpv4.py0000666000000000000000000000500713202307134023567 0ustar 00000000000000""" Internet Control Message Protocol for IPv4 (TCP/IP protocol stack) """ from construct_legacy import * from ipv4 import IpAddress from binascii import unhexlify import six echo_payload = Struct("echo_payload", UBInt16("identifier"), UBInt16("sequence"), Bytes("data", 32), # length is implementation dependent... # is anyone using more than 32 bytes? ) dest_unreachable_payload = Struct("dest_unreachable_payload", Padding(2), UBInt16("next_hop_mtu"), IpAddress("host"), Bytes("echo", 8), ) dest_unreachable_code = Enum(Byte("code"), Network_unreachable_error = 0, Host_unreachable_error = 1, Protocol_unreachable_error = 2, Port_unreachable_error = 3, The_datagram_is_too_big = 4, Source_route_failed_error = 5, Destination_network_unknown_error = 6, Destination_host_unknown_error = 7, Source_host_isolated_error = 8, Desination_administratively_prohibited = 9, Host_administratively_prohibited2 = 10, Network_TOS_unreachable = 11, Host_TOS_unreachable = 12, ) icmp_header = Struct("icmp_header", Enum(Byte("type"), Echo_reply = 0, Destination_unreachable = 3, Source_quench = 4, Redirect = 5, Alternate_host_address = 6, Echo_request = 8, Router_advertisement = 9, Router_solicitation = 10, Time_exceeded = 11, Parameter_problem = 12, Timestamp_request = 13, Timestamp_reply = 14, Information_request = 15, Information_reply = 16, Address_mask_request = 17, Address_mask_reply = 18, _default_ = Pass, ), Switch("code", lambda ctx: ctx.type, { "Destination_unreachable" : dest_unreachable_code, }, default = Byte("code"), ), UBInt16("crc"), Switch("payload", lambda ctx: ctx.type, { "Echo_reply" : echo_payload, "Echo_request" : echo_payload, "Destination_unreachable" : dest_unreachable_payload, }, default = Pass ) ) if __name__ == "__main__": cap1 = unhexlify(six.b("0800305c02001b006162636465666768696a6b6c6d6e6f70717273747576776162" "63646566676869")) cap2 = unhexlify(six.b("0000385c02001b006162636465666768696a6b6c6d6e6f70717273747576776162" "63646566676869")) cap3 = unhexlify(six.b("0301000000001122aabbccdd0102030405060708")) print (icmp_header.parse(cap1)) print (icmp_header.parse(cap2)) print (icmp_header.parse(cap3)) construct_legacy-2.5.3/construct_legacy/protocols/layer3/igmpv2.py0000666000000000000000000000131613202307134023570 0ustar 00000000000000""" What : Internet Group Management Protocol, Version 2 How : http://www.ietf.org/rfc/rfc2236.txt Who : jesse @ housejunkie . ca """ from construct_legacy import Byte, Enum,Struct, UBInt16 from construct_legacy.protocols.layer3.ipv4 import IpAddress from binascii import unhexlify import six igmp_type = Enum(Byte("igmp_type"), MEMBERSHIP_QUERY = 0x11, MEMBERSHIP_REPORT_V1 = 0x12, MEMBERSHIP_REPORT_V2 = 0x16, LEAVE_GROUP = 0x17, ) igmpv2_header = Struct("igmpv2_header", igmp_type, Byte("max_resp_time"), UBInt16("checksum"), IpAddress("group_address"), ) if __name__ == '__main__': capture = unhexlify(six.b("1600FA01EFFFFFFD")) print (igmpv2_header.parse(capture)) construct_legacy-2.5.3/construct_legacy/protocols/layer3/ipv4.py0000666000000000000000000000365213202307134023253 0ustar 00000000000000""" Internet Protocol version 4 (TCP/IP protocol stack) """ from construct_legacy import * import six from binascii import unhexlify try: bytes except NameError: bytes = str class IpAddressAdapter(Adapter): def _encode(self, obj, context): if bytes is str: return "".join(chr(int(b)) for b in obj.split(".")) else: return bytes(int(b) for b in obj.split(".")) def _decode(self, obj, context): if bytes is str: return ".".join(str(ord(b)) for b in obj) else: return ".".join("%d" % (b,) for b in obj) def IpAddress(name): return IpAddressAdapter(Bytes(name, 4)) def ProtocolEnum(code): return Enum(code, ICMP = 1, TCP = 6, UDP = 17, ) ipv4_header = Struct("ip_header", EmbeddedBitStruct( Const(Nibble("version"), 4), ExprAdapter(Nibble("header_length"), decoder = lambda obj, ctx: obj * 4, encoder = lambda obj, ctx: obj / 4 ), ), BitStruct("tos", Bits("precedence", 3), Flag("minimize_delay"), Flag("high_throuput"), Flag("high_reliability"), Flag("minimize_cost"), Padding(1), ), UBInt16("total_length"), Value("payload_length", lambda ctx: ctx.total_length - ctx.header_length), UBInt16("identification"), EmbeddedBitStruct( Struct("flags", Padding(1), Flag("dont_fragment"), Flag("more_fragments"), ), Bits("frame_offset", 13), ), UBInt8("ttl"), ProtocolEnum(UBInt8("protocol")), UBInt16("checksum"), IpAddress("source"), IpAddress("destination"), Field("options", lambda ctx: ctx.header_length - 20), ) if __name__ == "__main__": cap = unhexlify(six.b("4500003ca0e3000080116185c0a80205d474a126")) obj = ipv4_header.parse(cap) print (obj) print (repr(ipv4_header.build(obj))) construct_legacy-2.5.3/construct_legacy/protocols/layer3/ipv6.py0000666000000000000000000000233613202307134023253 0ustar 00000000000000""" Internet Protocol version 6 (TCP/IP protocol stack) """ from construct_legacy import * from ipv4 import ProtocolEnum from binascii import unhexlify import six class Ipv6AddressAdapter(Adapter): def _encode(self, obj, context): if bytes is str: return "".join(part.decode("hex") for part in obj.split(":")) else: return bytes(int(part, 16) for part in obj.split(":")) def _decode(self, obj, context): if bytes is str: return ":".join(b.encode("hex") for b in obj) else: return ":".join("%02x" % (b,) for b in obj) def Ipv6Address(name): return Ipv6AddressAdapter(Bytes(name, 16)) ipv6_header = Struct("ip_header", EmbeddedBitStruct( OneOf(Bits("version", 4), [6]), Bits("traffic_class", 8), Bits("flow_label", 20), ), UBInt16("payload_length"), ProtocolEnum(UBInt8("protocol")), UBInt8("hoplimit"), Alias("ttl", "hoplimit"), Ipv6Address("source"), Ipv6Address("destination"), ) if __name__ == "__main__": o = ipv6_header.parse(six.b("\x6f\xf0\x00\x00\x01\x02\x06\x80" "0123456789ABCDEF" "FEDCBA9876543210" )) print (o) print (repr(ipv6_header.build(o))) construct_legacy-2.5.3/construct_legacy/protocols/layer3/mtp3.py0000666000000000000000000000030713202307133023245 0ustar 00000000000000""" Message Transport Part 3 (SS7 protocol stack) (untested) """ from construct_legacy import * mtp3_header = BitStruct("mtp3_header", Nibble("service_indicator"), Nibble("subservice"), ) construct_legacy-2.5.3/construct_legacy/protocols/layer3/__init__.py0000666000000000000000000000004512756674033024143 0ustar 00000000000000""" layer 3 (network) protocols """ construct_legacy-2.5.3/construct_legacy/protocols/layer4/0000777000000000000000000000000013202307640022014 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy/protocols/layer4/isup.py0000666000000000000000000000044213202307133023343 0ustar 00000000000000""" ISDN User Part (SS7 protocol stack) """ from construct_legacy import * isup_header = Struct("isup_header", Bytes("routing_label", 5), UBInt16("cic"), UBInt8("message_type"), # mandatory fixed parameters # mandatory variable parameters # optional parameters ) construct_legacy-2.5.3/construct_legacy/protocols/layer4/tcp.py0000666000000000000000000000211313202307133023146 0ustar 00000000000000""" Transmission Control Protocol (TCP/IP protocol stack) """ from construct_legacy import * from binascii import unhexlify import six tcp_header = Struct("tcp_header", UBInt16("source"), UBInt16("destination"), UBInt32("seq"), UBInt32("ack"), EmbeddedBitStruct( ExprAdapter(Nibble("header_length"), encoder = lambda obj, ctx: obj / 4, decoder = lambda obj, ctx: obj * 4, ), Padding(3), Struct("flags", Flag("ns"), Flag("cwr"), Flag("ece"), Flag("urg"), Flag("ack"), Flag("psh"), Flag("rst"), Flag("syn"), Flag("fin"), ), ), UBInt16("window"), UBInt16("checksum"), UBInt16("urgent"), Field("options", lambda ctx: ctx.header_length - 20), ) if __name__ == "__main__": cap = unhexlify(six.b("0db5005062303fb21836e9e650184470c9bc0000")) obj = tcp_header.parse(cap) print (obj) built = tcp_header.build(obj) print (built) assert cap == built construct_legacy-2.5.3/construct_legacy/protocols/layer4/udp.py0000666000000000000000000000111513202307133023151 0ustar 00000000000000""" User Datagram Protocol (TCP/IP protocol stack) """ from construct_legacy import * import six from binascii import unhexlify udp_header = Struct("udp_header", Value("header_length", lambda ctx: 8), UBInt16("source"), UBInt16("destination"), ExprAdapter(UBInt16("payload_length"), encoder = lambda obj, ctx: obj + 8, decoder = lambda obj, ctx: obj - 8, ), UBInt16("checksum"), ) if __name__ == "__main__": cap = unhexlify(six.b("0bcc003500280689")) obj = udp_header.parse(cap) print (obj) print (repr(udp_header.build(obj))) construct_legacy-2.5.3/construct_legacy/protocols/layer4/__init__.py0000666000000000000000000000005312756674033024143 0ustar 00000000000000""" layer 4 (transporation) protocols """ construct_legacy-2.5.3/construct_legacy/protocols/__init__.py0000666000000000000000000000020112756674033022736 0ustar 00000000000000""" protocols - a collection of network protocols unlike the formats package, protocols convey information between two sides """ construct_legacy-2.5.3/construct_legacy/version.py0000666000000000000000000000011112756674033020640 0ustar 00000000000000version = (2, 5, 3) version_string = "2.5.3" release_date = "2016.08.22" construct_legacy-2.5.3/construct_legacy/__init__.py0000666000000000000000000001127713202307144020710 0ustar 00000000000000""" Construct 2 -- Parsing Made Fun Homepage: http://construct.readthedocs.org Hands-on example: >>> from construct_legacy import * >>> s = Struct("foo", ... UBInt8("a"), ... UBInt16("b"), ... ) >>> print s.parse(b"\\x01\\x02\\x03") Container: a = 1 b = 515 >>> s.build(Container(a = 1, b = 0x0203)) b"\\x01\\x02\\x03" """ from construct_legacy.core import (AdaptationError, Adapter, Anchor, ArrayError, Buffered, Construct, ConstructError, Container, FieldError, FormatField, LazyBound, LazyContainer, ListContainer, MetaArray, MetaField, OnDemand, OverwriteError, Packer, Pass, Peek, Pointer, Range, RangeError, Reconfig, RepeatUntil, Restream, Select, SelectError, Sequence, SizeofError, StaticField, Struct, Subconstruct, Switch, SwitchError, Terminator, TerminatorError, ULInt24, Union, Value) from construct_legacy.adapters import (BitIntegerAdapter, BitIntegerError, CStringAdapter, ConstAdapter, ConstError, ExprAdapter, FlagsAdapter, FlagsContainer, HexDumpAdapter, HexString, IndexingAdapter, LengthValueAdapter, MappingAdapter, MappingError, NoneOf, OneOf, PaddedStringAdapter, PaddingAdapter, PaddingError, SlicingAdapter, StringAdapter, TunnelAdapter, ValidationError, Validator) from construct_legacy.macros import (Alias, Aligned, AlignedStruct, Array, BFloat32, BFloat64, Bit, BitField, BitStreamReader, BitStreamWriter, BitStruct, Bitwise, CString, Embedded, EmbeddedBitStruct, Enum, Field, Flag, FlagsEnum, GreedyRange, If, IfThenElse, LFloat32, LFloat64, Magic, NFloat32, NFloat64, Nibble, Octet, OnDemandPointer, OpenRange, Optional, OptionalGreedyRange, Padding, PascalString, PrefixedArray, Rename, SBInt16, SBInt32, SBInt64, SBInt8, SLInt16, SLInt32, SLInt64, SLInt8, SNInt16, SNInt32, SNInt64, SNInt8, SeqOfOne, String, SymmetricMapping, UBInt16, UBInt32, UBInt64, UBInt8, ULInt16, ULInt32, ULInt64, ULInt8, UNInt16, UNInt32, UNInt64, UNInt8, GreedyString) from construct_legacy.lib.expr import this from construct_legacy.debug import Probe, Debugger from construct_legacy.version import version, version_string as __version__ #=============================================================================== # Metadata #=============================================================================== __author__ = "Tomer Filiba , Corbin Simpson " #=============================================================================== # Shorthand expressions #=============================================================================== Bits = BitField Byte = UBInt8 Bytes = Field Const = ConstAdapter Tunnel = TunnelAdapter Embed = Embedded #=============================================================================== # exposed names #=============================================================================== __all__ = [ 'AdaptationError', 'Adapter', 'Alias', 'Aligned', 'AlignedStruct', 'Anchor', 'Array', 'ArrayError', 'BFloat32', 'BFloat64', 'Bit', 'BitField', 'BitIntegerAdapter', 'BitIntegerError', 'BitStreamReader', 'BitStreamWriter', 'BitStruct', 'Bitwise', 'Buffered', 'CString', 'CStringAdapter', 'ConstAdapter', 'ConstError', 'Construct', 'ConstructError', 'Container', 'Debugger', 'Embedded', 'EmbeddedBitStruct', 'Enum', 'ExprAdapter', 'Field', 'FieldError', 'Flag', 'FlagsAdapter', 'FlagsContainer', 'FlagsEnum', 'FormatField', 'GreedyRange', 'HexDumpAdapter', 'HexString', 'If', 'IfThenElse', 'IndexingAdapter', 'LFloat32', 'LFloat64', 'LazyBound', 'LazyContainer', 'LengthValueAdapter', 'ListContainer', 'Magic', 'MappingAdapter', 'MappingError', 'MetaArray', 'MetaField', 'NFloat32', 'NFloat64', 'Nibble', 'NoneOf', 'Octet', 'OnDemand', 'OnDemandPointer', 'OneOf', 'OpenRange', 'Optional', 'OptionalGreedyRange', 'OverwriteError', 'Packer', 'PaddedStringAdapter', 'Padding', 'PaddingAdapter', 'PaddingError', 'PascalString', 'Pass', 'Peek', 'Pointer', 'PrefixedArray', 'Probe', 'Range', 'RangeError', 'Reconfig', 'Rename', 'RepeatUntil', 'Restream', 'SBInt16', 'SBInt32', 'SBInt64', 'SBInt8', 'SLInt16', 'SLInt32', 'SLInt64', 'SLInt8', 'SNInt16', 'SNInt32', 'SNInt64', 'SNInt8', 'Select', 'SelectError', 'SeqOfOne', 'Sequence', 'SizeofError', 'SlicingAdapter', 'StaticField', 'String', 'StringAdapter', 'Struct', 'Subconstruct', 'Switch', 'SwitchError', 'SymmetricMapping', 'Terminator', 'TerminatorError', 'TunnelAdapter', 'UBInt16', 'UBInt32', 'UBInt64', 'UBInt8', 'ULInt16', 'ULInt24', 'ULInt32', 'ULInt64', 'ULInt8', 'UNInt16', 'UNInt32', 'UNInt64', 'UNInt8', 'Union', 'ValidationError', 'Validator', 'Value', 'this', 'Bits', 'Byte', 'Bytes', 'Const', 'Tunnel', 'Embed', ] construct_legacy-2.5.3/construct_legacy.egg-info/0000777000000000000000000000000013202307640020262 5ustar 00000000000000construct_legacy-2.5.3/construct_legacy.egg-info/dependency_links.txt0000666000000000000000000000000113202307637024336 0ustar 00000000000000 construct_legacy-2.5.3/construct_legacy.egg-info/PKG-INFO0000666000000000000000000001130613202307637021366 0ustar 00000000000000Metadata-Version: 1.1 Name: construct-legacy Version: 2.5.3 Summary: A powerful declarative parser/builder for binary data (legacy version - 2.5) Home-page: http://construct.readthedocs.org Author: Tomer Filiba, Corbin Simpson Author-email: tomerfiliba@gmail.com, MostAwesomeDude@gmail.com License: MIT Description-Content-Type: UNKNOWN Description: Construct2 ========== Construct is a powerful **declarative** parser (and builder) for binary data. Instead of writing *imperative code* to parse a piece of data, you declaratively define a *data structure* that describes your data. As this data structure is not code, you can use it in one direction to *parse* data into Pythonic objects, and in the other direction, convert ("build") objects into binary data. The library provides both simple, atomic constructs (such as integers of various sizes), as well as composite ones which allow you form hierarchical structures of increasing complexity. Construct features **bit and byte granularity**, easy debugging and testing, an **easy-to-extend subclass system**, and lots of primitive constructs to make your work easier: * Fields: raw bytes or numerical types * Structs and Sequences: combine simpler constructs into more complex ones * Adapters: change how data is represented * Arrays/Ranges: duplicate constructs * Meta-constructs: use the context (history) to compute the size of data * If/Switch: branch the computational path based on the context * On-demand (lazy) parsing: read only what you require * Pointers: jump from here to there in the data stream .. note:: This is a *legacy* fork of the original construct 2.5.3 repackaged for compatibility purposes. Example ------- A ``PascalString`` is a string prefixed by its length:: >>> from construct_legacy import * >>> >>> PascalString = Struct("PascalString", ... UBInt8("length"), ... Bytes("data", lambda ctx: ctx.length), ... ) >>> >>> PascalString.parse("\x05helloXXX") Container({'length': 5, 'data': 'hello'}) >>> PascalString.build(Container(length = 6, data = "foobar")) '\x06foobar' Instead of specifying the length manually, let's use an adapter:: >>> PascalString2 = ExprAdapter(PascalString, ... encoder = lambda obj, ctx: Container(length = len(obj), data = obj), ... decoder = lambda obj, ctx: obj.data ... ) >>> PascalString2.parse("\x05hello") 'hello' >>> PascalString2.build("i'm a long string") "\x11i'm a long string" See more examples of `file formats `_ and `network protocols `_ in the repository. Resources --------- Construct's homepage is ``_, where you can find all kinds of docs and resources. The library itself is developed on `github `_; please use `github issues `_ to report bugs, and github pull-requests to send in patches. For general discussion or questions, please use the `new discussion group `_. Requirements ------------ Construct should run on any Python 2.5-3.5 or PyPy implementation. Its only requirement is `six `_, which is used to overcome the differences between Python 2 and 3. Keywords: construct,declarative,data structure,binary,parser,builder,pack,unpack Platform: POSIX Platform: Windows Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.0 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Requires: six Provides: construct_legacy construct_legacy-2.5.3/construct_legacy.egg-info/requires.txt0000666000000000000000000000000413202307637022662 0ustar 00000000000000six construct_legacy-2.5.3/construct_legacy.egg-info/SOURCES.txt0000666000000000000000000000426113202307637022157 0ustar 00000000000000LICENSE MANIFEST.in README.rst setup.cfg setup.py construct_legacy/__init__.py construct_legacy/adapters.py construct_legacy/core.py construct_legacy/debug.py construct_legacy/macros.py construct_legacy/version.py construct_legacy.egg-info/PKG-INFO construct_legacy.egg-info/SOURCES.txt construct_legacy.egg-info/dependency_links.txt construct_legacy.egg-info/requires.txt construct_legacy.egg-info/top_level.txt construct_legacy/formats/__init__.py construct_legacy/formats/data/__init__.py construct_legacy/formats/data/cap.py construct_legacy/formats/data/snoop.py construct_legacy/formats/executable/__init__.py construct_legacy/formats/executable/elf32.py construct_legacy/formats/executable/pe32.py construct_legacy/formats/filesystem/__init__.py construct_legacy/formats/filesystem/ext2.py construct_legacy/formats/filesystem/fat16.py construct_legacy/formats/filesystem/mbr.py construct_legacy/formats/graphics/__init__.py construct_legacy/formats/graphics/bmp.py construct_legacy/formats/graphics/emf.py construct_legacy/formats/graphics/gif.py construct_legacy/formats/graphics/png.py construct_legacy/formats/graphics/wmf.py construct_legacy/lib/__init__.py construct_legacy/lib/binary.py construct_legacy/lib/bitstream.py construct_legacy/lib/container.py construct_legacy/lib/expr.py construct_legacy/lib/hex.py construct_legacy/lib/py3compat.py construct_legacy/protocols/__init__.py construct_legacy/protocols/ipstack.py construct_legacy/protocols/application/__init__.py construct_legacy/protocols/application/dns.py construct_legacy/protocols/layer2/__init__.py construct_legacy/protocols/layer2/arp.py construct_legacy/protocols/layer2/ethernet.py construct_legacy/protocols/layer2/mtp2.py construct_legacy/protocols/layer3/__init__.py construct_legacy/protocols/layer3/dhcpv4.py construct_legacy/protocols/layer3/dhcpv6.py construct_legacy/protocols/layer3/icmpv4.py construct_legacy/protocols/layer3/igmpv2.py construct_legacy/protocols/layer3/ipv4.py construct_legacy/protocols/layer3/ipv6.py construct_legacy/protocols/layer3/mtp3.py construct_legacy/protocols/layer4/__init__.py construct_legacy/protocols/layer4/isup.py construct_legacy/protocols/layer4/tcp.py construct_legacy/protocols/layer4/udp.pyconstruct_legacy-2.5.3/construct_legacy.egg-info/top_level.txt0000666000000000000000000000002113202307637023013 0ustar 00000000000000construct_legacy construct_legacy-2.5.3/LICENSE0000666000000000000000000000216012756674033014244 0ustar 00000000000000Copyright (C) 2006-2013 Tomer Filiba (tomerfiliba@gmail.com) Corbin Simpson (MostAwesomeDude@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. construct_legacy-2.5.3/MANIFEST.in0000666000000000000000000000004412756674033014774 0ustar 00000000000000include README.rst include LICENSE construct_legacy-2.5.3/PKG-INFO0000666000000000000000000001130613202307640014316 0ustar 00000000000000Metadata-Version: 1.1 Name: construct_legacy Version: 2.5.3 Summary: A powerful declarative parser/builder for binary data (legacy version - 2.5) Home-page: http://construct.readthedocs.org Author: Tomer Filiba, Corbin Simpson Author-email: tomerfiliba@gmail.com, MostAwesomeDude@gmail.com License: MIT Description-Content-Type: UNKNOWN Description: Construct2 ========== Construct is a powerful **declarative** parser (and builder) for binary data. Instead of writing *imperative code* to parse a piece of data, you declaratively define a *data structure* that describes your data. As this data structure is not code, you can use it in one direction to *parse* data into Pythonic objects, and in the other direction, convert ("build") objects into binary data. The library provides both simple, atomic constructs (such as integers of various sizes), as well as composite ones which allow you form hierarchical structures of increasing complexity. Construct features **bit and byte granularity**, easy debugging and testing, an **easy-to-extend subclass system**, and lots of primitive constructs to make your work easier: * Fields: raw bytes or numerical types * Structs and Sequences: combine simpler constructs into more complex ones * Adapters: change how data is represented * Arrays/Ranges: duplicate constructs * Meta-constructs: use the context (history) to compute the size of data * If/Switch: branch the computational path based on the context * On-demand (lazy) parsing: read only what you require * Pointers: jump from here to there in the data stream .. note:: This is a *legacy* fork of the original construct 2.5.3 repackaged for compatibility purposes. Example ------- A ``PascalString`` is a string prefixed by its length:: >>> from construct_legacy import * >>> >>> PascalString = Struct("PascalString", ... UBInt8("length"), ... Bytes("data", lambda ctx: ctx.length), ... ) >>> >>> PascalString.parse("\x05helloXXX") Container({'length': 5, 'data': 'hello'}) >>> PascalString.build(Container(length = 6, data = "foobar")) '\x06foobar' Instead of specifying the length manually, let's use an adapter:: >>> PascalString2 = ExprAdapter(PascalString, ... encoder = lambda obj, ctx: Container(length = len(obj), data = obj), ... decoder = lambda obj, ctx: obj.data ... ) >>> PascalString2.parse("\x05hello") 'hello' >>> PascalString2.build("i'm a long string") "\x11i'm a long string" See more examples of `file formats `_ and `network protocols `_ in the repository. Resources --------- Construct's homepage is ``_, where you can find all kinds of docs and resources. The library itself is developed on `github `_; please use `github issues `_ to report bugs, and github pull-requests to send in patches. For general discussion or questions, please use the `new discussion group `_. Requirements ------------ Construct should run on any Python 2.5-3.5 or PyPy implementation. Its only requirement is `six `_, which is used to overcome the differences between Python 2 and 3. Keywords: construct,declarative,data structure,binary,parser,builder,pack,unpack Platform: POSIX Platform: Windows Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.0 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Requires: six Provides: construct_legacy construct_legacy-2.5.3/README.rst0000666000000000000000000000603713202307475014723 0ustar 00000000000000Construct2 ========== Construct is a powerful **declarative** parser (and builder) for binary data. Instead of writing *imperative code* to parse a piece of data, you declaratively define a *data structure* that describes your data. As this data structure is not code, you can use it in one direction to *parse* data into Pythonic objects, and in the other direction, convert ("build") objects into binary data. The library provides both simple, atomic constructs (such as integers of various sizes), as well as composite ones which allow you form hierarchical structures of increasing complexity. Construct features **bit and byte granularity**, easy debugging and testing, an **easy-to-extend subclass system**, and lots of primitive constructs to make your work easier: * Fields: raw bytes or numerical types * Structs and Sequences: combine simpler constructs into more complex ones * Adapters: change how data is represented * Arrays/Ranges: duplicate constructs * Meta-constructs: use the context (history) to compute the size of data * If/Switch: branch the computational path based on the context * On-demand (lazy) parsing: read only what you require * Pointers: jump from here to there in the data stream .. note:: This is a *legacy* fork of the original construct 2.5.3 repackaged for compatibility purposes. Example ------- A ``PascalString`` is a string prefixed by its length:: >>> from construct_legacy import * >>> >>> PascalString = Struct("PascalString", ... UBInt8("length"), ... Bytes("data", lambda ctx: ctx.length), ... ) >>> >>> PascalString.parse("\x05helloXXX") Container({'length': 5, 'data': 'hello'}) >>> PascalString.build(Container(length = 6, data = "foobar")) '\x06foobar' Instead of specifying the length manually, let's use an adapter:: >>> PascalString2 = ExprAdapter(PascalString, ... encoder = lambda obj, ctx: Container(length = len(obj), data = obj), ... decoder = lambda obj, ctx: obj.data ... ) >>> PascalString2.parse("\x05hello") 'hello' >>> PascalString2.build("i'm a long string") "\x11i'm a long string" See more examples of `file formats `_ and `network protocols `_ in the repository. Resources --------- Construct's homepage is ``_, where you can find all kinds of docs and resources. The library itself is developed on `github `_; please use `github issues `_ to report bugs, and github pull-requests to send in patches. For general discussion or questions, please use the `new discussion group `_. Requirements ------------ Construct should run on any Python 2.5-3.5 or PyPy implementation. Its only requirement is `six `_, which is used to overcome the differences between Python 2 and 3. construct_legacy-2.5.3/setup.cfg0000666000000000000000000000011213202307640015033 0ustar 00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 construct_legacy-2.5.3/setup.py0000666000000000000000000000364413202306463014743 0ustar 00000000000000#!/usr/bin/env python import os try: from setuptools import setup except ImportError: from distutils.core import setup HERE = os.path.dirname(__file__) exec(open(os.path.join(HERE, "construct_legacy", "version.py")).read()) setup( name = "construct_legacy", version = version_string, #@UndefinedVariable packages = [ 'construct_legacy', 'construct_legacy.lib', 'construct_legacy.formats', 'construct_legacy.formats.data', 'construct_legacy.formats.executable', 'construct_legacy.formats.filesystem', 'construct_legacy.formats.graphics', 'construct_legacy.protocols', 'construct_legacy.protocols.application', 'construct_legacy.protocols.layer2', 'construct_legacy.protocols.layer3', 'construct_legacy.protocols.layer4', ], license = "MIT", description = "A powerful declarative parser/builder for binary data (legacy version - 2.5)", long_description = open(os.path.join(HERE, "README.rst")).read(), platforms = ["POSIX", "Windows"], url = "http://construct.readthedocs.org", author = "Tomer Filiba, Corbin Simpson", author_email = "tomerfiliba@gmail.com, MostAwesomeDude@gmail.com", install_requires = ["six"], requires = ["six"], provides = ["construct_legacy"], keywords = "construct, declarative, data structure, binary, parser, builder, pack, unpack", classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", ], )