├── CHANGELOG.md ├── CONTRIBUTORS.md ├── construct3_formats ├── bmp.py ├── cap.py ├── emf.py ├── ext2.py ├── fat16.py ├── gif.py ├── mbr.py ├── pe32.py ├── png.py ├── snoop.py ├── wmf.py └── __init__.py ├── construct3_protocols ├── arp.py ├── dhcp.py ├── dns.py ├── icmp.py ├── tcp.py ├── udp.py ├── __init__.py ├── ethernet.py ├── stacks.py └── ip.py ├── docs ├── .gitignore ├── _static │ └── logo.png ├── api.rst ├── index.rst ├── make.bat ├── Makefile └── conf.py ├── construct3 ├── compiler │ ├── __init__.py │ ├── compiler.py │ ├── optimizer.py │ ├── cython_backend.py │ ├── testpacker.py │ └── python_backend.py ├── version.py ├── lib │ ├── __init__.py │ ├── config.py │ ├── containers.py │ ├── thisexpr.py │ └── binutil.py ├── __init__.py ├── crc.py ├── experimental.py ├── macros.py ├── adapters.py ├── numbers.py └── packers.py ├── MANIFEST.in ├── tests ├── test_2.py ├── test_3.py ├── test_1.py ├── demo.py └── test_numbers.py ├── todo.txt ├── README.md ├── .travis.yml ├── .gitignore ├── LICENSE.md ├── setup.py └── designer └── grider.py /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/bmp.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/cap.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/emf.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/ext2.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/fat16.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/gif.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/mbr.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/pe32.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/png.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/snoop.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/wmf.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/arp.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/dhcp.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/dns.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/icmp.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/tcp.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/udp.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | -------------------------------------------------------------------------------- /construct3/compiler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3/compiler/compiler.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3/compiler/optimizer.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_formats/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/ethernet.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /construct3_protocols/stacks.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | 3 | -------------------------------------------------------------------------------- /construct3/compiler/cython_backend.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_2.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | 4 | print inspect.getsource(inspect.BlockFinder) 5 | 6 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomerfiliba/construct3/HEAD/docs/_static/logo.png -------------------------------------------------------------------------------- /construct3/version.py: -------------------------------------------------------------------------------- 1 | version = (3, 0, 0) 2 | version_string = "3.0.0" 3 | release_date = "2013.03.01" 4 | -------------------------------------------------------------------------------- /construct3/lib/__init__.py: -------------------------------------------------------------------------------- 1 | from construct3.lib.thisexpr import this 2 | from construct3.lib.containers import Container 3 | 4 | def singleton(cls): 5 | return cls() 6 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | * bit level stuff 2 | * tunneling, buffering, restream 3 | * pointers 4 | * crc 5 | * from construct2: introduce container displayers (repr, xml, tree-view) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | construct3 2 | ========== 3 | 4 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/tomerfiliba/construct3/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 5 | 6 | -------------------------------------------------------------------------------- /construct3/compiler/testpacker.py: -------------------------------------------------------------------------------- 1 | import struct as _struct 2 | import struct as _struct 3 | 4 | def test_unpack(stream): 5 | var0 = {} 6 | var1, = _struct.unpack('B', stream.read(1)) 7 | var0['len'] = var1 8 | var2, = _struct.unpack('B', stream.read(1)) 9 | var0['gth'] = var2 10 | var3 = stream.read((var1 + var2)) 11 | var0['data'] = var3 12 | return var0 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.5" 4 | - "2.6" 5 | - "2.7" 6 | - "3.1" 7 | - "3.2" 8 | 9 | install: 10 | - pip install six 11 | 12 | before_script: 13 | - "export PYTHONPATH=$PYTHONPATH:`pwd`" 14 | - "uname -a" 15 | - "cd tests" 16 | 17 | script: nosetests -vv 18 | 19 | notifications: 20 | email: 21 | on_success: change 22 | on_failure: change 23 | -------------------------------------------------------------------------------- /construct3/lib/config.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | 4 | class Config(object): 5 | @contextmanager 6 | def set(self, **kwargs): 7 | prev = self.__dict__.copy() 8 | self.__dict__.update(kwargs) 9 | yield 10 | self.__dict__ = prev 11 | def __getattr__(self, name): 12 | return None 13 | def __delattr__(self, name): 14 | self.__dict__.pop(name, None) 15 | 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | 19 | # Installer logs 20 | pip-log.txt 21 | 22 | # Unit test / coverage reports 23 | .coverage 24 | .tox 25 | nosetests.xml 26 | 27 | # Translations 28 | *.mo 29 | 30 | # Mr Developer 31 | .mr.developer.cfg 32 | .project 33 | .pydevproject 34 | .settings 35 | 36 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | Packers 5 | ------- 6 | .. automodule:: construct3.packers 7 | :members: 8 | :special-members: 9 | 10 | Adapters 11 | -------- 12 | .. automodule:: construct3.adapters 13 | :members: 14 | 15 | Numbers 16 | ------- 17 | .. automodule:: construct3.numbers 18 | :members: 19 | 20 | Macros 21 | ------ 22 | .. automodule:: construct3.macros 23 | :members: 24 | 25 | Compiler 26 | -------- 27 | .. automodule:: construct3.compiler 28 | :members: 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Construct3 documentation master file, created by 2 | sphinx-quickstart on Tue Dec 18 21:01:16 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Construct3's documentation! 7 | ====================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | api 15 | 16 | 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | 25 | -------------------------------------------------------------------------------- /tests/test_3.py: -------------------------------------------------------------------------------- 1 | from construct3 import Struct, Sequence, int8, Embedded, Padding, Computed, this, anchor, Container 2 | 3 | 4 | x = Struct( 5 | "a" / int8, 6 | "b" / int8, 7 | Padding(2), 8 | "c" / int8, 9 | Embedded(Struct( 10 | "d" / int8, 11 | "e" / int8, 12 | "x" / Computed(this.d - this.a), 13 | )), 14 | "f" / int8, 15 | # "g" / anchor, 16 | ) 17 | 18 | #print x.unpack("ABppCDEF") 19 | print repr(x.pack(Container(a=1,b=2,c=3,d=4,e=5,f=6))) 20 | 21 | y = Sequence( 22 | int8, 23 | int8, 24 | Padding(2), 25 | int8, 26 | Embedded(Sequence( 27 | int8, 28 | int8, 29 | Computed(this[4] - this[0]), 30 | )), 31 | int8, 32 | anchor, 33 | ) 34 | 35 | #print y.unpack("ABppCDEF") 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /construct3/__init__.py: -------------------------------------------------------------------------------- 1 | from construct3.version import version, version_string as __version__ 2 | from construct3.lib import this, Container 3 | from construct3.packers import (Packer, PackerError, Adapter, Struct, Raw, Range, Bitwise, Embedded, Sequence, Named, 4 | anchor, Pointer, noop) 5 | from construct3.numbers import (uint8, sint8, uint16b, sint16b, uint16l, sint16l, uint32b, sint32b, uint32l, 6 | sint32l, uint64b, sint64b, uint64l, sint64l, float32b, float64b, float32l, float64l, word8, int8, word16, int16, 7 | word32, int32, word64, int64, float32, float64, Bits, bit, nibble, octet, uint24l, sint24l, uint24b, sint24b, 8 | TwosComplement, MaskedInteger) 9 | from construct3.macros import If, PascalString, Array, Bijection, Enum, flag, BitStruct 10 | from construct3.adapters import Computed, OneOf, NoneOf, StringAdapter, LengthValue, Padding 11 | 12 | __author__ = "Tomer Filiba " 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/test_1.py: -------------------------------------------------------------------------------- 1 | from construct3 import Struct, int16ub, int32ub, this, int8u, Raw 2 | 3 | ipaddr = int8u[4] 4 | print ipaddr 5 | # 6 | #x = u_int8 >> u_int8 >> u_int8 >> (u_int8 >> u_int8) 7 | #print x 8 | #y = u_int8/"a" >> u_int8/"b" >> u_int8/"c" >> (u_int8/"x" >> u_int8/"y")/"d" 9 | #print y 10 | 11 | z = int8u >> Raw(this[0]) 12 | print z 13 | print z.unpack("\x05hello") 14 | 15 | #s = Struct( 16 | # M(foo = ub_int16), 17 | # M(bar = ub_int32), 18 | # M(spam = Struct( 19 | # M(bacon = ub_int16), 20 | # M(eggs = ub_int16), 21 | # )), 22 | # M(viking = ub_int16[this.foo]), 23 | #) 24 | # 25 | #obj = s.unpack("\x00\x03yyyyzzwwaabbcc") 26 | #print obj 27 | ##obj["viking"].append(7) 28 | #print repr(s.pack(obj)) 29 | 30 | #s = Struct( 31 | # int16ub/"foo", 32 | # int32ub/"bar", 33 | # Struct( 34 | # int32ub/"bacon", 35 | # int32ub/"eggs", 36 | # ) / "spam", 37 | #) 38 | #print s 39 | 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Tomer Filiba (tomerfiliba@gmail.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | try: 4 | from setuptools import setup 5 | except ImportError: 6 | from distutils.core import setup 7 | 8 | HERE = os.path.dirname(__file__) 9 | exec(open(os.path.join(HERE, "construct3", "version.py")).read()) 10 | 11 | setup(name = "construct3", 12 | version = version_string, #@UndefinedVariable 13 | description = "Construct3: Binary parser combinators", 14 | author = "Tomer Filiba", 15 | author_email = "tomerfiliba@gmail.com", 16 | license = "MIT", 17 | url = "http://construct.readthedocs.org", 18 | packages = ["construct3", "construct3.lib"], 19 | platforms = ["POSIX", "Windows"], 20 | requires = ["six"], 21 | install_requires = ["six"], 22 | keywords = "construct, data structure, binary, parser, builder, pack, unpack", 23 | #use_2to3 = False, 24 | #zip_safe = True, 25 | long_description = open(os.path.join(HERE, "README.md"), "r").read(), 26 | classifiers = [ 27 | "Development Status :: 4 - Beta", 28 | "License :: OSI Approved :: MIT License", 29 | "Programming Language :: Python :: 2.5", 30 | "Programming Language :: Python :: 2.6", 31 | "Programming Language :: Python :: 2.7", 32 | "Programming Language :: Python :: 3", 33 | "Programming Language :: Python :: 3.0", 34 | "Programming Language :: Python :: 3.1", 35 | "Programming Language :: Python :: 3.2", 36 | "Programming Language :: Python :: 3.3", 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /tests/demo.py: -------------------------------------------------------------------------------- 1 | from construct3 import byte, this, Adapter, Raw, int16ub, Struct, Enum, Sequence 2 | 3 | ipaddr = Sequence(byte, byte, byte, byte) 4 | print ipaddr.unpack("ABCD") # [65, 66, 67, 68] 5 | print ipaddr.pack([65, 66, 67, 68]) # ABCD 6 | 7 | ipaddr = byte >> byte >> byte >> byte 8 | print ipaddr.unpack("ABCD") # [65, 66, 67, 68] 9 | print ipaddr 10 | 11 | ipaddr = byte[4] 12 | print ipaddr.unpack("ABCD") # [65, 66, 67, 68] 13 | 14 | ipaddr = Adapter(byte[4], 15 | decode = lambda arr, _: ".".join(map(str, arr)), 16 | encode = lambda ipstr, _: map(int, ipstr.split(".")) 17 | ) 18 | print ipaddr.unpack("ABCD") # 65.66.67.68 19 | print repr(ipaddr.pack("127.0.0.1")) # '\x7f\x00\x00\x01' 20 | 21 | mac_addr = Adapter(byte[6], 22 | decode = lambda arr, _: "-".join(map("%02x".__mod__, arr)), 23 | encode = lambda macstr, _: macstr.replace("-", "").decode("hex") 24 | ) 25 | 26 | ethernet_header = Struct( 27 | "destination" / mac_addr, 28 | "source" / mac_addr, 29 | "type" / int16ub 30 | ) 31 | 32 | print ethernet_header.unpack("123456ABCDEF\x86\xDD") 33 | 34 | ethernet_header = Struct( 35 | "destination" / mac_addr, 36 | "source" / mac_addr, 37 | "type" / Enum(int16ub, 38 | IPv4 = 0x0800, 39 | ARP = 0x0806, 40 | RARP = 0x8035, 41 | X25 = 0x0805, 42 | IPX = 0x8137, 43 | IPv6 = 0x86DD, 44 | ) 45 | ) 46 | 47 | print ethernet_header.unpack("123456ABCDEF\x86\xDD") 48 | 49 | prefixed_string = byte >> Raw(this[0]) 50 | print prefixed_string.unpack("\x05helloXXX") 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /construct3_protocols/ip.py: -------------------------------------------------------------------------------- 1 | from construct3 import (Struct, Adapter, Enum, int8u, this, Computed, int16ub, Raw, Padding, flag, 2 | BitStruct, Bits, nibble) 3 | 4 | ipaddr = Adapter(int8u[4], 5 | decode = lambda obj, ctx: ".".join(str(x) for x in obj), 6 | encode = lambda obj, ctx: [int(x) for x in obj.split(".")] 7 | ) 8 | 9 | ipv4_header = Struct( 10 | +BitStruct( 11 | "version" / nibble, 12 | "header_length" / Adapter(nibble, decode = lambda obj, _: obj * 4, encode = lambda obj, _: obj / 4), 13 | ), 14 | "tos" / BitStruct( 15 | "precedence" / Bits(3), 16 | "minimize_delay" / flag, 17 | "high_throuput" / flag, 18 | "high_reliability" / flag, 19 | "minimize_cost" / flag, 20 | Padding(1), 21 | ), 22 | "total_length" / int16ub, 23 | "payload_length" / Computed(this.total_length - this.header_length), 24 | int16ub / "identification", 25 | +BitStruct( 26 | "flags" / Struct( 27 | Padding(1), 28 | "dont_fragment" / flag, 29 | "more_fragments" / flag, 30 | ), 31 | "frame_offset" / Bits(13), 32 | ), 33 | "ttl" / int8u, 34 | "protocol" / Enum(int8u, ICMP = 1, TCP = 6, UDP = 17), 35 | "checksum" / int16ub, 36 | "source" / ipaddr, 37 | "destination" / ipaddr, 38 | "options" / Raw(this.header_length - 20), 39 | ) 40 | 41 | 42 | if __name__ == "__main__": 43 | cap = "4500003ca0e3000080116185c0a80205d474a126".decode("hex") 44 | obj = ipv4_header.unpack(cap) 45 | print (obj) 46 | #rebuilt = ipv4_header.pack(obj) 47 | #print (repr(rebuilt)) 48 | #assert cap == rebuilt 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /construct3/crc.py: -------------------------------------------------------------------------------- 1 | from construct3.packers import Packer, Struct 2 | 3 | class DigestingStream(object): 4 | __slots__ = ["stream", "digestor"] 5 | def __init__(self, stream, digestor): 6 | self.stream = stream 7 | self.digestor = digestor 8 | def read(self, count): 9 | data = self.stream.read(count) 10 | self.digestor.update(data) 11 | return data 12 | def write(self, data): 13 | self.digestor.update(data) 14 | self.stream.write(data) 15 | 16 | class Digested(Packer): 17 | def __init__(self, underlying): 18 | self.underlying = underlying 19 | def _unpack(self, stream, ctx, cfg): 20 | return self.underlying._unpack(DigestingStream(stream, cfg.digestor), ctx, cfg) 21 | def _pack(self, obj, stream, ctx, cfg): 22 | self.underlying._pack(obj, DigestingStream(stream, cfg.digestor), ctx, cfg) 23 | 24 | class DigestedStruct(Struct): 25 | def __init__(self, *members, **kwargs): 26 | self.digestor_factory = kwargs.pop("digestor_factory") 27 | Struct.__init__(self, *members, **kwargs) 28 | 29 | def _unpack(self, stream, ctx, cfg): 30 | digestor = self.digestor_factory() 31 | with cfg.set(digestor = digestor): 32 | obj = Struct._unpack(self, stream, ctx, cfg) 33 | obj["__digest__"] = digestor.digest() 34 | return obj 35 | 36 | 37 | 38 | 39 | if __name__ == "__main__": 40 | from construct3 import int8 41 | from hashlib import md5 42 | 43 | d = DigestedStruct( 44 | "a" / Digested(int8), 45 | "b" / Digested(int8), 46 | "c" / Digested(int8), 47 | "d" / Digested(int8), 48 | digestor_factory = md5, 49 | ) 50 | print d.unpack("ABCD") 51 | print repr(md5("ABCD").digest()) 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /construct3/lib/containers.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import six 3 | _counter = itertools.count() 4 | 5 | class Container(dict): 6 | __slots__ = ["__order__"] 7 | def __init__(self, iterable = (), **kwargs): 8 | dict.__setattr__(self, "__order__", {}) 9 | self.update(iterable, **kwargs) 10 | def __setitem__(self, key, val): 11 | dict.__setitem__(self, key, val) 12 | if key not in self.__order__: 13 | self.__order__[key] = six.next(_counter) 14 | def __delitem__(self, key): 15 | dict.__delitem__(self, key) 16 | del self.__order__[key] 17 | def update(self, iterable, **kwargs): 18 | for k, v in iterable: 19 | self[k] = v 20 | for k, v in kwargs.items(): 21 | self[k] = v 22 | def pop(self, key, *default): 23 | dict.pop(self, key, *default) 24 | self.__order__.pop(key, None) 25 | __getattr__ = dict.__getitem__ 26 | __setattr__ = __setitem__ 27 | __delattr__ = __delitem__ 28 | def __iter__(self): 29 | items = list(self.__order__.items()) 30 | items.sort(key = lambda item: item[1]) 31 | return (k for k, _ in items) 32 | def keys(self): 33 | return list(iter(self)) 34 | def values(self): 35 | return [self[k] for k in self] 36 | def items(self): 37 | for k in self: 38 | yield k, self[k] 39 | iterkeys = keys 40 | itervalues = values 41 | iteritems = items 42 | def __repr__(self): 43 | if not self: 44 | return "%s()" % (self.__class__.__name__,) 45 | attrs = "\n ".join("%s = %s" % (k, "\n ".join(repr(v).splitlines())) 46 | for k, v in self.items()) 47 | return "%s:\n %s" % (self.__class__.__name__, attrs) 48 | 49 | 50 | if __name__ == "__main__": 51 | c = Container(aa = 6, b = 7, c = Container(d = 8, e = 9, f = 10)) 52 | del c.aa 53 | c.aa = 0 54 | c.xx = Container() 55 | print [c, c, c] 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /construct3/experimental.py: -------------------------------------------------------------------------------- 1 | from construct3.packers import Packer, Struct 2 | 3 | 4 | class DigestingStream(object): 5 | def __init__(self, stream, digestor): 6 | self.stream = stream 7 | self.digestor = digestor 8 | def read(self, count): 9 | data = self.stream.read(count) 10 | self.digestor.update(data) 11 | return data 12 | def write(self, data): 13 | self.digestor.update(data) 14 | self.stream.write(data) 15 | 16 | class Digested(Packer): 17 | def __init__(self, underlying): 18 | self.underlying = underlying 19 | def _unpack(self, stream, ctx): 20 | return self.underlying._unpack(DigestingStream(stream, ctx["_"]["__digestor__"]), ctx) 21 | def _pack(self, obj, stream, ctx): 22 | self.underlying._pack(obj, DigestingStream(stream, ctx["_"]["__digestor__"]), ctx) 23 | 24 | class DigestedStruct(Struct): 25 | def __init__(self, *members, **kwargs): 26 | self.digestor_factory = kwargs.pop("digestor_factory") 27 | Struct.__init__(self, *members, **kwargs) 28 | 29 | def _unpack(self, stream, ctx): 30 | digestor = ctx["__digestor__"] = self.digestor_factory() 31 | obj = Struct._unpack(self, stream, ctx) 32 | del ctx["__digestor__"] 33 | return obj, digestor 34 | 35 | #class Modified(Packer): 36 | # def __init__(self, underlying, ctx_modifier): 37 | # self.underlying = underlying 38 | # self.ctx_modifier = ctx_modifier 39 | # def _unpack(self, stream, ctx): 40 | # ctx2 = ctx.copy() 41 | # self.ctx_modifier(ctx2) 42 | # return self.underlying._unpack(stream, ctx2) 43 | # 44 | #def DigestedStruct(*members, **kwargs): 45 | # digestor_factory = kwargs.pop("digestor_factory") 46 | # return Modified(Struct(*members, **kwargs), 47 | # lambda ctx: ctx.update(__digestor__ = digestor_factory())) 48 | 49 | if __name__ == "__main__": 50 | from construct3 import uint64b 51 | from hashlib import md5 52 | 53 | d = DigestedStruct( 54 | ("foo", Digested(uint64b)), 55 | ("bar", uint64b), 56 | ("spam", Digested(uint64b)), 57 | digestor_factory = md5 58 | ) 59 | obj, dig = d.unpack("abcdefgh12345678ABCDEFGH") 60 | print obj 61 | print dig.hexdigest() 62 | print dig.hexdigest() == md5("abcdefghABCDEFGH").hexdigest() 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /construct3/macros.py: -------------------------------------------------------------------------------- 1 | from six import b 2 | from construct3.packers import Switch, _contextify, Range, Raw, Struct, Bitwise 3 | from construct3.adapters import LengthValue, StringAdapter, Mapping, Padding 4 | 5 | 6 | def If(cond, thenpkr, elsepkr): 7 | return Switch(lambda ctx, cond = _contextify(cond): bool(cond(ctx)), 8 | {True : thenpkr, False : elsepkr}) 9 | 10 | def PascalString(lengthpkr, encoding = "utf8"): 11 | return StringAdapter(LengthValue(lengthpkr), encoding) 12 | 13 | def Array(count, itempkr): 14 | return Range(count, count, itempkr) 15 | 16 | def Bijection(pkr, enc_mapping, default = NotImplemented): 17 | dec_mapping = dict((v, k) for k, v in enc_mapping.items()) 18 | if default is not NotImplemented: 19 | enc_default = enc_mapping[default] 20 | dec_default = dec_mapping[enc_default] 21 | else: 22 | enc_default = dec_default = NotImplemented 23 | return Mapping(pkr, dec_mapping, enc_mapping, dec_default, enc_default) 24 | 25 | def Enum(pkr, **kwargs): 26 | return Bijection(pkr, kwargs, kwargs.pop("__default__", NotImplemented)) 27 | 28 | flag = Bijection(Raw(1), {True : b("\x01"), False : b("\x00")}, False) 29 | 30 | def BitStruct(*args, **kwargs): 31 | return Bitwise(Struct(*args, **kwargs)) 32 | 33 | def Optional(pkr): 34 | return pkr[0:1] 35 | 36 | def AlignedStruct(*members): 37 | """ 38 | Algorithm taken from http://en.wikipedia.org/wiki/Data_structure_alignment#Computing_padding 39 | 40 | Example:: 41 | 42 | s = AlignedStruct( 43 | "a" / word8, # 0 44 | # padding (1) # 1 45 | "b" / word16, # 2-3 46 | "c" / word16, # 4-5 47 | # padding (2) # 6-7 48 | "d" / word32, # 8-11 49 | "e" / word8, # 12 50 | "f" / BitStruct( # 13 51 | "x" / nibble, 52 | "y" / nibble, 53 | ) 54 | # padding (2) # 14-15 55 | # total size = 16 56 | ) 57 | """ 58 | members2 = [] 59 | offset = 0 60 | largest = 0 61 | # align each member to its native size, e.g., int32 is aligned to 4-bytes 62 | for name, pkr in members: 63 | align = pkr.sizeof() 64 | largest = max(align, largest) 65 | padding = (align - offset % align) % align 66 | offset += align + padding 67 | if padding > 0: 68 | members2.append(Padding(padding)) 69 | members2.append((name, pkr)) 70 | # the struct must be aligned to its largest member 71 | if offset % largest != 0: 72 | members2.append(Padding(largest - offset % largest)) 73 | return Struct(*members2) 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /construct3/adapters.py: -------------------------------------------------------------------------------- 1 | from construct3.packers import Adapter, noop, Raw, PackerError, _contextify, UnnamedPackerMixin, Sequence 2 | from construct3.lib import this 3 | from construct3.lib.containers import Container 4 | from construct3.lib.config import Config 5 | 6 | try: 7 | from io import BytesIO 8 | except ImportError: 9 | from cStringIO import StringIO as BytesIO 10 | import six 11 | 12 | 13 | class ValidationError(PackerError): 14 | pass 15 | class PaddingError(PackerError): 16 | pass 17 | 18 | class SymmetricAdapter(Adapter): 19 | __slots__ = () 20 | def codec(self, obj, ctx): 21 | raise NotImplementedError() 22 | def encode(self, obj, ctx): 23 | return self.codec(obj, ctx) 24 | decode = encode 25 | 26 | class OneOf(SymmetricAdapter): 27 | __slots__ = ["values"] 28 | def __init__(self, pkr, values): 29 | SymmetricAdapter.__init__(self, pkr) 30 | self.values = values 31 | def codec(self, obj, ctx): 32 | if obj not in self.values: 33 | raise ValidationError("%r must be in %r" % (obj, self.values)) 34 | return obj 35 | 36 | class NoneOf(SymmetricAdapter): 37 | __slots__ = ["values"] 38 | def __init__(self, pkr, values): 39 | SymmetricAdapter.__init__(self, pkr) 40 | self.values = values 41 | def codec(self, obj, ctx): 42 | if obj in self.values: 43 | raise ValidationError("%r must not be in %r" % (obj, self.values)) 44 | return obj 45 | 46 | class Computed(SymmetricAdapter): 47 | __slots__ = ["expr"] 48 | def __init__(self, expr): 49 | SymmetricAdapter.__init__(self, noop) 50 | self.expr = _contextify(expr) 51 | def codec(self, obj, ctx): 52 | return self.expr(ctx) 53 | 54 | class Mapping(Adapter): 55 | __slots__ = ["pkr", "enc_mapping", "enc_default", "dec_mapping", "dec_default"] 56 | def __init__(self, pkr, dec_mapping, enc_mapping, dec_default = NotImplemented, enc_default = NotImplemented): 57 | Adapter.__init__(self, pkr) 58 | self.enc_mapping = enc_mapping 59 | self.enc_default = enc_default 60 | self.dec_mapping = dec_mapping 61 | self.dec_default = dec_default 62 | def encode(self, obj, ctx): 63 | if obj in self.enc_mapping: 64 | return self.enc_mapping[obj] 65 | if self.enc_default is NotImplemented: 66 | raise KeyError("%r is unknown and a default value is not given" % (obj,)) 67 | return self.enc_default 68 | def decode(self, obj, ctx): 69 | if obj in self.dec_mapping: 70 | return self.dec_mapping[obj] 71 | if self.dec_default is NotImplemented: 72 | raise KeyError("%r is unknown and a default value is not given" % (obj,)) 73 | return self.dec_default 74 | 75 | #class Stamp(Adapter): 76 | # __slots__ = ["value"] 77 | # def __init__(self, value): 78 | # Adapter.__init__(self, Raw(len(value))) 79 | # self.value = contextify(value) 80 | # def decode(self, obj, ctx): 81 | # if obj != self.value: 82 | # raise ValidationError("%r must be %r" % (obj, self.value)) 83 | # return obj 84 | # def encode(self, obj, ctx): 85 | # return self.value 86 | 87 | class LengthValue(Adapter): 88 | __slots__ = () 89 | def __init__(self, lengthpkr): 90 | Adapter.__init__(self, Sequence(lengthpkr, Raw(this[0]))) 91 | def decode(self, obj, ctx): 92 | return obj[1] 93 | def encode(self, obj, ctx): 94 | return [len(obj), obj] 95 | 96 | class StringAdapter(Adapter): 97 | __slots__ = ["encoding"] 98 | def __init__(self, underlying, encoding): 99 | Adapter.__init__(self, underlying) 100 | self.encoding = encoding 101 | def decode(self, obj, ctx): 102 | return obj.decode(self.encoding) 103 | def encode(self, obj, ctx): 104 | return obj.encode(self.encoding) 105 | 106 | class Padding(UnnamedPackerMixin, Adapter): 107 | def __init__(self, length, padchar = six.b("\x00"), strict = False): 108 | self.length = _contextify(length) 109 | self.padchar = padchar 110 | self.strict = strict 111 | Adapter.__init__(self, Raw(self.length)) 112 | def __repr__(self): 113 | return "Padding(%r)" % (self.length) 114 | def encode(self, obj, ctx): 115 | return self.padchar * self.length(ctx) 116 | def decode(self, obj, ctx): 117 | if self.strict and obj != self.padchar * self.length(ctx): 118 | raise PaddingError("Wrong padding pattern %r" % (obj,)) 119 | return None 120 | 121 | class Flags(Adapter): 122 | __slots__ = ["flags"] 123 | def __init__(self, underlying, **flags): 124 | Adapter.__init__(self, underlying) 125 | self.flags = flags 126 | def _encode(self, obj, context): 127 | num = 0 128 | for name, value in self.flags.items(): 129 | if obj.get(name, None): 130 | num |= value 131 | return num 132 | def _decode(self, obj, context): 133 | return Container((name, bool(obj & mask)) for name, mask in self.flags.items()) 134 | 135 | class Tunnel(Adapter): 136 | __slots__ = ["top"] 137 | def __init__(self, bottom, top): 138 | Adapter.__init__(self, bottom) 139 | self.top = top 140 | def _decode(self, obj, context): 141 | return self.top._unpack(BytesIO(obj), context, Config()) 142 | def _encode(self, obj, context): 143 | stream2 = BytesIO() 144 | self.top._pack(obj, stream2, context, Config()) 145 | return stream2.getvalue() 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /construct3/lib/thisexpr.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | operator_truediv = operator.truediv if hasattr(operator, "truediv") else operator.div 4 | 5 | opnames = { 6 | operator.add : "+", 7 | operator.sub : "-", 8 | operator.mul : "*", 9 | operator_truediv : "/", 10 | operator.floordiv : "//", 11 | operator.mod : "%", 12 | operator.pow : "**", 13 | operator.xor : "^", 14 | operator.lshift : "<<", 15 | operator.rshift : ">>", 16 | operator.and_ : "&", 17 | operator.or_ : "|", 18 | operator.not_ : "~", 19 | operator.neg : "-", 20 | operator.pos : "+", 21 | operator.contains : "in", 22 | operator.gt : ">", 23 | operator.ge : ">=", 24 | operator.lt : "<", 25 | operator.le : "<=", 26 | operator.eq : "==", 27 | operator.ne : "!=", 28 | } 29 | 30 | 31 | class ExprMixin(object): 32 | __slots__ = () 33 | def __add__(self, other): 34 | return BinExpr(operator.add, self, other) 35 | def __sub__(self, other): 36 | return BinExpr(operator.sub, self, other) 37 | def __mul__(self, other): 38 | return BinExpr(operator.mul, self, other) 39 | def __floordiv__(self, other): 40 | return BinExpr(operator.floordiv, self, other) 41 | def __truediv__(self, other): 42 | return BinExpr(operator_truediv, self, other) 43 | __div__ = __floordiv__ 44 | def __mod__(self, other): 45 | return BinExpr(operator.mod, self, other) 46 | def __pow__(self, other): 47 | return BinExpr(operator.pow, self, other) 48 | def __xor__(self, other): 49 | return BinExpr(operator.xor, self, other) 50 | def __rshift__(self, other): 51 | return BinExpr(operator.rshift, self, other) 52 | def __lshift__(self, other): 53 | return BinExpr(operator.rshift, self, other) 54 | def __and__(self, other): 55 | return BinExpr(operator.and_, self, other) 56 | def __or__(self, other): 57 | return BinExpr(operator.or_, self, other) 58 | 59 | def __radd__(self, other): 60 | return BinExpr(operator.add, other, self) 61 | def __rsub__(self, other): 62 | return BinExpr(operator.sub, other, self) 63 | def __rmul__(self, other): 64 | return BinExpr(operator.mul, other, self) 65 | def __rfloordiv__(self, other): 66 | return BinExpr(operator.floordiv, other, self) 67 | def __rtruediv__(self, other): 68 | return BinExpr(operator_truediv, other, self) 69 | __rdiv__ = __rfloordiv__ 70 | def __rmod__(self, other): 71 | return BinExpr(operator.mod, other, self) 72 | def __rpow__(self, other): 73 | return BinExpr(operator.pow, other, self) 74 | def __rxor__(self, other): 75 | return BinExpr(operator.xor, other, self) 76 | def __rrshift__(self, other): 77 | return BinExpr(operator.rshift, other, self) 78 | def __rlshift__(self, other): 79 | return BinExpr(operator.rshift, other, self) 80 | def __rand__(self, other): 81 | return BinExpr(operator.and_, other, self) 82 | def __ror__(self, other): 83 | return BinExpr(operator.or_, other, self) 84 | 85 | def __neg__(self): 86 | return UniExpr(operator.neg, self) 87 | def __pos__(self): 88 | return UniExpr(operator.pos, self) 89 | def __invert__(self): 90 | return UniExpr(operator.not_, self) 91 | __inv__ = __invert__ 92 | 93 | def __contains__(self, other): 94 | return BinExpr(operator.contains, self, other) 95 | def __gt__(self, other): 96 | return BinExpr(operator.gt, self, other) 97 | def __ge__(self, other): 98 | return BinExpr(operator.ge, self, other) 99 | def __lt__(self, other): 100 | return BinExpr(operator.lt, self, other) 101 | def __le__(self, other): 102 | return BinExpr(operator.le, self, other) 103 | def __eq__(self, other): 104 | return BinExpr(operator.eq, self, other) 105 | def __ne__(self, other): 106 | return BinExpr(operator.ne, self, other) 107 | 108 | 109 | class UniExpr(ExprMixin): 110 | __slots__ = ["op", "operand"] 111 | def __init__(self, op, operand): 112 | self.op = op 113 | self.operand = operand 114 | def __repr__(self): 115 | return "%s%r" % (opnames[self.op], self.operand) 116 | def __call__(self, context): 117 | operand = self.operand(context) if callable(self.operand) else self.operand 118 | return self.op(operand) 119 | 120 | 121 | class BinExpr(ExprMixin): 122 | __slots__ = ["op", "lhs", "rhs"] 123 | def __init__(self, op, lhs, rhs): 124 | self.op = op 125 | self.lhs = lhs 126 | self.rhs = rhs 127 | def __repr__(self): 128 | return "(%r %s %r)" % (self.lhs, opnames[self.op], self.rhs) 129 | def __call__(self, context): 130 | lhs = self.lhs(context) if callable(self.lhs) else self.lhs 131 | rhs = self.rhs(context) if callable(self.rhs) else self.rhs 132 | return self.op(lhs, rhs) 133 | 134 | 135 | class Path(ExprMixin): 136 | __slots__ = ["__name", "__parent"] 137 | def __init__(self, name, parent = None): 138 | self.__name = name 139 | self.__parent = parent 140 | def __repr__(self): 141 | if self.__parent is None: 142 | return self.__name 143 | return "%r.%s" % (self.__parent, self.__name) 144 | def __call__(self, context): 145 | if self.__parent is None: 146 | return context 147 | context2 = self.__parent(context) 148 | return context2[self.__name] 149 | def __getattr__(self, name): 150 | return Path(name, self) 151 | __getitem__ = __getattr__ 152 | 153 | 154 | # let the magic begin! 155 | this = Path("this") 156 | 157 | 158 | if __name__ == "__main__": 159 | x = ~((this.foo * 2 + 3 << 2) % 11) 160 | print (x) 161 | print (x({"foo" : 7})) 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Construct3.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Construct3.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /construct3/lib/binutil.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | 4 | empty = six.b("") 5 | 6 | if six.PY3: 7 | def num_to_bits(number, width = 32): 8 | number = int(number) 9 | if number < 0: 10 | number += 1 << width 11 | i = width - 1 12 | bits = bytearray(width) 13 | while number and i >= 0: 14 | bits[i] = number & 1 15 | number >>= 1 16 | i -= 1 17 | return bits 18 | 19 | _bitarr = six.b("01") 20 | def bits_to_num(bits, signed = False): 21 | bits = bytes(_bitarr[b & 1] for b in bits) 22 | if signed and bits[0] == 49: 23 | bits = bits[1:] 24 | bias = 1 << len(bits) 25 | else: 26 | bias = 0 27 | return int(bits, 2) - bias 28 | 29 | _to_bits = [0] * 256 30 | _to_bytes = {} 31 | for i in range(256): 32 | bits = bytes(num_to_bits(i, 8)) 33 | _to_bits[i] = bits 34 | _to_bytes[bits] = i 35 | 36 | def bytes_to_bits(data): 37 | return empty.join(_to_bits[int(b)] for b in data) 38 | 39 | def bits_to_bytes(bits): 40 | if len(bits) & 7: 41 | raise ValueError("The length of `bits` must be a multiple of 8") 42 | return bytes(_to_bytes[bits[i:i+8]] for i in range(0, len(bits), 8)) 43 | 44 | byte_to_int = int 45 | def int_to_byte(num): 46 | return bytes((num,)) 47 | 48 | def swap_bytes(bits, bytesize=8): 49 | i = 0 50 | l = len(bits) 51 | output = bytearray((l // bytesize) + bool(l & 7)) 52 | j = len(output) - 1 53 | while i < l: 54 | output[j] = bits[i : i + bytesize] 55 | i += bytesize 56 | j -= 1 57 | return output 58 | else: 59 | def num_to_bits(number, width = 32): 60 | number = int(number) 61 | if number < 0: 62 | number += 1 << width 63 | i = width - 1 64 | bits = ["\x00"] * width 65 | while number and i >= 0: 66 | bits[i] = "\x00\x01"[number & 1] 67 | number >>= 1 68 | i -= 1 69 | return "".join(bits) 70 | 71 | def bits_to_num(bits, signed = False): 72 | bits = "".join("01"[ord(b) & 1] for b in bits) 73 | if signed and bits[0] == "1": 74 | bits = bits[1:] 75 | bias = 1 << len(bits) 76 | else: 77 | bias = 0 78 | return int(bits, 2) - bias 79 | 80 | _to_bits = [0] * 256 81 | _to_bytes = {} 82 | for i in range(256): 83 | bits = num_to_bits(i, 8) 84 | _to_bits[i] = bits 85 | _to_bytes[bits] = chr(i) 86 | 87 | def bytes_to_bits(data): 88 | return "".join(_to_bits[ord(b)] for b in data) 89 | 90 | def bits_to_bytes(bits): 91 | if len(bits) & 7: 92 | raise ValueError("The length of `bits` must be a multiple of 8") 93 | return "".join(_to_bytes[bits[i:i+8]] for i in range(0, len(bits), 8)) 94 | 95 | byte_to_int = ord 96 | int_to_byte = chr 97 | 98 | def swap_bytes(bits, bytesize=8): 99 | i = 0 100 | l = len(bits) 101 | output = [""] * ((l // bytesize) + 1) 102 | j = len(output) - 1 103 | while i < l: 104 | output[j] = bits[i : i + bytesize] 105 | i += bytesize 106 | j -= 1 107 | return "".join(output) 108 | 109 | 110 | class BitStreamReader(object): 111 | __slots__ = ["stream", "buffer"] 112 | def __init__(self, stream): 113 | self.stream = stream 114 | self.buffer = empty 115 | def read(self, count): 116 | if count == 0: 117 | return empty 118 | if count > len(self.buffer): 119 | extra = count - len(self.buffer) 120 | data = self.stream.read(extra // 8 + bool(extra & 7)) 121 | self.buffer += bytes_to_bits(data) 122 | buf = self.buffer[:count] 123 | self.buffer = self.buffer[count:] 124 | return buf 125 | def close(self): 126 | if self.buffer: 127 | raise ValueError("Not all data has been consumed (it must sum up to whole bytes)", self.buffer) 128 | 129 | class BitStreamWriter(object): 130 | __slots__ = ["stream", "buffer"] 131 | def __init__(self, stream): 132 | self.stream = stream 133 | self.buffer = empty 134 | def write(self, bits): 135 | self.buffer += bits 136 | def flush(self, force_all = False): 137 | if not self.buffer: 138 | return 139 | if force_all and len(self.buffer) & 7: 140 | raise ValueError("Written data must sum up to whole bytes (got %r bits)" % (len(self.buffer,))) 141 | count = (len(self.buffer) >> 3) << 3 142 | bits = self.buffer[:count] 143 | self.buffer = self.buffer[count:] 144 | self.stream.write(bits_to_bytes(bits)) 145 | def close(self): 146 | self.flush(True) 147 | 148 | _printable = ["."] * 256 149 | _printable[32:128] = [chr(i) for i in range(32, 128)] 150 | 151 | def hexdump(data, linesize = 16): 152 | dumped = [] 153 | if len(data) < 65536: 154 | fmt = "%%04X %%-%ds %%s" 155 | else: 156 | fmt = "%%08X %%-%ds %%s" 157 | fmt = fmt % (3 * linesize - 1,) 158 | for i in range(0, len(data), linesize): 159 | line = data[i : i + linesize] 160 | hextext = " ".join('%02x' % (byte_to_int(b),) for b in line) 161 | rawtext = "".join(_printable[byte_to_int(b)] for b in line) 162 | dumped.append(fmt % (i, str(hextext), str(rawtext))) 163 | return "\n".join(dumped) 164 | 165 | 166 | if __name__ == "__main__": 167 | assert bits_to_num(num_to_bits(17, 8)) == 17 168 | assert bits_to_num(num_to_bits(128, 8)) == 128 169 | assert bits_to_num(num_to_bits(255, 8), signed = True) == -1 170 | assert bits_to_bytes(bytes_to_bits(six.b("ABC"))) == six.b("ABC") 171 | print(hexdump("hello world what is the up? how\t\r\n\x00\xff are we feeling today?!", 16)) 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Construct3.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Construct3.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Construct3" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Construct3" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /construct3/compiler/python_backend.py: -------------------------------------------------------------------------------- 1 | import struct as _struct 2 | from srcgen.python import PythonModule, STMT, DEF, IMPORT, FROM_IMPORT, IF, FOR, WHILE, DOC, SEP 3 | from construct3.packers import Packer, Struct, Sequence, Raw, CtxConst, Adapter, noop 4 | from construct3.numbers import Formatted 5 | from construct3.lib.thisexpr import Path, BinExpr, UniExpr, opnames 6 | 7 | 8 | _registry = {} 9 | def register(pkr_cls): 10 | def deco(cls): 11 | _registry[pkr_cls] = cls 12 | return cls 13 | return deco 14 | 15 | def _get_visitor(pkr): 16 | for cls in type(pkr).mro(): 17 | if cls in _registry: 18 | return _registry[cls] 19 | raise TypeError("Cannot generate code for %r" % (pkr,)) 20 | 21 | class BaseVisitor(object): 22 | @classmethod 23 | def setup(cls, pkr): 24 | pass 25 | @classmethod 26 | def generate_unpacker(cls, pkr, streamvar, ctxvar): 27 | raise NotImplementedError() 28 | @classmethod 29 | def generate_packer(cls, pkr, streamvar, ctxvar): 30 | raise NotImplementedError() 31 | 32 | class Variable(object): 33 | _all_vars = frozenset(range(5000)) 34 | _used_vars = set() 35 | def __init__(self): 36 | self.id = min(self._all_vars - self._used_vars) 37 | self._used_vars.add(self.id) 38 | def __enter__(self): 39 | pass 40 | def __exit__(self, t, v, tb): 41 | self.free() 42 | def free(self): 43 | self._used_vars.discard(self.id) 44 | self.id = None 45 | def __del__(self): 46 | self.free() 47 | def __str__(self): 48 | if self.id is None: 49 | raise ValueError("Variable has been freed") 50 | return "var%d" % (self.id,) 51 | __repr__ = __str__ 52 | def __hash__(self): 53 | return hash(str(self)) 54 | def __eq__(self, other): 55 | return str(self) == str(other) 56 | def __ne__(self, other): 57 | return str(self) != str(other) 58 | def __add__(self, other): 59 | return str(self) + other 60 | 61 | def _path_to_list(path): 62 | elems = [] 63 | while path and path._Path__parent: 64 | elems.insert(0, path._Path__name) 65 | path = path._Path__parent 66 | return elems 67 | 68 | def _generate_expr(expr, ctx): 69 | if isinstance(expr, CtxConst): 70 | return repr(expr.value) 71 | elif isinstance(expr, Path): 72 | path = _path_to_list(expr) 73 | while path: 74 | if path[0] in ctx: 75 | ctx = ctx[path[0]] 76 | path.pop(0) 77 | else: 78 | break 79 | #return "%s%s" % (ctx["this"], "".join("[%r]" % (p,) for p in path)) 80 | return "%s%s" % (ctx, "".join("[%r]" % (p,) for p in path)) 81 | elif isinstance(expr, BinExpr): 82 | return "(%s %s %s)" % (_generate_expr(expr.lhs, ctx), opnames[expr.op], _generate_expr(expr.rhs, ctx)) 83 | elif isinstance(expr, UniExpr): 84 | return "%s%s" % (opnames[expr.op], _generate_expr(expr.operand, ctx)) 85 | elif isinstance(expr, (bool, int, float, long, str, unicode)): 86 | return repr(expr) 87 | else: 88 | raise ValueError(expr) 89 | 90 | #======================================================================================================================= 91 | @register(Sequence) 92 | class SequenceVisitor(BaseVisitor): 93 | @classmethod 94 | def setup(cls, pkr): 95 | for pkr2 in pkr.members: 96 | _get_visitor(pkr2).setup(pkr2) 97 | 98 | @classmethod 99 | def generate_unpacker(cls, pkr, streamvar, ctx): 100 | res = Variable() 101 | STMT("{0} = []", res) 102 | ctx2 = {"_" : ctx} 103 | for i, pkr2 in enumerate(pkr.members): 104 | tmp = _generate_unpacker(pkr2, streamvar, ctx2) 105 | ctx2[i] = tmp 106 | STMT("{0}.append({1})", res, tmp) 107 | return res 108 | 109 | @register(Struct) 110 | class StructVisitor(BaseVisitor): 111 | @classmethod 112 | def setup(cls, pkr): 113 | for _, pkr2 in pkr.members: 114 | _get_visitor(pkr2).setup(pkr2) 115 | 116 | @classmethod 117 | def generate_unpacker(cls, pkr, streamvar, ctx): 118 | res = Variable() 119 | STMT("{0} = {{}}", res) 120 | ctx2 = {"_" : ctx} 121 | for name, pkr2 in pkr.members: 122 | tmp = _generate_unpacker(pkr2, streamvar, ctx2) 123 | ctx2[name] = tmp 124 | STMT("{0}[{1!r}] = {2}", res, name, tmp) 125 | return res 126 | 127 | @register(Adapter) 128 | class AdapterVisitor(BaseVisitor): 129 | @classmethod 130 | def setup(cls, pkr): 131 | _get_visitor(pkr.underlying).setup(pkr.underlying) 132 | FROM_IMPORT(pkr.__class__.__module__, pkr.__class__.__name__) 133 | 134 | @classmethod 135 | def generate_unpacker(cls, pkr, streamvar, ctx): 136 | res = _generate_unpacker(pkr.underlying, streamvar, ctx) 137 | STMT("{0} = {1.__class__.__name__}.decode.im_func(None, {0}, {{}})", res, pkr) 138 | return res 139 | 140 | @register(Formatted) 141 | class FormattedVisitor(BaseVisitor): 142 | @classmethod 143 | def setup(cls, pkr): 144 | IMPORT("struct as _struct") 145 | 146 | @classmethod 147 | def generate_unpacker(cls, pkr, streamvar, ctx): 148 | res = Variable() 149 | STMT("{0}, = _struct.unpack({1!r}, {2}.read({3}))", res, pkr.FORMAT, streamvar, 150 | _struct.calcsize(pkr.FORMAT)) 151 | return res 152 | 153 | @register(Raw) 154 | class RawVisitor(BaseVisitor): 155 | @classmethod 156 | def generate_unpacker(cls, pkr, streamvar, ctx): 157 | res = Variable() 158 | STMT("{0} = {1}.read({2})", res, streamvar, _generate_expr(pkr.length, ctx)) 159 | return res 160 | 161 | @register(type(noop)) 162 | class NoopsVisitor(BaseVisitor): 163 | @classmethod 164 | def generate_unpacker(cls, pkr, streamvar, ctx): 165 | pass 166 | 167 | #======================================================================================================================= 168 | 169 | def _generate_unpacker(pkr, streamvar, ctxvar): 170 | return _get_visitor(pkr).generate_unpacker(pkr, streamvar, ctxvar) 171 | 172 | def generate(pkr, name): 173 | with PythonModule(name) as mod: 174 | _get_visitor(pkr).setup(pkr) 175 | SEP() 176 | with DEF("%s_unpack" % (name,), ["stream"]): 177 | ctx = {} 178 | res = _generate_unpacker(pkr, "stream", ctx) 179 | STMT("return {0}", res) 180 | return mod 181 | 182 | 183 | 184 | if __name__ == "__main__": 185 | from construct3.lib import this 186 | from construct3.numbers import byte 187 | from construct3.adapters import LengthValue 188 | #testpkr = Sequence(byte, byte, Sequence(byte, byte, Raw(this[0] + this[1] + this._[0] + this._[1]))) 189 | testpkr = Struct("len" / byte, "gth" / byte, "data" / Raw(this.len + this.gth)) 190 | mod = generate(testpkr, "test") 191 | print testpkr 192 | mod.dump("testpacker.py") 193 | import testpacker 194 | from io import BytesIO 195 | data = BytesIO("\x03\x02helloXX") 196 | #data = BytesIO("\x01\x01\x01\x02helloXX") 197 | print testpacker.test_unpack(data) 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /construct3/numbers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct as _struct 3 | from construct3.lib import singleton 4 | from construct3.packers import Adapter, Raw, Sequence 5 | from construct3.lib.binutil import num_to_bits, swap_bytes, bits_to_num 6 | from construct3.lib.containers import Container 7 | 8 | 9 | class Formatted(Adapter): 10 | __slots__ = ["fmt"] 11 | FORMAT = None 12 | def __init__(self): 13 | self.fmt = _struct.Struct(self.FORMAT) 14 | Adapter.__init__(self, Raw(self.fmt.size)) 15 | def __repr__(self): 16 | return self.__class__.__name__ 17 | def encode(self, obj, ctx): 18 | return self.fmt.pack(obj) 19 | def decode(self, obj, ctx): 20 | return self.fmt.unpack(obj)[0] 21 | 22 | @singleton 23 | class uint8(Formatted): 24 | """Unsigned 8-bit integer""" 25 | FORMAT = "B" 26 | 27 | @singleton 28 | class sint8(Formatted): 29 | """Signed 8-bit integer""" 30 | FORMAT = "b" 31 | 32 | @singleton 33 | class uint16b(Formatted): 34 | """Unsigned big-endian 16-bit integer""" 35 | FORMAT = "!H" 36 | 37 | @singleton 38 | class sint16b(Formatted): 39 | """Signed big-endian 16-bit integer""" 40 | FORMAT = "!h" 41 | 42 | @singleton 43 | class uint16l(Formatted): 44 | """Unsigned little-endian 16-bit integer""" 45 | FORMAT = "> 1 148 | def encode(self, obj, ctx): 149 | return obj + self.maxval if obj < 0 else obj 150 | def decode(self, obj, ctx): 151 | return obj - self.maxval if obj & self.midval else obj 152 | 153 | uint24b = Adapter(Sequence(uint8, uint16b), 154 | decode = lambda obj, _: (obj[0] << 16) | obj[1], 155 | encode = lambda obj, _: (obj >> 16, obj & 0xffff)) 156 | sint24b = TwosComplement(uint24b, 24) 157 | uint24l = Adapter(uint24b, 158 | decode = lambda obj, _: ((obj >> 16) & 0xff) | (obj & 0xff00) | ((obj & 0xff) << 16), 159 | encode = lambda obj, _: ((obj >> 16) & 0xff) | (obj & 0xff00) | ((obj & 0xff) << 16)) 160 | sint24l = TwosComplement(uint24l, 24) 161 | 162 | class MaskedInteger(Adapter): 163 | r""" 164 | >>> m = MaskedInteger(uint16l, 165 | ... bottom4 = (0, 4), 166 | ... upper12 = (4, 12), 167 | ... ) 168 | >>> print m.unpack("\x17\x02") 169 | Container: 170 | bottom4 = 7 171 | upper12 = 33 172 | >>> print repr(m.pack(Container(upper12 = 33, bottom4 = 7))) 173 | '\x17\x02' 174 | """ 175 | __slots__ = ["fields"] 176 | def __init__(self, underlying, **fields): 177 | Adapter.__init__(self, underlying) 178 | self.fields = [(k, offset, (1 << size) - 1) for k, (offset, size) in fields.items()] 179 | def encode(self, obj, ctx): 180 | num = 0 181 | for name, offset, mask in self.fields: 182 | num |= (obj[name] & mask) << offset 183 | return num 184 | def decode(self, obj, ctx): 185 | return Container((name, (obj >> offset) & mask) for name, offset, mask in self.fields) 186 | 187 | #======================================================================================================================= 188 | # aliases 189 | #======================================================================================================================= 190 | word8 = uint8 191 | int8 = sint8 192 | if sys.byteorder == "little": 193 | int16 = sint16l 194 | int24 = sint24l 195 | int32 = sint32l 196 | int64 = sint64l 197 | float32 = float32l 198 | float64 = float64l 199 | word16 = uint16l 200 | word24 = uint24l 201 | word32 = uint32l 202 | word64 = uint64l 203 | else: 204 | int16 = sint16b 205 | int24 = sint24b 206 | int32 = sint32b 207 | int64 = sint64b 208 | float32 = float32b 209 | float64 = float64b 210 | word16 = uint16b 211 | word24 = uint24b 212 | word32 = uint32b 213 | word64 = uint64b 214 | 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /tests/test_numbers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from six import b 3 | from construct3 import (byte, int8, int16ub, int16ul, int16sb, int16sl, int32ub, int32sb, int32ul, int32sl, 4 | int64ub, int64sb, int64ul, int64sl, float32l, float32b, float64l, float64b, int24ul, int24sl, int24ub, int24sb, 5 | bit, nibble, octet, Bitwise, TwosComplement, MaskedInteger, Padding) 6 | 7 | 8 | class TestNumbers(unittest.TestCase): 9 | def test_int8(self): 10 | self.assertEqual(byte.unpack(b("\x88")), 0x88) 11 | self.assertEqual(int8.unpack(b("\x88")), 0x88 - 0x100) 12 | 13 | self.assertEqual(byte.pack(0x88), b("\x88")) 14 | self.assertEqual(int8.pack(0x88 - 0x100), b("\x88")) 15 | 16 | def test_int16(self): 17 | self.assertEqual(int16ub.unpack(b("\x88\x77")), 0x8877) 18 | self.assertEqual(int16sb.unpack(b("\x88\x77")), 0x8877-0x10000) 19 | self.assertEqual(int16ul.unpack(b("\x77\x88")), 0x8877) 20 | self.assertEqual(int16sl.unpack(b("\x77\x88")), 0x8877-0x10000) 21 | 22 | self.assertEqual(int16ub.pack(0x8877), b("\x88\x77")) 23 | self.assertEqual(int16sb.pack(0x8877-0x10000), b("\x88\x77")) 24 | self.assertEqual(int16ul.pack(0x8877), b("\x77\x88")) 25 | self.assertEqual(int16sl.pack(0x8877-0x10000), b("\x77\x88")) 26 | 27 | def test_int32(self): 28 | self.assertEqual(int32ub.unpack(b("\x88\x77\x66\x55")), 0x88776655) 29 | self.assertEqual(int32sb.unpack(b("\x88\x77\x66\x55")), 0x88776655-0x100000000) 30 | self.assertEqual(int32ul.unpack(b("\x55\x66\x77\x88")), 0x88776655) 31 | self.assertEqual(int32sl.unpack(b("\x55\x66\x77\x88")), 0x88776655-0x100000000) 32 | 33 | self.assertEqual(int32ub.pack(0x88776655), b("\x88\x77\x66\x55")) 34 | self.assertEqual(int32sb.pack(0x88776655-0x100000000), b("\x88\x77\x66\x55")) 35 | self.assertEqual(int32ul.pack(0x88776655), b("\x55\x66\x77\x88")) 36 | self.assertEqual(int32sl.pack(0x88776655-0x100000000), b("\x55\x66\x77\x88")) 37 | 38 | def test_int64(self): 39 | self.assertEqual(int64ub.unpack(b("\x88\x77\x66\x55\x44\x33\x22\x11")), 0x8877665544332211) 40 | self.assertEqual(int64sb.unpack(b("\x88\x77\x66\x55\x44\x33\x22\x11")), 0x8877665544332211-0x10000000000000000) 41 | self.assertEqual(int64ul.unpack(b("\x11\x22\x33\x44\x55\x66\x77\x88")), 0x8877665544332211) 42 | self.assertEqual(int64sl.unpack(b("\x11\x22\x33\x44\x55\x66\x77\x88")), 0x8877665544332211-0x10000000000000000) 43 | 44 | self.assertEqual(int64ub.pack(0x8877665544332211), b("\x88\x77\x66\x55\x44\x33\x22\x11")) 45 | self.assertEqual(int64sb.pack(0x8877665544332211-0x10000000000000000), b("\x88\x77\x66\x55\x44\x33\x22\x11")) 46 | self.assertEqual(int64ul.pack(0x8877665544332211), b("\x11\x22\x33\x44\x55\x66\x77\x88")) 47 | self.assertEqual(int64sl.pack(0x8877665544332211-0x10000000000000000), b("\x11\x22\x33\x44\x55\x66\x77\x88")) 48 | 49 | def test_float(self): 50 | self.assertAlmostEqual(float32b.unpack(b("\x41\x02\x02\x02")), 8.1254901886) 51 | self.assertAlmostEqual(float32l.unpack(b("\x02\x02\x02\x41")), 8.1254901886) 52 | self.assertAlmostEqual(float64b.unpack(b("\x41\x02\x02\x02\x03\x03\x03\x03")), 147520.25147058823) 53 | self.assertAlmostEqual(float64l.unpack(b("\x03\x03\x03\x03\x02\x02\x02\x41")), 147520.25147058823) 54 | 55 | self.assertEqual(float32b.pack(8.1254901886), b("\x41\x02\x02\x02")) 56 | self.assertEqual(float32l.pack(8.1254901886), b("\x02\x02\x02\x41")) 57 | self.assertEqual(float64b.pack(147520.25147058823), b("\x41\x02\x02\x02\x03\x03\x03\x03")) 58 | self.assertEqual(float64l.pack(147520.25147058823), b("\x03\x03\x03\x03\x02\x02\x02\x41")) 59 | 60 | def test_bits(self): 61 | self.assertEqual(Bitwise(octet).unpack(b("\x88")), 0x88) 62 | self.assertEqual(TwosComplement(Bitwise(octet), 8).unpack(b("\x88")), 0x88 - 0x100) 63 | self.assertEqual(Bitwise(bit >> Padding(2) >> bit >> bit >> Padding(3)).unpack(b("\x88")), [1, 0, 1]) 64 | self.assertEqual(Bitwise(nibble >> nibble).unpack(b("\x87")), [8, 7]) 65 | 66 | self.assertEqual(Bitwise(octet).pack(0x88), b("\x88")) 67 | self.assertEqual(TwosComplement(Bitwise(octet), 8).pack(0x88 - 0x100), b("\x88")) 68 | self.assertEqual(Bitwise(bit >> Padding(2) >> bit >> bit >> Padding(3)).pack([1, 0, 1]), b("\x88")) 69 | self.assertEqual(Bitwise(nibble >> nibble).pack([8, 7]), b("\x87")) 70 | 71 | self.assertRaises(ValueError, Bitwise(bit >> bit).unpack, b("\x88")) 72 | self.assertRaises(ValueError, Bitwise(bit >> bit).pack, [1,1]) 73 | 74 | def test_int24(self): 75 | self.assertEqual(int24ub.unpack(b("\x03\x02\x01")), 0x030201) 76 | self.assertEqual(int24sb.unpack(b("\x03\x02\x01")), 0x030201) 77 | self.assertEqual(int24ub.unpack(b("\x83\x02\x01")), 0x830201) 78 | self.assertEqual(int24sb.unpack(b("\x83\x02\x01")), 0x830201 - 0x1000000) 79 | self.assertEqual(int24ul.unpack(b("\x01\x02\x03")), 0x030201) 80 | self.assertEqual(int24sl.unpack(b("\x01\x02\x03")), 0x030201) 81 | self.assertEqual(int24ul.unpack(b("\x01\x02\x83")), 0x830201) 82 | self.assertEqual(int24sl.unpack(b("\x01\x02\x83")), 0x830201 - 0x1000000) 83 | 84 | self.assertEqual(int24ub.pack(0x030201), b("\x03\x02\x01")) 85 | self.assertEqual(int24sb.pack(0x030201), b("\x03\x02\x01")) 86 | self.assertEqual(int24ub.pack(0x830201), b("\x83\x02\x01")) 87 | self.assertEqual(int24sb.pack(0x830201 - 0x1000000), b("\x83\x02\x01")) 88 | self.assertEqual(int24ul.pack(0x030201), b("\x01\x02\x03")) 89 | self.assertEqual(int24sl.pack(0x030201), b("\x01\x02\x03")) 90 | self.assertEqual(int24ul.pack(0x830201), b("\x01\x02\x83")) 91 | self.assertEqual(int24sl.pack(0x830201 - 0x1000000), b("\x01\x02\x83")) 92 | 93 | def test_maskedint(self): 94 | self.assertEqual(MaskedInteger(int16ub, lowbyte = (0, 8), highbyte = (8, 8)).unpack(b("\x34\x56")), 95 | dict(highbyte = 0x34, lowbyte = 0x56)) 96 | self.assertEqual(MaskedInteger(int16ub, lownib = (0, 4), midbyte = (4, 8), highnib = (12, 4)).unpack(b("\x34\x56")), 97 | dict(highnib = 0x3, midbyte = 0x45, lownib = 0x6)) 98 | self.assertEqual(MaskedInteger(int16ul, lowbyte = (0, 8), highbyte = (8, 8)).unpack(b("\x56\x34")), 99 | dict(highbyte = 0x34, lowbyte = 0x56)) 100 | self.assertEqual(MaskedInteger(int16ul, lownib = (0, 4), midbyte = (4, 8), highnib = (12, 4)).unpack(b("\x56\x34")), 101 | dict(highnib = 0x3, midbyte = 0x45, lownib = 0x6)) 102 | 103 | self.assertEqual(MaskedInteger(int16ub, lowbyte = (0, 8), highbyte = (8, 8)).pack(dict(highbyte = 0x34, lowbyte = 0x56)), 104 | b("\x34\x56")) 105 | self.assertEqual(MaskedInteger(int16ub, lownib = (0, 4), midbyte = (4, 8), highnib = (12, 4)).pack( 106 | dict(highnib = 0x3, midbyte = 0x45, lownib = 0x6)), b("\x34\x56")) 107 | self.assertEqual(MaskedInteger(int16ul, lowbyte = (0, 8), highbyte = (8, 8)).pack(dict(highbyte = 0x34, lowbyte = 0x56)), 108 | b("\x56\x34")) 109 | self.assertEqual(MaskedInteger(int16ul, lownib = (0, 4), midbyte = (4, 8), highnib = (12, 4)).pack( 110 | dict(highnib = 0x3, midbyte = 0x45, lownib = 0x6)), b("\x56\x34")) 111 | 112 | 113 | if __name__ == "__main__": 114 | unittest.main() 115 | 116 | 117 | -------------------------------------------------------------------------------- /designer/grider.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # UI FRAMEWORK FOR CONSTRUCT 3 DESIGNER 3 | # VERSION: Proof of concept 0.1 (Late night edition) 4 | # DATE: 2012-06-02 5 | # AUTHOR: Jan Costermans 6 | # LICENSE: COPYLEFT 7 | # 8 | # PLANNED UI DESIGN (BRAINSTORM) 9 | # 10 | # Start app -> only the girder frame opens 11 | # User can open a project which loads the girder frame with all the constructs, 12 | # containers, pcap files (for testing), perhaps some network settings for real- 13 | # time monitoring, adapters, and other stuff 14 | # OR User can choose to work without project and just open other frames and do 15 | # stuff. 16 | # 17 | # Several frames can be openend from within the central girder frame. These are 18 | # 1. An editor frame to insert python code (construct or container). 19 | # A tool palette should be openend along with this frame. 20 | # This editor can switch between code view and graphical view. 21 | # The graphical view will support drag-and-drop functionality. 22 | # Multiple editor frames can be open at the same time. 23 | # 2. 24 | # Enable these lines for pubsub v1.x / disable for pusub >= 3.0 25 | # from wx.lib.pubsub import setupv1 26 | # from wx.lib.pubsub import Publisher as pub 27 | # Disable these lines for pubsub v1.x / enable for pusub >= 3.0 28 | # from wx.lib.pubsub import setuparg1 29 | ############################################################################### 30 | import wx 31 | import wx.lib.pubsub.setupkwargs 32 | from wx.lib.pubsub import pub 33 | 34 | # TODO PUT ALL WX ID's OVER HERE 35 | ID_SEARCH_BOX = wx.NewId() 36 | 37 | ############################################################################### 38 | class SomeReceiver(object): 39 | """The pubsub receiver class. http://pubsub.sourceforge.net/""" 40 | def __init__(self): 41 | # The app creates 1 object of this class to handle all messages. 42 | # 43 | pub.subscribe(self.__onObjectAdded, 'object.added') 44 | 45 | # All message handlers go here 46 | # Larger functions go into some other module and are called from here. 47 | 48 | def __onObjectAdded(self, msg): 49 | # data passed with your message is put in msg.data. 50 | # Any object can be passed to subscribers this way. 51 | print "Object", repr(msg), "is added" 52 | pub.sendMessage("completed", msg="HELLO!") 53 | 54 | 55 | ############################################################################### 56 | class constructApp(wx.App): 57 | """Construct designer application""" 58 | def __init__(self, redirect=True, filename=None): 59 | print "App __init__" 60 | wx.App.__init__(self, redirect, filename) 61 | 62 | def OnInit(self): 63 | self.frame = girderFrame(None, 64 | title="Girder") 65 | self.SetTopWindow(self.frame) 66 | self.frame.Show() 67 | return True 68 | 69 | class girderFrame(wx.Frame): 70 | """Main Frame holding the Panel.""" 71 | def __init__(self, *args, **kwargs): 72 | """Create the girderFrame.""" 73 | wx.Frame.__init__(self, *args, **kwargs) 74 | 75 | # Add the Widget Panel 76 | self.Panel = girderPanel(self) 77 | 78 | # Change the Size of the Frame 79 | self.Fit() 80 | 81 | class girderPanel(wx.Panel): 82 | """This Panel holds two simple buttons, but doesn't really do anything.""" 83 | def __init__(self, parent, *args, **kwargs): 84 | """Create the girderPanel.""" 85 | wx.Panel.__init__(self, parent, *args, **kwargs) 86 | 87 | self.parent = parent 88 | 89 | 90 | # subscribe the girderPanel to some pubsub messages: 91 | pub.subscribe(self.onCompleted, 'completed') 92 | 93 | ############################################################################### 94 | # MORE BRAINSTORM 95 | # 1. Search box for searching buttons, recently used constructs, containers,... 96 | # 2. Constructs (collapsible pane) 97 | # 2.1. Create new construct ... ( and adapters ?? ) -> opens an 'editor' frame 98 | # 2.2. Open/Load existing construct 99 | # 2.3. list of recently used constructs, perhaps also favourites, projects, ... 100 | # 3. Containers 101 | # 3.1. Create new Container ... 102 | # 3.2. Open/Load existing Container 103 | # 3.3. list of recently used containers, perhaps also favourites, projects, ... 104 | # 4. Open pcap file / real-time sniffing with pcapy module ?? 105 | # ?? where does the encode/decode frame belong? 106 | 107 | # GIRDER - SEARCH BOX 108 | self.searchbox = ExtendedSearchCtrl(self, 109 | id=ID_SEARCH_BOX, 110 | style=wx.TE_PROCESS_ENTER) 111 | # GIRDER - COLLAPSIBLEPANE 112 | # probably need some kind of list here to make it easy to add stuff 113 | # also needs drag-n-drop for dropping files 114 | # perhaps needs some virtual folders (like code::blocks) 115 | 116 | self.cp = cp = wx.CollapsiblePane(self, 117 | label="colpane", 118 | style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE) 119 | self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, cp) 120 | # self.MakePaneContent(cp.GetPane()) 121 | 122 | 123 | 124 | self.newConstructBtn = wx.Button(self, 125 | id= -1, 126 | label="Create new Struct...") 127 | self.Bind(wx.EVT_BUTTON, self.OnnewConstructBtn, self.newConstructBtn) 128 | 129 | 130 | self.newValueBtn = wx.Button(self, id= -1, label="Request new value") 131 | self.Bind(wx.EVT_BUTTON, self.OnBtn, self.newValueBtn) 132 | # self.newValueBtn.Bind(wx.EVT_BUTTON, self.OnBtn) 133 | 134 | self.displayLbl = wx.StaticText(self, id= -1, label="Value goes here") 135 | 136 | Sizer = wx.BoxSizer(wx.VERTICAL) 137 | Sizer.Add(self.searchbox, 0, wx.ALIGN_LEFT | wx.ALL, 5) 138 | Sizer.Add(self.newConstructBtn, 0, wx.ALIGN_CENTER | wx.ALL, 5) 139 | Sizer.Add(self.newValueBtn, 0, wx.ALIGN_CENTER | wx.ALL, 5) 140 | Sizer.Add(self.displayLbl, 0, wx.ALL | wx.CENTER, 5) 141 | 142 | self.SetSizerAndFit(Sizer) 143 | 144 | ############################################################################### 145 | ### EVENT HANDLING - ALL EVENTS ARE MAPPED TO PUBSUB MESSAGES ### 146 | ############################################################################### 147 | 148 | def OnnewConstructBtn(self, msg): 149 | pass 150 | 151 | def onCompleted(self, msg): 152 | # data passed with your message is put in message.data. 153 | # Any object can be passed to subscribers this way. 154 | print 'pubsub', repr(msg), "this works" 155 | self.newValueBtn.Enable() # doesn't work 156 | self.newValueBtn.SetLabel("This Works") 157 | self.displayLbl.SetLabel("This also works") 158 | 159 | def OnBtn(self, event=None): 160 | 161 | self.displayLbl.SetLabel("Requesting...") 162 | # self.newValueBtn = event.GetEventObject() 163 | self.newValueBtn.Disable() 164 | pub.sendMessage("object.added", msg="HELLO WORLD") 165 | 166 | def OnPaneChanged(self): 167 | pass 168 | 169 | 170 | ############################################################################### 171 | class ExtendedSearchCtrl(wx.SearchCtrl): 172 | """Extended Search Control. Based on a snip from Cody Precord. (thanks) """ 173 | def __init__(self, parent, id, value='', pos=wx.DefaultPosition, 174 | size=wx.DefaultSize, style=wx.TE_PROCESS_ENTER): 175 | wx.SearchCtrl.__init__(self, parent, id, value, pos, size) 176 | self.Bind(wx.EVT_TEXT_ENTER, self.SearchHandler) 177 | self.Bind(wx.EVT_KEY_UP, self.SearchHandler) 178 | self.Show() 179 | 180 | def SearchHandler(self, event): 181 | print "Searching...." 182 | eventType = event.GetEventType() 183 | searchQuery = self.GetValue() 184 | if eventType == wx.wxEVT_COMMAND_TEXT_ENTER: 185 | # TODO Do we need to check for this event? 186 | print "COMMAND_TEXT is looking for" + searchQuery 187 | elif eventType == wx.wxEVT_KEY_UP: 188 | # SEARCH FUNCTIONALITY GOES HERE 189 | print "Looking for: " + searchQuery 190 | # program a filter on this --> USE PUBSUB !! 191 | else: 192 | event.Skip() 193 | 194 | 195 | # TODO ADD THREADING SUPPORT HERE 196 | if __name__ == '__main__': 197 | a = SomeReceiver() 198 | # change redirect to False to use the console for stout and stderr 199 | app = constructApp(redirect=False, filename="mypylog") 200 | 201 | app.MainLoop() 202 | 203 | 204 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Construct3 documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Dec 18 21:01:16 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os, time 15 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 16 | 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be extensions 29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 30 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix of source filenames. 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = u'Construct3' 46 | copyright = u'%d, Tomer Filiba. Licensed under Attribution-ShareAlike 3.0' % (time.gmtime().tm_year,) 47 | 48 | # The version info for the project you're documenting, acts as replacement for 49 | # |version| and |release|, also used in various other places throughout the 50 | # built documents. 51 | # 52 | # The short X.Y version. 53 | from construct3.version import version_string, release_date 54 | version = version_string 55 | # The full version, including alpha/beta/rc tags. 56 | release = version_string + " / " + release_date 57 | 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | #language = None 62 | 63 | # There are two options for replacing |today|: either, you set today to some 64 | # non-false value, then it is used: 65 | #today = '' 66 | # Else, today_fmt is used as the format for a strftime call. 67 | #today_fmt = '%B %d, %Y' 68 | 69 | autodoc_member_order = "bysource" 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | exclude_patterns = ['_build'] 74 | 75 | # The reST default role (used for this markup: `text`) to use for all documents. 76 | #default_role = None 77 | 78 | # If true, '()' will be appended to :func: etc. cross-reference text. 79 | add_function_parentheses = True 80 | 81 | # If true, the current module name will be prepended to all description 82 | # unit titles (such as .. function::). 83 | #add_module_names = True 84 | 85 | # If true, sectionauthor and moduleauthor directives will be shown in the 86 | # output. They are ignored by default. 87 | #show_authors = False 88 | 89 | # The name of the Pygments (syntax highlighting) style to use. 90 | pygments_style = 'sphinx' 91 | 92 | # A list of ignored prefixes for module index sorting. 93 | #modindex_common_prefix = [] 94 | 95 | 96 | # -- Options for HTML output --------------------------------------------------- 97 | 98 | # The theme to use for HTML and HTML Help pages. See the documentation for 99 | # a list of builtin themes. 100 | #html_theme = 'default' 101 | html_theme = 'haiku' 102 | 103 | # Theme options are theme-specific and customize the look and feel of a theme 104 | # further. For a list of options available for each theme, see the 105 | # documentation. 106 | html_theme_options = {"full_logo" : True} 107 | 108 | # Add any paths that contain custom themes here, relative to this directory. 109 | #html_theme_path = [] 110 | 111 | # The name for this set of Sphinx documents. If None, it defaults to 112 | # " v documentation". 113 | html_title = "Construct3: Binary Parsing Combinators" 114 | 115 | # A shorter title for the navigation bar. Default is the same as html_title. 116 | html_short_title = "" 117 | 118 | # The name of an image file (relative to this directory) to place at the top 119 | # of the sidebar. 120 | html_logo = "_static/logo.png" 121 | 122 | # The name of an image file (within the static path) to use as favicon of the 123 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 124 | # pixels large. 125 | #html_favicon = None 126 | 127 | # Add any paths that contain custom static files (such as style sheets) here, 128 | # relative to this directory. They are copied after the builtin static files, 129 | # so a file named "default.css" will overwrite the builtin "default.css". 130 | html_static_path = ['_static'] 131 | 132 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 133 | # using the given strftime format. 134 | #html_last_updated_fmt = '%b %d, %Y' 135 | 136 | # If true, SmartyPants will be used to convert quotes and dashes to 137 | # typographically correct entities. 138 | html_use_smartypants = True 139 | 140 | # Custom sidebar templates, maps document names to template names. 141 | #html_sidebars = {} 142 | 143 | # Additional templates that should be rendered to pages, maps page names to 144 | # template names. 145 | #html_additional_pages = {} 146 | 147 | # If false, no module index is generated. 148 | #html_domain_indices = True 149 | 150 | # If false, no index is generated. 151 | #html_use_index = True 152 | 153 | # If true, the index is split into individual pages for each letter. 154 | #html_split_index = False 155 | 156 | # If true, links to the reST sources are added to the pages. 157 | #html_show_sourcelink = True 158 | 159 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 160 | #html_show_sphinx = True 161 | 162 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 163 | #html_show_copyright = True 164 | 165 | # If true, an OpenSearch description file will be output, and all pages will 166 | # contain a tag referring to it. The value of this option must be the 167 | # base URL from which the finished HTML is served. 168 | #html_use_opensearch = '' 169 | 170 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 171 | #html_file_suffix = None 172 | 173 | # Output file base name for HTML help builder. 174 | htmlhelp_basename = 'Construct3doc' 175 | 176 | 177 | # -- Options for LaTeX output -------------------------------------------------- 178 | 179 | latex_elements = { 180 | # The paper size ('letterpaper' or 'a4paper'). 181 | #'papersize': 'letterpaper', 182 | 183 | # The font size ('10pt', '11pt' or '12pt'). 184 | #'pointsize': '10pt', 185 | 186 | # Additional stuff for the LaTeX preamble. 187 | #'preamble': '', 188 | } 189 | 190 | # Grouping the document tree into LaTeX files. List of tuples 191 | # (source start file, target name, title, author, documentclass [howto/manual]). 192 | latex_documents = [ 193 | ('index', 'Construct3.tex', u'Construct3 Documentation', 194 | u'Tomer Filiba', 'manual'), 195 | ] 196 | 197 | # The name of an image file (relative to this directory) to place at the top of 198 | # the title page. 199 | #latex_logo = None 200 | 201 | # For "manual" documents, if this is true, then toplevel headings are parts, 202 | # not chapters. 203 | #latex_use_parts = False 204 | 205 | # If true, show page references after internal links. 206 | #latex_show_pagerefs = False 207 | 208 | # If true, show URL addresses after external links. 209 | #latex_show_urls = False 210 | 211 | # Documents to append as an appendix to all manuals. 212 | #latex_appendices = [] 213 | 214 | # If false, no module index is generated. 215 | #latex_domain_indices = True 216 | 217 | 218 | # -- Options for manual page output -------------------------------------------- 219 | 220 | # One entry per manual page. List of tuples 221 | # (source start file, name, description, authors, manual section). 222 | man_pages = [ 223 | ('index', 'construct3', u'Construct3 Documentation', 224 | [u'Tomer Filiba'], 1) 225 | ] 226 | 227 | # If true, show URL addresses after external links. 228 | #man_show_urls = False 229 | 230 | 231 | # -- Options for Texinfo output ------------------------------------------------ 232 | 233 | # Grouping the document tree into Texinfo files. List of tuples 234 | # (source start file, target name, title, author, 235 | # dir menu entry, description, category) 236 | texinfo_documents = [ 237 | ('index', 'Construct3', u'Construct3 Documentation', 238 | u'Tomer Filiba', 'Construct3', 'One line description of project.', 239 | 'Miscellaneous'), 240 | ] 241 | 242 | # Documents to append as an appendix to all manuals. 243 | #texinfo_appendices = [] 244 | 245 | # If false, no module index is generated. 246 | #texinfo_domain_indices = True 247 | 248 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 249 | #texinfo_show_urls = 'footnote' 250 | -------------------------------------------------------------------------------- /construct3/packers.py: -------------------------------------------------------------------------------- 1 | from construct3.lib import singleton 2 | import sys 3 | from construct3.lib.binutil import BitStreamReader, BitStreamWriter 4 | from construct3.lib.containers import Container 5 | from construct3.lib.config import Config 6 | try: 7 | from io import BytesIO 8 | except ImportError: 9 | from cStringIO import StringIO as BytesIO 10 | 11 | import six 12 | from six.moves import xrange 13 | 14 | 15 | class PackerError(Exception): 16 | pass 17 | class RawError(PackerError): 18 | pass 19 | class RangeError(PackerError): 20 | pass 21 | class SwitchError(PackerError): 22 | pass 23 | 24 | 25 | class Packer(object): 26 | __slots__ = () 27 | def pack(self, obj): 28 | stream = BytesIO() 29 | self._pack(obj, stream, {}, Config()) 30 | return stream.getvalue() 31 | def pack_to_stream(self, obj, stream): 32 | self._pack(obj, stream, {}, Config()) 33 | def _pack(self, obj, stream, ctx, cfg): 34 | raise NotImplementedError() 35 | 36 | def unpack(self, buf_or_stream): 37 | if not hasattr(buf_or_stream, "read"): 38 | buf_or_stream = BytesIO(buf_or_stream) 39 | return self._unpack(buf_or_stream, {}, Config()) 40 | def _unpack(self, stream, ctx, cfg): 41 | raise NotImplementedError() 42 | 43 | def sizeof(self, ctx = None, cfg = None): 44 | return self._sizeof(ctx or {}, cfg or Config()) 45 | def _sizeof(self, ctx, cfg): 46 | raise NotImplementedError() 47 | 48 | # 49 | # short hands 50 | # 51 | def __getitem__(self, count): 52 | if isinstance(count, slice): 53 | if count.step: 54 | raise ValueError("Slice must not contain as step: %r" % (count,)) 55 | return Range(count.start, count.stop, self) 56 | elif isinstance(count, six.integer_types) or hasattr(count, "__call__"): 57 | return Range(count, count, self) 58 | else: 59 | raise TypeError("Expected a number, a contextual expression or a slice thereof, got %r" % (count,)) 60 | def __rtruediv__(self, name): 61 | if name is not None and not isinstance(name, str): 62 | raise TypeError("`name` must be a string or None, got %r" % (name,)) 63 | return (name, self) 64 | __rdiv__ = __rtruediv__ 65 | 66 | 67 | @singleton 68 | class noop(Packer): 69 | __slots__ = () 70 | def __repr__(self): 71 | return "noop" 72 | def _pack(self, obj, stream, ctx, cfg): 73 | pass 74 | def _unpack(self, stream, ctx, cfg): 75 | return None 76 | def _sizeof(self, ctx, cfg): 77 | return 0 78 | 79 | class CtxConst(object): 80 | __slots__ = ["value"] 81 | def __init__(self, value): 82 | self.value = value 83 | def __repr__(self): 84 | return repr(self.value) 85 | def __call__(self, ctx): 86 | return self.value 87 | 88 | def _contextify(value): 89 | if hasattr(value, "__call__"): 90 | return value 91 | else: 92 | return CtxConst(value) 93 | 94 | class Adapter(Packer): 95 | #__slots__ = ["underlying", "_decode", "_encode"] 96 | def __init__(self, underlying, decode = None, encode = None): 97 | self.underlying = underlying 98 | 99 | if not hasattr(self, '_decode') and decode is None: 100 | self._decode = decode 101 | 102 | if not hasattr(self, '_encode') and encode is None: 103 | self._encode = encode 104 | 105 | def __repr__(self): 106 | return "%s(%s)" % (self.__class__.__name__, self.underlying) 107 | 108 | def _pack(self, obj, stream, ctx, cfg): 109 | obj2 = self.encode(obj, ctx) 110 | self.underlying._pack(obj2, stream, ctx, cfg) 111 | def _unpack(self, stream, ctx, cfg): 112 | obj = self.underlying._unpack(stream, ctx, cfg) 113 | return self.decode(obj, ctx) 114 | def _sizeof(self, ctx, cfg): 115 | return self.underlying._sizeof(ctx, cfg) 116 | 117 | def encode(self, obj, ctx): 118 | if self._encode: 119 | return self._encode(obj, ctx) 120 | else: 121 | return obj 122 | def decode(self, obj, ctx): 123 | if self._decode: 124 | return self._decode(obj, ctx) 125 | else: 126 | return obj 127 | 128 | class Raw(Packer): 129 | __slots__ = ["length"] 130 | def __init__(self, length): 131 | self.length = _contextify(length) 132 | def __repr__(self): 133 | return "Raw(%r)" % (self.length,) 134 | def _pack(self, obj, stream, ctx, cfg): 135 | length = self.length(ctx) 136 | if len(obj) != length: 137 | raise RawError("Expected buffer of length %d, got %d" % (length, len(obj))) 138 | stream.write(obj) 139 | def _unpack(self, stream, ctx, cfg): 140 | length = self.length(ctx) 141 | data = stream.read(length) 142 | if len(data) != length: 143 | raise RawError("Expected buffer of length %d, got %d" % (length, len(data))) 144 | return data 145 | def _sizeof(self, ctx, cfg): 146 | return self.length(ctx) 147 | 148 | def Named(*args, **kwargs): 149 | if (args and kwargs) or (not args and not kwargs): 150 | raise TypeError("This function takes either two positional arguments or a single keyword attribute", 151 | args, kwargs) 152 | elif args: 153 | if len(args) != 2: 154 | raise TypeError("Expected exactly two positional arguments", args) 155 | elif not isinstance(args[0], str): 156 | raise TypeError("The first argument must be a string, got %r" % (args[0],)) 157 | elif not isinstance(args[1], Packer): 158 | raise TypeError("The second argument must be a Packer, got %r" % (args[1],)) 159 | else: 160 | if len(kwargs) != 1: 161 | raise TypeError("Expected exactly one keyword argument", kwargs) 162 | args = kwargs.popitem() 163 | if not isinstance(args[1], Packer): 164 | raise TypeError("The second argument must be a Packer, got %r" % (args[1],)) 165 | if isinstance(args[1], UnnamedPackerMixin): 166 | raise TypeError("%s cannot take a name" % (args[1].__class__.__name__,)) 167 | return tuple(args) 168 | 169 | class UnnamedPackerMixin(object): 170 | # make us look like a tuple 171 | __slots__ = () 172 | def __iter__(self): 173 | yield None 174 | yield self 175 | def __len__(self): 176 | return 2 177 | def __getitem__(self, index): 178 | return None if index == 0 else self 179 | def __rtruediv__(self, other): 180 | raise TypeError("%s cannot take a name" % (self.__class__.__name__,)) 181 | __rdiv__ = __rtruediv__ 182 | 183 | 184 | class Embedded(UnnamedPackerMixin, Packer): 185 | __slots__ = ["underlying"] 186 | def __init__(self, underlying): 187 | self.underlying = underlying 188 | def _unpack(self, stream, ctx, cfg): 189 | with cfg.set(embedded = True): 190 | return self.underlying._unpack(stream, ctx, cfg) 191 | def _pack(self, obj, stream, ctx, cfg): 192 | with cfg.set(embedded = True): 193 | self.underlying._pack(obj, stream, ctx, cfg) 194 | def _sizeof(self, ctx, cfg): 195 | with cfg.set(embedded = True): 196 | return self.underlying._sizeof(ctx, cfg) 197 | 198 | class Struct(Packer): 199 | __slots__ = ["members", "container_factory"] 200 | 201 | def __init__(self, *members, **kwargs): 202 | self.members = members 203 | self.container_factory = kwargs.pop("container_factory", None) 204 | if kwargs: 205 | raise TypeError("invalid keyword argument(s): %s" % (", ".join(kwargs.keys()),)) 206 | names = set() 207 | for mem in members: 208 | if (not hasattr(mem, "__len__") or len(mem) != 2 or 209 | not isinstance(mem[0], (type(None), str)) or not isinstance(mem[1], Packer)): 210 | raise TypeError("Struct members must be 2-tuples of (name, Packer): %r" % (mem,)) 211 | if mem[0] in names: 212 | raise TypeError("Member %r already exists in this struct" % (mem[0],)) 213 | if mem[0]: 214 | names.add(mem[0]) 215 | 216 | def __repr__(self): 217 | return "Struct(%s)" % (", ".join(repr(m) for m in self.members),) 218 | 219 | def _unpack(self, stream, ctx, cfg): 220 | factory = self.container_factory or cfg.container_factory or Container 221 | if cfg.embedded: 222 | ctx2 = ctx 223 | obj = cfg.container 224 | del cfg.embedded 225 | else: 226 | ctx2 = {"_" : ctx} 227 | obj = factory() 228 | with cfg.set(container = obj, ctx = ctx2, container_factory = factory): 229 | for name, pkr in self.members: 230 | cfg.name = name 231 | obj2 = pkr._unpack(stream, ctx2, cfg) 232 | if name: 233 | ctx2[name] = obj[name] = obj2 234 | return obj 235 | 236 | def _pack(self, obj, stream, ctx, cfg): 237 | if cfg.embedded: 238 | ctx2 = ctx 239 | obj = cfg.container 240 | del cfg.embedded 241 | else: 242 | ctx2 = {"_" : ctx} 243 | with cfg.set(container = obj, ctx = ctx2): 244 | for name, pkr in self.members: 245 | cfg.name = name 246 | if not name: 247 | obj2 = None 248 | else: 249 | obj2 = ctx2[name] = obj[name] 250 | pkr._pack(obj2, stream, ctx2, cfg) 251 | 252 | def _sizeof(self, ctx, cfg): 253 | if cfg.embedded: 254 | ctx2 = ctx 255 | del cfg.embedded 256 | else: 257 | ctx2 = {"_" : ctx} 258 | return sum(pkr.sizeof(ctx2, cfg) for _, pkr in self.members) 259 | 260 | 261 | class Sequence(Packer): 262 | __slots__ = ["members", "container_factory"] 263 | 264 | def __init__(self, *members, **kwargs): 265 | self.members = members 266 | self.container_factory = kwargs.pop("container_factory", list) 267 | if kwargs: 268 | raise TypeError("Invalid keyword argument(s): %s" % (", ".join(kwargs.keys()),)) 269 | for mem in members: 270 | if not isinstance(mem, Packer): 271 | raise TypeError("Sequence members must be Packers: %r" % (mem,)) 272 | 273 | def __repr__(self): 274 | return "Sequence(%s)" % (", ".join(repr(m) for m in self.members),) 275 | 276 | def _unpack(self, stream, ctx, cfg): 277 | factory = self.container_factory or cfg.container_factory or Container 278 | if cfg.embedded: 279 | ctx2 = ctx 280 | obj = cfg.container 281 | i = cfg.name + 1 282 | del cfg.embedded 283 | embedded = True 284 | else: 285 | ctx2 = {"_" : ctx} 286 | obj = factory() 287 | i = 0 288 | embedded = False 289 | with cfg.set(container = obj, ctx = ctx, container_factory = factory): 290 | for pkr in self.members: 291 | cfg.name = i 292 | obj2 = pkr._unpack(stream, ctx2, cfg) 293 | if obj2 is not None: 294 | obj.append(obj2) 295 | ctx2[i] = obj2 296 | i += 1 297 | return None if embedded else obj 298 | 299 | def _pack(self, obj, stream, ctx, cfg): 300 | from construct3.adapters import Padding 301 | ctx2 = {"_" : ctx} 302 | i = 0 303 | for pkr in self.members: 304 | if isinstance(pkr, Padding): 305 | pkr._pack(None, stream, ctx2) 306 | else: 307 | obj2 = ctx2[i] = obj[i] 308 | pkr._pack(obj2, stream, ctx2) 309 | i += 1 310 | 311 | def _sizeof(self, ctx, cfg): 312 | if cfg.embedded: 313 | ctx2 = ctx 314 | del cfg.embedded 315 | else: 316 | ctx2 = {"_" : ctx} 317 | return sum(pkr._sizeof(ctx2) for pkr in self.members) 318 | 319 | 320 | class Range(Packer): 321 | __slots__ = ["mincount", "maxcount", "itempkr"] 322 | def __init__(self, mincount, maxcount, itempkr): 323 | self.mincount = _contextify(mincount) 324 | self.maxcount = _contextify(maxcount) 325 | self.itempkr = itempkr 326 | 327 | def __repr__(self): 328 | return "Range(%r, %r, %r)" % (self.mincount, self.maxcount, self.itempkr) 329 | 330 | def _pack(self, obj, stream, ctx, cfg): 331 | mincount = self.mincount(ctx) 332 | if mincount is None: 333 | mincount = 0 334 | maxcount = self.maxcount(ctx) 335 | if maxcount is None: 336 | maxcount = sys.maxsize 337 | assert maxcount >= mincount 338 | if len(obj) < mincount or len(obj) > maxcount: 339 | raise RangeError("Expected %s items, found %s" % ( 340 | mincount if mincount == maxcount else "%s..%s" % (mincount, maxcount), len(obj))) 341 | ctx2 = {"_" : ctx} 342 | for i, item in enumerate(obj): 343 | ctx2[i] = item 344 | #import ipdb; ipdb.set_trace() 345 | self.itempkr._pack(item, stream, ctx2, cfg) 346 | 347 | def _unpack(self, stream, ctx, cfg): 348 | mincount = self.mincount(ctx) 349 | if mincount is None: 350 | mincount = 0 351 | maxcount = self.maxcount(ctx) 352 | if maxcount is None: 353 | maxcount = sys.maxsize 354 | assert maxcount >= mincount 355 | ctx2 = {"_" : ctx} 356 | obj = [] 357 | for i in xrange(maxcount): 358 | try: 359 | obj2 = self.itempkr._unpack(stream, ctx2, cfg) 360 | except PackerError as ex: 361 | if i >= mincount: 362 | break 363 | else: 364 | raise RangeError("Expected %s items, found %s\nUnderlying exception: %r" % ( 365 | mincount if mincount == maxcount else "%s..%s" % (mincount, maxcount), i, ex)) 366 | ctx2[i] = obj2 367 | obj.append(obj2) 368 | return obj 369 | 370 | def _sizeof(self, ctx, cfg): 371 | return self.count(ctx) * self.itempkr._sizeof({"_" : ctx}, cfg) 372 | 373 | 374 | class While(Packer): 375 | __slots__ = ["cond", "itempkr"] 376 | def __init__(self, cond, itempkr): 377 | self.cond = cond 378 | self.itempkr = itempkr 379 | 380 | def __repr__(self): 381 | return "While(%r, %r)" % (self.cond, self.itempkr) 382 | 383 | def _pack(self, obj, stream, ctx, cfg): 384 | ctx2 = {"_" : ctx} 385 | for i, item in enumerate(obj): 386 | ctx2[i] = item 387 | self.itempkr._pack(item, stream, ctx2) 388 | if not self.cond(ctx2): 389 | break 390 | 391 | def _unpack(self, stream, ctx, cfg): 392 | ctx2 = {"_" : ctx} 393 | obj = [] 394 | i = 0 395 | while True: 396 | obj2 = ctx2[i] = self.itempkr._unpack(stream, ctx2) 397 | if not self.cond(ctx2): 398 | break 399 | obj.append(obj2) 400 | i += 1 401 | return obj 402 | 403 | def _sizeof(self): 404 | raise NotImplementedError("Cannot compute sizeof of %r" % (self,)) 405 | 406 | 407 | class Switch(Packer): 408 | __slots__ = ["expr", "cases", "default"] 409 | def __init__(self, expr, cases, default = NotImplemented): 410 | self.expr = expr 411 | self.cases = cases 412 | self.default = default 413 | 414 | def _choose_packer(self, ctx): 415 | val = self.expr(ctx) 416 | if val in self.cases: 417 | return self.cases[val] 418 | elif self.default is not NotImplemented: 419 | return self.default 420 | else: 421 | raise SwitchError("Cannot find a handler for %r" % (val,)) 422 | 423 | def _pack(self, obj, stream, ctx, cfg): 424 | pkr = self._choose_packer(ctx) 425 | pkr._pack(obj, stream, ctx, cfg) 426 | 427 | def _unpack(self, stream, ctx, cfg): 428 | pkr = self._choose_packer(ctx) 429 | return pkr._unpack(stream, ctx, cfg) 430 | 431 | def _sizeof(self, ctx, cfg): 432 | return self._choose_packer(ctx)._sizeof(ctx, cfg) 433 | 434 | #======================================================================================================================= 435 | # Stream-related 436 | #======================================================================================================================= 437 | class Bitwise(Packer): 438 | def __init__(self, underlying): 439 | self.underlying = underlying 440 | def __repr__(self): 441 | return "Bitwise(%r)" % (self.underlying,) 442 | def _unpack(self, stream, ctx, cfg): 443 | stream2 = BitStreamReader(stream) 444 | obj = self.underlying._unpack(stream2, ctx, cfg) 445 | stream2.close() 446 | return obj 447 | def _pack(self, obj, stream, ctx, cfg): 448 | stream2 = BitStreamWriter(stream) 449 | self.underlying._pack(obj, stream2, ctx, cfg) 450 | stream2.close() 451 | def _sizeof(self, ctx, cfg): 452 | return self.underlying._sizeof(ctx, cfg) // 8 453 | 454 | @singleton 455 | class anchor(Packer): 456 | __slots__ = () 457 | def __repr__(self): 458 | return "anchor" 459 | def _unpack(self, stream, ctx, cfg): 460 | return stream.tell() 461 | def _pack(self, obj, stream, ctx, cfg): 462 | ctx[cfg.name] = stream.tell() 463 | def _sizeof(self, ctx, cfg): 464 | return 0 465 | 466 | class Pointer(Packer): 467 | __slots__ = ["underlying", "offset"] 468 | def __init__(self, offset, underlying): 469 | self.underlying = underlying 470 | self.offset = _contextify(offset) 471 | def __repr__(self): 472 | return "Pointer(%r, %r)" % (self.offset, self.underlying) 473 | def _unpack(self, stream, ctx, cfg): 474 | newpos = self.offset(ctx) 475 | origpos = stream.tell() 476 | stream.seek(newpos) 477 | obj = self.underlying._unpack(stream, ctx, cfg) 478 | stream.seek(origpos) 479 | return obj 480 | def _pack(self, obj, stream, ctx, cfg): 481 | newpos = self.offset(ctx) 482 | origpos = stream.tell() 483 | stream.seek(newpos) 484 | self.underlying._pack(obj, stream, ctx, cfg) 485 | stream.seek(origpos) 486 | def _sizeof(self, ctx, cfg): 487 | return 0 488 | 489 | 490 | 491 | --------------------------------------------------------------------------------