├── .github ├── FUNDING.yml └── workflows │ ├── pythonpackage.yml │ └── release.yml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── pyproject.toml ├── requirements.txt ├── setup.py ├── src └── bitstruct │ ├── __init__.py │ ├── bitstream.c │ ├── bitstream.h │ └── c.c └── tests ├── __init__.py ├── test_bitstruct.py └── test_c.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: eerimoq 2 | -------------------------------------------------------------------------------- /.github/workflows/pythonpackage.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | python-version: [ 12 | '3.9', '3.10', '3.11', '3.12', '3.13', 13 | 'pypy-3.9', 'pypy-3.10', 'pypy-3.11' 14 | ] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | python -m pip install pytest 25 | python -m pip install . 26 | - name: Test 27 | run: | 28 | python -m pytest -v --assert=plain ./tests 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build & Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | 10 | jobs: 11 | build_wheels: 12 | name: Build wheels on ${{ matrix.os }} 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | include: 17 | - os: ubuntu-latest 18 | - os: ubuntu-24.04-arm 19 | architectures: auto armv7l 20 | - os: macos-13 # Intel 21 | - os: macos-14 # Apple Silicon 22 | - os: windows-latest 23 | fail-fast: false 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | # Used to host cibuildwheel 29 | - uses: actions/setup-python@v4 30 | with: 31 | python-version: "3.11" 32 | 33 | - name: Install cibuildwheel 34 | run: python -m pip install cibuildwheel 35 | 36 | - name: Build wheels 37 | run: python -m cibuildwheel --output-dir wheelhouse 38 | env: 39 | CIBW_ARCHS: ${{ matrix.architectures || 'auto' }} 40 | 41 | - uses: actions/upload-artifact@v4 42 | with: 43 | name: wheel-${{ matrix.os }} 44 | path: ./wheelhouse/*.whl 45 | 46 | build_sdist: 47 | name: Build source distribution 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | 52 | - uses: actions/setup-python@v4 53 | with: 54 | python-version: "3.11" 55 | 56 | - name: Build sdist 57 | run: pipx run build --sdist 58 | 59 | - uses: actions/upload-artifact@v4 60 | with: 61 | name: sdist 62 | path: dist/*.tar.gz 63 | 64 | release: 65 | name: Publish distribution 📦 to PyPI 66 | needs: [build_wheels, build_sdist] 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: actions/download-artifact@v4 70 | with: 71 | path: dist 72 | merge-multiple: true 73 | 74 | - uses: pypa/gh-action-pypi-publish@release/v1 75 | with: 76 | skip_existing: true 77 | password: ${{ secrets.pypi_password }} 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | *~ -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | python: 12 | install: 13 | - requirements: requirements.txt 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2019 Erik Moqvist 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include Makefile 3 | include src/bitstruct/c.c 4 | include src/bitstruct/bitstream.[hc] 5 | recursive-include docs *.bat 6 | recursive-include docs *.py 7 | recursive-include docs *.rst 8 | recursive-include docs Makefile 9 | recursive-include tests *.py 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | python3 -m pip install -e . 3 | python3 -m unittest 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | This module is intended to have a similar interface as the python 5 | struct module, but working on bits instead of primitive data types 6 | (char, int, ...). 7 | 8 | Project homepage: https://github.com/eerimoq/bitstruct 9 | 10 | Documentation: https://bitstruct.readthedocs.io 11 | 12 | Installation 13 | ============ 14 | 15 | .. code-block:: python 16 | 17 | pip install bitstruct 18 | 19 | Performance 20 | =========== 21 | 22 | Parts of this package has been re-implemented in C for faster pack and 23 | unpack operations. There are two independent C implementations; 24 | `bitstruct.c`, which is part of this package, and the standalone 25 | package `cbitstruct`_. These implementations are only available in 26 | CPython 3, and must be explicitly imported. By default the pure Python 27 | implementation is used. 28 | 29 | To use `bitstruct.c`, do ``import bitstruct.c as bitstruct``. 30 | 31 | To use `cbitstruct`_, do ``import cbitstruct as bitstruct``. 32 | 33 | `bitstruct.c` has a few limitations compared to the pure Python 34 | implementation: 35 | 36 | - Integers and booleans must be 64 bits or less. 37 | 38 | - Text and raw must be a multiple of 8 bits. 39 | 40 | - Bit endianness and byte order are not yet supported. 41 | 42 | - ``byteswap()`` can only swap 1, 2, 4 and 8 bytes. 43 | 44 | See `cbitstruct`_ for its limitations. 45 | 46 | MicroPython 47 | =========== 48 | 49 | The C implementation has been ported to `MicroPython`_. See 50 | `bitstruct-micropython`_ for more details. 51 | 52 | Example usage 53 | ============= 54 | 55 | A basic example of `packing`_ and `unpacking`_ four integers using the 56 | format string ``'u1u3u4s16'``: 57 | 58 | .. code-block:: python 59 | 60 | >>> from bitstruct import * 61 | >>> pack('u1u3u4s16', 1, 2, 3, -4) 62 | b'\xa3\xff\xfc' 63 | >>> unpack('u1u3u4s16', b'\xa3\xff\xfc') 64 | (1, 2, 3, -4) 65 | >>> calcsize('u1u3u4s16') 66 | 24 67 | 68 | An example `compiling`_ the format string once, and use it to `pack`_ 69 | and `unpack`_ data: 70 | 71 | .. code-block:: python 72 | 73 | >>> import bitstruct 74 | >>> cf = bitstruct.compile('u1u3u4s16') 75 | >>> cf.pack(1, 2, 3, -4) 76 | b'\xa3\xff\xfc' 77 | >>> cf.unpack(b'\xa3\xff\xfc') 78 | (1, 2, 3, -4) 79 | 80 | Use the `pack into`_ and `unpack from`_ functions to pack/unpack 81 | values at a bit offset into the data, in this example the bit offset 82 | is 5: 83 | 84 | .. code-block:: python 85 | 86 | >>> from bitstruct import * 87 | >>> data = bytearray(b'\x00\x00\x00\x00') 88 | >>> pack_into('u1u3u4s16', data, 5, 1, 2, 3, -4) 89 | >>> data 90 | bytearray(b'\x05\x1f\xff\xe0') 91 | >>> unpack_from('u1u3u4s16', data, 5) 92 | (1, 2, 3, -4) 93 | 94 | The unpacked values can be named by assigning them to variables or by 95 | wrapping the result in a named tuple: 96 | 97 | .. code-block:: python 98 | 99 | >>> from bitstruct import * 100 | >>> from collections import namedtuple 101 | >>> MyName = namedtuple('myname', ['a', 'b', 'c', 'd']) 102 | >>> unpacked = unpack('u1u3u4s16', b'\xa3\xff\xfc') 103 | >>> myname = MyName(*unpacked) 104 | >>> myname 105 | myname(a=1, b=2, c=3, d=-4) 106 | >>> myname.c 107 | 3 108 | 109 | Use the `pack_dict`_ and `unpack_dict`_ functions to pack/unpack 110 | values in dictionaries: 111 | 112 | .. code-block:: python 113 | 114 | >>> from bitstruct import * 115 | >>> names = ['a', 'b', 'c', 'd'] 116 | >>> pack_dict('u1u3u4s16', names, {'a': 1, 'b': 2, 'c': 3, 'd': -4}) 117 | b'\xa3\xff\xfc' 118 | >>> unpack_dict('u1u3u4s16', names, b'\xa3\xff\xfc') 119 | {'a': 1, 'b': 2, 'c': 3, 'd': -4} 120 | 121 | An example of `packing`_ and `unpacking`_ an unsigned integer, a 122 | signed integer, a float, a boolean, a byte string and a string: 123 | 124 | .. code-block:: python 125 | 126 | >>> from bitstruct import * 127 | >>> pack('u5s5f32b1r13t40', 1, -1, 3.75, True, b'\xff\xff', 'hello') 128 | b'\x0f\xd0\x1c\x00\x00?\xffhello' 129 | >>> unpack('u5s5f32b1r13t40', b'\x0f\xd0\x1c\x00\x00?\xffhello') 130 | (1, -1, 3.75, True, b'\xff\xf8', 'hello') 131 | >>> calcsize('u5s5f32b1r13t40') 132 | 96 133 | 134 | The same format string and values as in the previous example, but 135 | using LSB (Least Significant Bit) first instead of the default MSB 136 | (Most Significant Bit) first: 137 | 138 | .. code-block:: python 139 | 140 | >>> from bitstruct import * 141 | >>> pack('>> unpack('>> calcsize('>> from bitstruct import * 153 | >>> from binascii import unhexlify 154 | >>> unpack('s17s13r24', unhexlify('0123456789abcdef')) 155 | (582, -3751, b'\xe2j\xf3') 156 | >>> with open("test.bin", "rb") as fin: 157 | ... unpack('s17s13r24', fin.read(8)) 158 | ... 159 | ... 160 | (582, -3751, b'\xe2j\xf3') 161 | 162 | Change endianness of the data with `byteswap`_, and then unpack the 163 | values: 164 | 165 | .. code-block:: python 166 | 167 | >>> from bitstruct import * 168 | >>> packed = pack('u1u3u4s16', 1, 2, 3, 1) 169 | >>> unpack('u1u3u4s16', byteswap('12', packed)) 170 | (1, 2, 3, 256) 171 | 172 | A basic example of `packing`_ and `unpacking`_ four integers using the 173 | format string ``'u1u3u4s16'`` using the C implementation: 174 | 175 | .. code-block:: python 176 | 177 | >>> from bitstruct.c import * 178 | >>> pack('u1u3u4s16', 1, 2, 3, -4) 179 | b'\xa3\xff\xfc' 180 | >>> unpack('u1u3u4s16', b'\xa3\xff\xfc') 181 | (1, 2, 3, -4) 182 | 183 | Contributing 184 | ============ 185 | 186 | #. Fork the repository. 187 | 188 | #. Install prerequisites. 189 | 190 | .. code-block:: text 191 | 192 | pip install -r requirements.txt 193 | 194 | #. Implement the new feature or bug fix. 195 | 196 | #. Implement test case(s) to ensure that future changes do not break 197 | legacy. 198 | 199 | #. Run the tests. 200 | 201 | .. code-block:: text 202 | 203 | make test 204 | 205 | #. Create a pull request. 206 | 207 | .. _packing: http://bitstruct.readthedocs.io/en/latest/#bitstruct.pack 208 | 209 | .. _unpacking: http://bitstruct.readthedocs.io/en/latest/#bitstruct.unpack 210 | 211 | .. _pack: http://bitstruct.readthedocs.io/en/latest/#bitstruct.CompiledFormat.pack 212 | 213 | .. _unpack: http://bitstruct.readthedocs.io/en/latest/#bitstruct.CompiledFormat.unpack 214 | 215 | .. _pack into: http://bitstruct.readthedocs.io/en/latest/#bitstruct.pack_into 216 | 217 | .. _unpack from: http://bitstruct.readthedocs.io/en/latest/#bitstruct.unpack_from 218 | 219 | .. _pack_dict: http://bitstruct.readthedocs.io/en/latest/#bitstruct.pack_dict 220 | 221 | .. _unpack_dict: http://bitstruct.readthedocs.io/en/latest/#bitstruct.unpack_dict 222 | 223 | .. _byteswap: http://bitstruct.readthedocs.io/en/latest/#bitstruct.byteswap 224 | 225 | .. _compiling: http://bitstruct.readthedocs.io/en/latest/#bitstruct.compile 226 | 227 | .. _cbitstruct: https://github.com/qchateau/cbitstruct 228 | 229 | .. _MicroPython: https://github.com/micropython/micropython 230 | 231 | .. _bitstruct-micropython: https://github.com/peterzuger/bitstruct-micropython 232 | -------------------------------------------------------------------------------- /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 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/bitstruct.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/bitstruct.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/bitstruct" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/bitstruct" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # bitstruct documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Apr 25 11:54:09 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | import bitstruct 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | #needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.autodoc', 36 | 'sphinx.ext.viewcode', 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | #source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = u'bitstruct' 55 | copyright = u'2015-2019, Erik Moqvist' 56 | author = u'Erik Moqvist' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = bitstruct.__version__ 64 | # The full version, including alpha/beta/rc tags. 65 | release = bitstruct.__version__ 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to some 75 | # non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built documents. 106 | #keep_warnings = False 107 | 108 | # If true, `todo` and `todoList` produce output, else they produce nothing. 109 | todo_include_todos = False 110 | 111 | 112 | # -- Options for HTML output ---------------------------------------------- 113 | 114 | # The theme to use for HTML and HTML Help pages. See the documentation for 115 | # a list of builtin themes. 116 | html_theme = 'sphinx_rtd_theme' 117 | 118 | # Theme options are theme-specific and customize the look and feel of a theme 119 | # further. For a list of options available for each theme, see the 120 | # documentation. 121 | #html_theme_options = {} 122 | 123 | # Add any paths that contain custom themes here, relative to this directory. 124 | #html_theme_path = [] 125 | 126 | # The name for this set of Sphinx documents. If None, it defaults to 127 | # " v documentation". 128 | #html_title = None 129 | 130 | # A shorter title for the navigation bar. Default is the same as html_title. 131 | #html_short_title = None 132 | 133 | # The name of an image file (relative to this directory) to place at the top 134 | # of the sidebar. 135 | #html_logo = None 136 | 137 | # The name of an image file (within the static path) to use as favicon of the 138 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 139 | # pixels large. 140 | #html_favicon = None 141 | 142 | # Add any paths that contain custom static files (such as style sheets) here, 143 | # relative to this directory. They are copied after the builtin static files, 144 | # so a file named "default.css" will overwrite the builtin "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # Add any extra paths that contain custom files (such as robots.txt or 148 | # .htaccess) here, relative to this directory. These files are copied 149 | # directly to the root of the documentation. 150 | #html_extra_path = [] 151 | 152 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 153 | # using the given strftime format. 154 | #html_last_updated_fmt = '%b %d, %Y' 155 | 156 | # If true, SmartyPants will be used to convert quotes and dashes to 157 | # typographically correct entities. 158 | #html_use_smartypants = True 159 | 160 | # Custom sidebar templates, maps document names to template names. 161 | #html_sidebars = {} 162 | 163 | # Additional templates that should be rendered to pages, maps page names to 164 | # template names. 165 | #html_additional_pages = {} 166 | 167 | # If false, no module index is generated. 168 | #html_domain_indices = True 169 | 170 | # If false, no index is generated. 171 | #html_use_index = True 172 | 173 | # If true, the index is split into individual pages for each letter. 174 | #html_split_index = False 175 | 176 | # If true, links to the reST sources are added to the pages. 177 | #html_show_sourcelink = True 178 | 179 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 180 | #html_show_sphinx = True 181 | 182 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 183 | #html_show_copyright = True 184 | 185 | # If true, an OpenSearch description file will be output, and all pages will 186 | # contain a tag referring to it. The value of this option must be the 187 | # base URL from which the finished HTML is served. 188 | #html_use_opensearch = '' 189 | 190 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 191 | #html_file_suffix = None 192 | 193 | # Language to be used for generating the HTML full-text search index. 194 | # Sphinx supports the following languages: 195 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 196 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 197 | #html_search_language = 'en' 198 | 199 | # A dictionary with options for the search language support, empty by default. 200 | # Now only 'ja' uses this config value 201 | #html_search_options = {'type': 'default'} 202 | 203 | # The name of a javascript file (relative to the configuration directory) that 204 | # implements a search results scorer. If empty, the default will be used. 205 | #html_search_scorer = 'scorer.js' 206 | 207 | # Output file base name for HTML help builder. 208 | htmlhelp_basename = 'bitstructdoc' 209 | 210 | # -- Options for LaTeX output --------------------------------------------- 211 | 212 | latex_elements = { 213 | # The paper size ('letterpaper' or 'a4paper'). 214 | #'papersize': 'letterpaper', 215 | 216 | # The font size ('10pt', '11pt' or '12pt'). 217 | #'pointsize': '10pt', 218 | 219 | # Additional stuff for the LaTeX preamble. 220 | #'preamble': '', 221 | 222 | # Latex figure (float) alignment 223 | #'figure_align': 'htbp', 224 | } 225 | 226 | # Grouping the document tree into LaTeX files. List of tuples 227 | # (source start file, target name, title, 228 | # author, documentclass [howto, manual, or own class]). 229 | latex_documents = [ 230 | (master_doc, 'bitstruct.tex', u'bitstruct Documentation', 231 | u'Erik Moqvist', 'manual'), 232 | ] 233 | 234 | # The name of an image file (relative to this directory) to place at the top of 235 | # the title page. 236 | #latex_logo = None 237 | 238 | # For "manual" documents, if this is true, then toplevel headings are parts, 239 | # not chapters. 240 | #latex_use_parts = False 241 | 242 | # If true, show page references after internal links. 243 | #latex_show_pagerefs = False 244 | 245 | # If true, show URL addresses after external links. 246 | #latex_show_urls = False 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #latex_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #latex_domain_indices = True 253 | 254 | 255 | # -- Options for manual page output --------------------------------------- 256 | 257 | # One entry per manual page. List of tuples 258 | # (source start file, name, description, authors, manual section). 259 | man_pages = [ 260 | (master_doc, 'bitstruct', u'bitstruct Documentation', 261 | [author], 1) 262 | ] 263 | 264 | # If true, show URL addresses after external links. 265 | #man_show_urls = False 266 | 267 | 268 | # -- Options for Texinfo output ------------------------------------------- 269 | 270 | # Grouping the document tree into Texinfo files. List of tuples 271 | # (source start file, target name, title, author, 272 | # dir menu entry, description, category) 273 | texinfo_documents = [ 274 | (master_doc, 'bitstruct', u'bitstruct Documentation', 275 | author, 'bitstruct', 'One line description of project.', 276 | 'Miscellaneous'), 277 | ] 278 | 279 | # Documents to append as an appendix to all manuals. 280 | #texinfo_appendices = [] 281 | 282 | # If false, no module index is generated. 283 | #texinfo_domain_indices = True 284 | 285 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 286 | #texinfo_show_urls = 'footnote' 287 | 288 | # If true, do not generate a @detailmenu in the "Top" node's menu. 289 | #texinfo_no_detailmenu = False 290 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. bitstruct documentation master file, created by 2 | sphinx-quickstart on Sat Apr 25 11:54:09 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | bitstruct - Interpret strings as packed binary data 10 | =================================================== 11 | 12 | .. include:: ../README.rst 13 | 14 | Functions 15 | ========= 16 | 17 | .. autofunction:: bitstruct.pack 18 | .. autofunction:: bitstruct.unpack 19 | .. autofunction:: bitstruct.pack_into 20 | .. autofunction:: bitstruct.unpack_from 21 | .. autofunction:: bitstruct.pack_dict 22 | .. autofunction:: bitstruct.unpack_dict 23 | .. autofunction:: bitstruct.pack_into_dict 24 | .. autofunction:: bitstruct.unpack_from_dict 25 | .. autofunction:: bitstruct.calcsize 26 | .. autofunction:: bitstruct.byteswap 27 | .. autofunction:: bitstruct.compile 28 | 29 | Classes 30 | ======= 31 | 32 | .. autoclass:: bitstruct.CompiledFormat 33 | 34 | .. automethod:: bitstruct.CompiledFormat.pack 35 | .. automethod:: bitstruct.CompiledFormat.unpack 36 | .. automethod:: bitstruct.CompiledFormat.pack_into 37 | .. automethod:: bitstruct.CompiledFormat.unpack_from 38 | 39 | .. autoclass:: bitstruct.CompiledFormatDict 40 | 41 | .. automethod:: bitstruct.CompiledFormatDict.pack 42 | .. automethod:: bitstruct.CompiledFormatDict.unpack 43 | .. automethod:: bitstruct.CompiledFormatDict.pack_into 44 | .. automethod:: bitstruct.CompiledFormatDict.unpack_from 45 | -------------------------------------------------------------------------------- /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. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\bitstruct.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\bitstruct.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = 'setuptools.build_meta' 3 | requires = ["setuptools >= 68.0"] 4 | 5 | [project] 6 | name = "bitstruct" 7 | description = """This module performs conversions between Python values \ 8 | and C bit field structs represented as Python \ 9 | byte strings.""" 10 | readme = "README.rst" 11 | requires-python = ">=3.9" 12 | license = "MIT" 13 | keywords = [ 14 | "bit field", 15 | "bit parsing", 16 | "bit unpack", 17 | "bit pack", 18 | ] 19 | authors = [ 20 | { name = "Erik Moqvist", email = "erik.moqvist@gmail.com" }, 21 | { name = "Ilya Petukhov" }, 22 | ] 23 | classifiers = [ 24 | "Development Status :: 5 - Production/Stable", 25 | "Programming Language :: Python", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: Implementation :: CPython", 28 | ] 29 | dependencies = [] 30 | dynamic = ["version"] 31 | 32 | [project.urls] 33 | Documentation = "https://bitstruct.readthedocs.io/en/latest/" 34 | Issues = "https://github.com/eerimoq/bitstruct/issues" 35 | Source = "https://github.com/eerimoq/bitstruct" 36 | Homepage = "https://github.com/eerimoq/bitstruct" 37 | 38 | [tool.setuptools.packages.find] 39 | where = ["src"] 40 | 41 | [tool.setuptools.dynamic] 42 | version = {attr = "bitstruct.__version__"} 43 | 44 | [tool.setuptools.package-data] 45 | "*" = [ 46 | "**/py.typed", 47 | "**.pyi", 48 | ] 49 | 50 | [tool.cibuildwheel] 51 | test-requires = "pytest" 52 | test-command = "pytest -v --assert=plain {project}/tests" 53 | build-frontend = "build" 54 | skip = "pp*" 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | codespell 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import platform 3 | 4 | import setuptools 5 | 6 | if platform.python_implementation() == "CPython": 7 | ext_modules = [ 8 | setuptools.Extension( 9 | "bitstruct.c", 10 | sources=[ 11 | "src/bitstruct/c.c", 12 | "src/bitstruct/bitstream.c", 13 | ], 14 | ) 15 | ] 16 | else: 17 | ext_modules = [] 18 | 19 | setuptools.setup( 20 | ext_modules=ext_modules, 21 | ) 22 | -------------------------------------------------------------------------------- /src/bitstruct/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '8.21.0' 2 | 3 | import binascii 4 | import re 5 | import struct 6 | from io import BytesIO 7 | 8 | 9 | class Error(Exception): 10 | pass 11 | 12 | 13 | class _Info: 14 | 15 | def __init__(self, size, name=None): 16 | self.size = size 17 | self.name = name 18 | self.endianness = None 19 | 20 | 21 | class _SignedInteger(_Info): 22 | 23 | def __init__(self, size, name): 24 | super().__init__(size, name) 25 | self.minimum = -2 ** (size - 1) 26 | self.maximum = -self.minimum - 1 27 | 28 | def pack(self, arg): 29 | value = int(arg) 30 | 31 | if value < self.minimum or value > self.maximum: 32 | raise Error( 33 | '"s{}" requires {} <= integer <= {} (got {})'.format( 34 | self.size, 35 | self.minimum, 36 | self.maximum, 37 | arg)) 38 | 39 | if value < 0: 40 | value += (1 << self.size) 41 | 42 | value += (1 << self.size) 43 | 44 | return bin(value)[3:] 45 | 46 | def unpack(self, bits): 47 | value = int(bits, 2) 48 | 49 | if bits[0] == '1': 50 | value -= (1 << len(bits)) 51 | 52 | return value 53 | 54 | 55 | class _UnsignedInteger(_Info): 56 | 57 | def __init__(self, size, name): 58 | super().__init__(size, name) 59 | self.maximum = 2 ** size - 1 60 | 61 | def pack(self, arg): 62 | value = int(arg) 63 | 64 | if value < 0 or value > self.maximum: 65 | raise Error( 66 | f'"u{self.size}" requires 0 <= integer <= {self.maximum} (got {arg})') 67 | 68 | return bin(value + (1 << self.size))[3:] 69 | 70 | def unpack(self, bits): 71 | return int(bits, 2) 72 | 73 | 74 | class _Boolean(_UnsignedInteger): 75 | 76 | def pack(self, arg): 77 | return super().pack(int(bool(arg))) 78 | 79 | def unpack(self, bits): 80 | return bool(super().unpack(bits)) 81 | 82 | 83 | class _Float(_Info): 84 | 85 | def pack(self, arg): 86 | value = float(arg) 87 | 88 | if self.size == 16: 89 | value = struct.pack('>e', value) 90 | elif self.size == 32: 91 | value = struct.pack('>f', value) 92 | elif self.size == 64: 93 | value = struct.pack('>d', value) 94 | else: 95 | raise Error(f'expected float size of 16, 32, or 64 bits (got {self.size})') 96 | 97 | return bin(int(b'01' + binascii.hexlify(value), 16))[3:] 98 | 99 | def unpack(self, bits): 100 | packed = _unpack_bytearray(self.size, bits) 101 | 102 | if self.size == 16: 103 | value = struct.unpack('>e', packed)[0] 104 | elif self.size == 32: 105 | value = struct.unpack('>f', packed)[0] 106 | elif self.size == 64: 107 | value = struct.unpack('>d', packed)[0] 108 | else: 109 | raise Error(f'expected float size of 16, 32, or 64 bits (got {self.size})') 110 | 111 | return value 112 | 113 | 114 | class _Raw(_Info): 115 | 116 | def pack(self, arg): 117 | number_of_padding_bytes = ((self.size - 8 * len(arg)) // 8) 118 | arg += (number_of_padding_bytes * b'\x00') 119 | 120 | return bin(int(b'01' + binascii.hexlify(arg), 16))[3:self.size + 3] 121 | 122 | def unpack(self, bits): 123 | rest = self.size % 8 124 | 125 | if rest > 0: 126 | bits += (8 - rest) * '0' 127 | 128 | return binascii.unhexlify(hex(int('10000000' + bits, 2))[4:].rstrip('L')) 129 | 130 | 131 | class _Padding(_Info): 132 | 133 | pass 134 | 135 | 136 | class _ZeroPadding(_Padding): 137 | 138 | def pack(self): 139 | return self.size * '0' 140 | 141 | 142 | class _OnePadding(_Padding): 143 | 144 | def pack(self): 145 | return self.size * '1' 146 | 147 | 148 | class _Text(_Info): 149 | 150 | def __init__(self, size, name, encoding, errors): 151 | super().__init__(size, name) 152 | self.encoding = encoding 153 | self.errors = errors 154 | 155 | def pack(self, arg): 156 | encoded = arg.encode('utf-8') 157 | number_of_padding_bytes = ((self.size - 8 * len(encoded)) // 8) 158 | encoded += (number_of_padding_bytes * b'\x00') 159 | 160 | return _pack_bytearray(self.size, encoded) 161 | 162 | def unpack(self, bits): 163 | return _unpack_bytearray(self.size, bits).decode(self.encoding, self.errors) 164 | 165 | 166 | def _parse_format(fmt, names, text_encoding, text_errors): 167 | if fmt and fmt[-1] in '><': 168 | byte_order = fmt[-1] 169 | fmt = fmt[:-1] 170 | else: 171 | byte_order = '' 172 | 173 | parsed_infos = re.findall(r'([<>]?)([a-zA-Z])(\d+)(\s*)', fmt) 174 | 175 | if ''.join([''.join(info) for info in parsed_infos]) != fmt: 176 | raise Error(f"bad format '{fmt + byte_order}'") 177 | 178 | # Use big endian as default and use the endianness of the previous 179 | # value if none is given for the current value. 180 | infos = [] 181 | endianness = ">" 182 | i = 0 183 | 184 | for parsed_info in parsed_infos: 185 | if parsed_info[0] != "": 186 | endianness = parsed_info[0] 187 | 188 | type_ = parsed_info[1] 189 | size = int(parsed_info[2]) 190 | 191 | if size == 0: 192 | raise Error(f"bad format '{fmt + byte_order}'") 193 | 194 | if names is None: 195 | name = i 196 | elif type_ not in 'pP': 197 | name = names[i] 198 | 199 | if type_ == 's': 200 | info = _SignedInteger(size, name) 201 | i += 1 202 | elif type_ == 'u': 203 | info = _UnsignedInteger(size, name) 204 | i += 1 205 | elif type_ == 'f': 206 | info = _Float(size, name) 207 | i += 1 208 | elif type_ == 'b': 209 | info = _Boolean(size, name) 210 | i += 1 211 | elif type_ == 't': 212 | info = _Text(size, name, text_encoding, text_errors) 213 | i += 1 214 | elif type_ == 'r': 215 | info = _Raw(size, name) 216 | i += 1 217 | elif type_ == 'p': 218 | info = _ZeroPadding(size) 219 | elif type_ == 'P': 220 | info = _OnePadding(size) 221 | else: 222 | raise Error(f"bad char '{type_}' in format") 223 | 224 | info.endianness = endianness 225 | 226 | infos.append(info) 227 | 228 | return infos, byte_order or '>' 229 | 230 | 231 | def _pack_bytearray(size, arg): 232 | return bin(int(b'01' + binascii.hexlify(arg), 16))[3:size + 3] 233 | 234 | 235 | def _unpack_bytearray(size, bits): 236 | rest = size % 8 237 | 238 | if rest > 0: 239 | bits += (8 - rest) * '0' 240 | 241 | return binascii.unhexlify(hex(int('10000000' + bits, 2))[4:].rstrip('L')) 242 | 243 | 244 | class _CompiledFormat: 245 | 246 | def __init__(self, 247 | fmt, 248 | names=None, 249 | text_encoding='utf-8', 250 | text_errors='strict'): 251 | infos, byte_order = _parse_format(fmt, names, text_encoding, text_errors) 252 | self._infos = infos 253 | self._byte_order = byte_order 254 | self._number_of_bits_to_unpack = sum([info.size for info in infos]) 255 | 256 | def pack_value(self, info, value, bits): 257 | value_bits = info.pack(value) 258 | 259 | # Reverse the bit order in little endian values. 260 | if info.endianness == "<": 261 | value_bits = value_bits[::-1] 262 | 263 | # Reverse bytes order for least significant byte first. 264 | if self._byte_order == ">" or isinstance(info, (_Raw, _Text)): 265 | bits += value_bits 266 | else: 267 | aligned_offset = len(value_bits) - (8 - (len(bits) % 8)) 268 | 269 | while aligned_offset > 0: 270 | bits += value_bits[aligned_offset:] 271 | value_bits = value_bits[:aligned_offset] 272 | aligned_offset -= 8 273 | 274 | bits += value_bits 275 | 276 | return bits 277 | 278 | def pack_any(self, values): 279 | bits = '' 280 | 281 | for info in self._infos: 282 | if isinstance(info, _Padding): 283 | bits += info.pack() 284 | else: 285 | bits = self.pack_value(info, values[info.name], bits) 286 | 287 | # Padding of last byte. 288 | tail = len(bits) % 8 289 | 290 | if tail != 0: 291 | bits += (8 - tail) * '0' 292 | 293 | return bytes(_unpack_bytearray(len(bits), bits)) 294 | 295 | def unpack_from_any(self, data, offset, allow_truncated): 296 | bits = bin(int(b'01' + binascii.hexlify(data), 16))[3 + offset:] 297 | 298 | # Sanity check. 299 | if not allow_truncated and self._number_of_bits_to_unpack > len(bits): 300 | raise Error( 301 | "unpack requires at least {} bits to unpack (got {})".format( 302 | self._number_of_bits_to_unpack, 303 | len(bits))) 304 | 305 | offset = 0 306 | 307 | for info in self._infos: 308 | if offset + info.size > len(bits): 309 | # Stop unpacking if we ran out of bytes to 310 | # unpack. Note that this condition will never trigger 311 | # if `allow_truncated` is not `True` because of the 312 | # sanity check above. 313 | return 314 | 315 | if isinstance(info, _Padding): 316 | pass 317 | else: 318 | # Reverse bytes order for least significant byte 319 | # first. 320 | if self._byte_order == ">" or isinstance(info, (_Raw, _Text)): 321 | value_bits = bits[offset:offset + info.size] 322 | else: 323 | value_bits_tmp = bits[offset:offset + info.size] 324 | aligned_offset = (info.size - ((offset + info.size) % 8)) 325 | value_bits = '' 326 | 327 | while aligned_offset > 0: 328 | value_bits += value_bits_tmp[aligned_offset:aligned_offset + 8] 329 | value_bits_tmp = value_bits_tmp[:aligned_offset] 330 | aligned_offset -= 8 331 | 332 | value_bits += value_bits_tmp 333 | 334 | # Reverse the bit order in little endian values. 335 | if info.endianness == "<": 336 | value_bits = value_bits[::-1] 337 | 338 | yield info, info.unpack(value_bits) 339 | 340 | offset += info.size 341 | 342 | def pack_into_any(self, buf, offset, data, **kwargs): 343 | fill_padding = kwargs.get('fill_padding', True) 344 | buf_bits = _pack_bytearray(8 * len(buf), buf) 345 | bits = buf_bits[0:offset] 346 | 347 | for info in self._infos: 348 | if isinstance(info, _Padding): 349 | if fill_padding: 350 | bits += info.pack() 351 | else: 352 | bits += buf_bits[len(bits):len(bits) + info.size] 353 | else: 354 | bits = self.pack_value(info, data[info.name], bits) 355 | 356 | bits += buf_bits[len(bits):] 357 | 358 | if len(bits) > len(buf_bits): 359 | raise Error( 360 | f'pack_into requires a buffer of at least {len(bits)} bits') 361 | 362 | buf[:] = _unpack_bytearray(len(bits), bits) 363 | 364 | def calcsize(self): 365 | """Return the number of bits in the compiled format string. 366 | 367 | """ 368 | 369 | return self._number_of_bits_to_unpack 370 | 371 | 372 | class CompiledFormat(_CompiledFormat): 373 | """A compiled format string that can be used to pack and/or unpack 374 | data multiple times. 375 | 376 | Instances of this class are created by the factory function 377 | :func:`~bitstruct.compile()`. 378 | 379 | """ 380 | 381 | def __init__(self, fmt, text_encoding='utf-8', text_errors='strict'): 382 | super().__init__(fmt, None, text_encoding, text_errors) 383 | self._number_of_arguments = 0 384 | 385 | for info in self._infos: 386 | if not isinstance(info, _Padding): 387 | self._number_of_arguments += 1 388 | 389 | def pack(self, *args): 390 | """See :func:`~bitstruct.pack()`. 391 | 392 | """ 393 | 394 | # Sanity check of the number of arguments. 395 | if len(args) < self._number_of_arguments: 396 | raise Error( 397 | "pack expected {} item(s) for packing (got {})".format( 398 | self._number_of_arguments, 399 | len(args))) 400 | 401 | return self.pack_any(args) 402 | 403 | def unpack(self, data, allow_truncated=False): 404 | """See :func:`~bitstruct.unpack()`. 405 | 406 | """ 407 | 408 | return self.unpack_from(data, allow_truncated=allow_truncated) 409 | 410 | def pack_into(self, buf, offset, *args, **kwargs): 411 | """See :func:`~bitstruct.pack_into()`. 412 | 413 | """ 414 | 415 | # Sanity check of the number of arguments. 416 | if len(args) < self._number_of_arguments: 417 | raise Error( 418 | "pack expected {} item(s) for packing (got {})".format( 419 | self._number_of_arguments, 420 | len(args))) 421 | 422 | self.pack_into_any(buf, offset, args, **kwargs) 423 | 424 | def unpack_from(self, data, offset=0, allow_truncated=False): 425 | """See :func:`~bitstruct.unpack_from()`. 426 | 427 | """ 428 | 429 | return tuple([v[1] for v in self.unpack_from_any( 430 | data, offset, allow_truncated=allow_truncated)]) 431 | 432 | class CompiledFormatDict(_CompiledFormat): 433 | """See :class:`~bitstruct.CompiledFormat`. 434 | 435 | """ 436 | 437 | def pack(self, data): 438 | """See :func:`~bitstruct.pack_dict()`. 439 | 440 | """ 441 | 442 | try: 443 | return self.pack_any(data) 444 | except KeyError as e: 445 | raise Error(f'{str(e)} not found in data dictionary') 446 | 447 | def unpack(self, data, allow_truncated=False): 448 | """See :func:`~bitstruct.unpack_dict()`. 449 | 450 | """ 451 | 452 | return self.unpack_from(data, allow_truncated=allow_truncated) 453 | 454 | def pack_into(self, buf, offset, data, **kwargs): 455 | """See :func:`~bitstruct.pack_into_dict()`. 456 | 457 | """ 458 | 459 | try: 460 | self.pack_into_any(buf, offset, data, **kwargs) 461 | except KeyError as e: 462 | raise Error(f'{str(e)} not found in data dictionary') 463 | 464 | def unpack_from(self, data, offset=0, allow_truncated=False): 465 | """See :func:`~bitstruct.unpack_from_dict()`. 466 | 467 | """ 468 | 469 | return { 470 | info.name: v for info, v in self.unpack_from_any( 471 | data, offset, allow_truncated=allow_truncated)} 472 | 473 | 474 | def pack(fmt, *args): 475 | """Return a bytes object containing the values v1, v2, ... packed 476 | according to given format string `fmt`. If the total number of 477 | bits are not a multiple of 8, padding will be added at the end of 478 | the last byte. 479 | 480 | `fmt` is a string of bit order-type-length groups, and optionally 481 | a byte order identifier after the groups. Bit Order and byte order 482 | may be omitted. 483 | 484 | Bit Order is either ``>`` or ``<``, where ``>`` means MSB first 485 | and ``<`` means LSB first. If bit order is omitted, the previous 486 | values' bit order is used for the current value. For example, in 487 | the format string ``'u1`` or ``<``, where ``>`` means most 491 | significant byte first and ``<`` means least significant byte 492 | first. If byte order is omitted, most significant byte first is 493 | used. 494 | 495 | There are eight types; ``u``, ``s``, ``f``, ``b``, ``t``, ``r``, 496 | ``p`` and ``P``. 497 | 498 | - ``u`` -- unsigned integer 499 | - ``s`` -- signed integer 500 | - ``f`` -- floating point number of 16, 32, or 64 bits 501 | - ``b`` -- boolean 502 | - ``t`` -- text (ascii or utf-8) 503 | - ``r`` -- raw, bytes 504 | - ``p`` -- padding with zeros, ignore 505 | - ``P`` -- padding with ones, ignore 506 | 507 | Length is the number of bits to pack the value into. 508 | 509 | Example format string with default bit and byte ordering: 510 | ``'u1u3p7s16'`` 511 | 512 | Same format string, but with least significant byte first: 513 | ``'u1u3p7s16<'`` 514 | 515 | Same format string, but with LSB first (``<`` prefix) and least 516 | significant byte first (``<`` suffix): ``'>> pack_dict('u4u4', ['foo', 'bar'], {'foo': 1, 'bar': 2}) 590 | b'\\x12' 591 | 592 | """ 593 | 594 | return CompiledFormatDict(fmt, names).pack(data) 595 | 596 | 597 | def unpack_dict(fmt, 598 | names, 599 | data, 600 | allow_truncated=False, 601 | text_encoding='utf-8', 602 | text_errors='strict'): 603 | """Same as :func:`~bitstruct.unpack()`, but returns a dictionary. 604 | 605 | See :func:`~bitstruct.pack_dict()` for details on `names`. 606 | 607 | >>> unpack_dict('u4u4', ['foo', 'bar'], b'\\x12') 608 | {'foo': 1, 'bar': 2} 609 | 610 | """ 611 | 612 | return CompiledFormatDict(fmt, names, text_encoding, text_errors).unpack( 613 | data, allow_truncated=allow_truncated) 614 | 615 | 616 | def pack_into_dict(fmt, names, buf, offset, data, **kwargs): 617 | """Same as :func:`~bitstruct.pack_into()`, but data is read from a 618 | dictionary. 619 | 620 | See :func:`~bitstruct.pack_dict()` for details on `names`. 621 | 622 | """ 623 | 624 | return CompiledFormatDict(fmt, names).pack_into(buf, 625 | offset, 626 | data, 627 | **kwargs) 628 | 629 | 630 | def unpack_from_dict(fmt, 631 | names, 632 | data, 633 | offset=0, 634 | allow_truncated=False, 635 | text_encoding='utf-8', 636 | text_errors='strict'): 637 | """Same as :func:`~bitstruct.unpack_from()`, but returns a 638 | dictionary. 639 | 640 | See :func:`~bitstruct.pack_dict()` for details on `names`. 641 | 642 | """ 643 | 644 | return CompiledFormatDict(fmt, names, text_encoding, text_errors).unpack_from( 645 | data, offset, allow_truncated=allow_truncated) 646 | 647 | 648 | def calcsize(fmt): 649 | """Return the number of bits in given format string `fmt`. 650 | 651 | >>> calcsize('u1s3p4') 652 | 8 653 | 654 | """ 655 | 656 | return CompiledFormat(fmt).calcsize() 657 | 658 | 659 | def byteswap(fmt, data, offset=0): 660 | """Swap bytes in `data` according to `fmt`, starting at byte `offset` 661 | and return the result. `fmt` must be an iterable, iterating over 662 | number of bytes to swap. For example, the format string ``'24'`` 663 | applied to the bytes ``b'\\x00\\x11\\x22\\x33\\x44\\x55'`` will 664 | produce the result ``b'\\x11\\x00\\x55\\x44\\x33\\x22'``. 665 | 666 | """ 667 | 668 | data = BytesIO(data) 669 | data.seek(offset) 670 | data_swapped = BytesIO() 671 | 672 | for f in fmt: 673 | swapped = data.read(int(f))[::-1] 674 | data_swapped.write(swapped) 675 | 676 | return data_swapped.getvalue() 677 | 678 | 679 | def compile(fmt, 680 | names=None, 681 | text_encoding='utf-8', 682 | text_errors='strict'): 683 | """Compile given format string `fmt` and return a compiled format 684 | object that can be used to pack and/or unpack data multiple times. 685 | 686 | Returns a :class:`~bitstruct.CompiledFormat` object if `names` is 687 | ``None``, and otherwise a :class:`~bitstruct.CompiledFormatDict` 688 | object. 689 | 690 | See :func:`~bitstruct.pack_dict()` for details on `names`. 691 | 692 | See :func:`~bitstruct.unpack()` for details on `text_encoding` and 693 | `text_errors`. 694 | 695 | """ 696 | 697 | if names is None: 698 | return CompiledFormat(fmt, text_encoding, text_errors) 699 | else: 700 | return CompiledFormatDict(fmt, names, text_encoding, text_errors) 701 | -------------------------------------------------------------------------------- /src/bitstruct/bitstream.c: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Erik Moqvist 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation 8 | * files (the "Software"), to deal in the Software without 9 | * restriction, including without limitation the rights to use, copy, 10 | * modify, merge, publish, distribute, sublicense, and/or sell copies 11 | * of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include "bitstream.h" 29 | 30 | void bitstream_writer_init(struct bitstream_writer_t *self_p, 31 | uint8_t *buf_p) 32 | { 33 | self_p->buf_p = buf_p; 34 | self_p->byte_offset = 0; 35 | self_p->bit_offset = 0; 36 | } 37 | 38 | int bitstream_writer_size_in_bits(struct bitstream_writer_t *self_p) 39 | { 40 | return (8 * self_p->byte_offset + self_p->bit_offset); 41 | } 42 | 43 | int bitstream_writer_size_in_bytes(struct bitstream_writer_t *self_p) 44 | { 45 | return (self_p->byte_offset + (self_p->bit_offset + 7) / 8); 46 | } 47 | 48 | void bitstream_writer_write_bit(struct bitstream_writer_t *self_p, 49 | int value) 50 | { 51 | if (self_p->bit_offset == 0) { 52 | self_p->buf_p[self_p->byte_offset] = (value << 7); 53 | self_p->bit_offset = 1; 54 | } else { 55 | self_p->buf_p[self_p->byte_offset] |= (value << (8 - self_p->bit_offset - 1)); 56 | 57 | if (self_p->bit_offset == 7) { 58 | self_p->bit_offset = 0; 59 | self_p->byte_offset++; 60 | } else { 61 | self_p->bit_offset++; 62 | } 63 | } 64 | } 65 | 66 | void bitstream_writer_write_bytes(struct bitstream_writer_t *self_p, 67 | const uint8_t *buf_p, 68 | int length) 69 | { 70 | int i; 71 | uint8_t *dst_p; 72 | 73 | dst_p = &self_p->buf_p[self_p->byte_offset]; 74 | 75 | if (self_p->bit_offset == 0) { 76 | memcpy(dst_p, buf_p, sizeof(uint8_t) * length); 77 | } else { 78 | for (i = 0; i < length; i++) { 79 | dst_p[i] |= (buf_p[i] >> self_p->bit_offset); 80 | dst_p[i + 1] = (uint8_t)(buf_p[i] << (8 - self_p->bit_offset)); 81 | } 82 | } 83 | 84 | self_p->byte_offset += length; 85 | } 86 | 87 | void bitstream_writer_write_u8(struct bitstream_writer_t *self_p, 88 | uint8_t value) 89 | { 90 | if (self_p->bit_offset == 0) { 91 | self_p->buf_p[self_p->byte_offset] = value; 92 | } else { 93 | self_p->buf_p[self_p->byte_offset] |= (value >> self_p->bit_offset); 94 | self_p->buf_p[self_p->byte_offset + 1] = 95 | (uint8_t)(value << (8 - self_p->bit_offset)); 96 | } 97 | 98 | self_p->byte_offset++; 99 | } 100 | 101 | void bitstream_writer_write_u16(struct bitstream_writer_t *self_p, 102 | uint16_t value) 103 | { 104 | if (self_p->bit_offset == 0) { 105 | self_p->buf_p[self_p->byte_offset] = (value >> 8); 106 | } else { 107 | self_p->buf_p[self_p->byte_offset] |= (value >> (8 + self_p->bit_offset)); 108 | self_p->buf_p[self_p->byte_offset + 2] = 109 | (uint8_t)(value << (8 - self_p->bit_offset)); 110 | value >>= self_p->bit_offset; 111 | } 112 | 113 | self_p->buf_p[self_p->byte_offset + 1] = (uint8_t)value; 114 | self_p->byte_offset += 2; 115 | } 116 | 117 | void bitstream_writer_write_u32(struct bitstream_writer_t *self_p, 118 | uint32_t value) 119 | { 120 | int i; 121 | 122 | if (self_p->bit_offset == 0) { 123 | self_p->buf_p[self_p->byte_offset] = (value >> 24); 124 | } else { 125 | self_p->buf_p[self_p->byte_offset] |= (value >> (24 + self_p->bit_offset)); 126 | self_p->buf_p[self_p->byte_offset + 4] = 127 | (uint8_t)(value << (8 - self_p->bit_offset)); 128 | value >>= self_p->bit_offset; 129 | } 130 | 131 | for (i = 3; i > 0; i--) { 132 | self_p->buf_p[self_p->byte_offset + i] = value; 133 | value >>= 8; 134 | } 135 | 136 | self_p->byte_offset += 4; 137 | } 138 | 139 | void bitstream_writer_write_u64(struct bitstream_writer_t *self_p, 140 | uint64_t value) 141 | { 142 | int i; 143 | 144 | 145 | if (self_p->bit_offset == 0) { 146 | self_p->buf_p[self_p->byte_offset] = (value >> 56); 147 | } else { 148 | self_p->buf_p[self_p->byte_offset] |= (value >> (56 + self_p->bit_offset)); 149 | self_p->buf_p[self_p->byte_offset + 8] = 150 | (uint8_t)(value << (8 - self_p->bit_offset)); 151 | value >>= self_p->bit_offset; 152 | } 153 | 154 | for (i = 7; i > 0; i--) { 155 | self_p->buf_p[self_p->byte_offset + i] = (uint8_t)value; 156 | value >>= 8; 157 | } 158 | 159 | self_p->byte_offset += 8; 160 | } 161 | 162 | void bitstream_writer_write_u64_bits(struct bitstream_writer_t *self_p, 163 | uint64_t value, 164 | int number_of_bits) 165 | { 166 | int i; 167 | int first_byte_bits; 168 | int last_byte_bits; 169 | int full_bytes; 170 | 171 | if (number_of_bits == 0) { 172 | return; 173 | } 174 | 175 | /* Align beginning. */ 176 | first_byte_bits = (8 - self_p->bit_offset); 177 | 178 | if (first_byte_bits != 8) { 179 | if (number_of_bits < first_byte_bits) { 180 | self_p->buf_p[self_p->byte_offset] |= 181 | (uint8_t)(value << (first_byte_bits - number_of_bits)); 182 | self_p->bit_offset += number_of_bits; 183 | } else { 184 | self_p->buf_p[self_p->byte_offset] |= (value >> (number_of_bits 185 | - first_byte_bits)); 186 | self_p->byte_offset++; 187 | self_p->bit_offset = 0; 188 | } 189 | 190 | number_of_bits -= first_byte_bits; 191 | 192 | if (number_of_bits <= 0) { 193 | return; 194 | } 195 | } 196 | 197 | /* Align end. */ 198 | last_byte_bits = (number_of_bits % 8); 199 | full_bytes = (number_of_bits / 8); 200 | 201 | if (last_byte_bits != 0) { 202 | self_p->buf_p[self_p->byte_offset + full_bytes] = 203 | (uint8_t)(value << (8 - last_byte_bits)); 204 | value >>= last_byte_bits; 205 | self_p->bit_offset = last_byte_bits; 206 | } 207 | 208 | /* Copy middle bytes. */ 209 | for (i = full_bytes; i > 0; i--) { 210 | self_p->buf_p[self_p->byte_offset + i - 1] = (uint8_t)value; 211 | value >>= 8; 212 | } 213 | 214 | self_p->byte_offset += full_bytes; 215 | } 216 | 217 | void bitstream_writer_write_repeated_bit(struct bitstream_writer_t *self_p, 218 | int value, 219 | int length) 220 | { 221 | int rest; 222 | 223 | if (value != 0) { 224 | value = 0xff; 225 | } 226 | 227 | rest = (length % 8); 228 | bitstream_writer_write_u64_bits(self_p, value & ((1 << rest) - 1), rest); 229 | bitstream_writer_write_repeated_u8(self_p, value, length / 8); 230 | } 231 | 232 | void bitstream_writer_write_repeated_u8(struct bitstream_writer_t *self_p, 233 | uint8_t value, 234 | int length) 235 | { 236 | int i; 237 | 238 | for (i = 0; i < length; i++) { 239 | bitstream_writer_write_u8(self_p, value); 240 | } 241 | } 242 | 243 | void bitstream_writer_insert_bit(struct bitstream_writer_t *self_p, 244 | int value) 245 | { 246 | struct bitstream_writer_bounds_t bounds; 247 | 248 | bitstream_writer_bounds_save(&bounds, 249 | self_p, 250 | (8 * self_p->byte_offset) + self_p->bit_offset, 251 | 1); 252 | bitstream_writer_write_bit(self_p, value); 253 | bitstream_writer_bounds_restore(&bounds); 254 | } 255 | 256 | void bitstream_writer_insert_bytes(struct bitstream_writer_t *self_p, 257 | const uint8_t *buf_p, 258 | int length) 259 | { 260 | struct bitstream_writer_bounds_t bounds; 261 | 262 | bitstream_writer_bounds_save(&bounds, 263 | self_p, 264 | (8 * self_p->byte_offset) + self_p->bit_offset, 265 | 8 * length); 266 | bitstream_writer_write_bytes(self_p, buf_p, length); 267 | bitstream_writer_bounds_restore(&bounds); 268 | } 269 | 270 | void bitstream_writer_insert_u8(struct bitstream_writer_t *self_p, 271 | uint8_t value) 272 | { 273 | struct bitstream_writer_bounds_t bounds; 274 | 275 | bitstream_writer_bounds_save(&bounds, 276 | self_p, 277 | (8 * self_p->byte_offset) + self_p->bit_offset, 278 | 8); 279 | bitstream_writer_write_u8(self_p, value); 280 | bitstream_writer_bounds_restore(&bounds); 281 | } 282 | 283 | void bitstream_writer_insert_u16(struct bitstream_writer_t *self_p, 284 | uint16_t value) 285 | { 286 | struct bitstream_writer_bounds_t bounds; 287 | 288 | bitstream_writer_bounds_save(&bounds, 289 | self_p, 290 | (8 * self_p->byte_offset) + self_p->bit_offset, 291 | 16); 292 | bitstream_writer_write_u16(self_p, value); 293 | bitstream_writer_bounds_restore(&bounds); 294 | } 295 | 296 | void bitstream_writer_insert_u32(struct bitstream_writer_t *self_p, 297 | uint32_t value) 298 | { 299 | struct bitstream_writer_bounds_t bounds; 300 | 301 | bitstream_writer_bounds_save(&bounds, 302 | self_p, 303 | (8 * self_p->byte_offset) + self_p->bit_offset, 304 | 32); 305 | bitstream_writer_write_u32(self_p, value); 306 | bitstream_writer_bounds_restore(&bounds); 307 | } 308 | 309 | void bitstream_writer_insert_u64(struct bitstream_writer_t *self_p, 310 | uint64_t value) 311 | { 312 | struct bitstream_writer_bounds_t bounds; 313 | 314 | bitstream_writer_bounds_save(&bounds, 315 | self_p, 316 | (8 * self_p->byte_offset) + self_p->bit_offset, 317 | 64); 318 | bitstream_writer_write_u64(self_p, value); 319 | bitstream_writer_bounds_restore(&bounds); 320 | } 321 | 322 | void bitstream_writer_insert_u64_bits(struct bitstream_writer_t *self_p, 323 | uint64_t value, 324 | int number_of_bits) 325 | { 326 | struct bitstream_writer_bounds_t bounds; 327 | 328 | bitstream_writer_bounds_save(&bounds, 329 | self_p, 330 | (8 * self_p->byte_offset) + self_p->bit_offset, 331 | number_of_bits); 332 | bitstream_writer_write_u64_bits(self_p, value, number_of_bits); 333 | bitstream_writer_bounds_restore(&bounds); 334 | } 335 | 336 | void bitstream_writer_seek(struct bitstream_writer_t *self_p, 337 | int offset) 338 | { 339 | offset += ((8 * self_p->byte_offset) + self_p->bit_offset); 340 | self_p->byte_offset = (offset / 8); 341 | self_p->bit_offset = (offset % 8); 342 | } 343 | 344 | void bitstream_writer_bounds_save(struct bitstream_writer_bounds_t *self_p, 345 | struct bitstream_writer_t *writer_p, 346 | int bit_offset, 347 | int length) 348 | { 349 | int number_of_bits; 350 | 351 | self_p->writer_p = writer_p; 352 | number_of_bits = (bit_offset % 8); 353 | 354 | if (number_of_bits == 0) { 355 | self_p->first_byte_offset = -1; 356 | } else { 357 | self_p->first_byte_offset = (bit_offset / 8); 358 | self_p->first_byte = writer_p->buf_p[self_p->first_byte_offset]; 359 | self_p->first_byte &= (0xff00 >> number_of_bits); 360 | } 361 | 362 | number_of_bits = ((bit_offset + length) % 8); 363 | 364 | if (number_of_bits == 0) { 365 | self_p->last_byte_offset = -1; 366 | } else { 367 | self_p->last_byte_offset = ((bit_offset + length) / 8); 368 | self_p->last_byte = writer_p->buf_p[self_p->last_byte_offset]; 369 | self_p->last_byte &= ~(0xff00 >> number_of_bits); 370 | writer_p->buf_p[self_p->last_byte_offset] = 0; 371 | } 372 | 373 | if (self_p->first_byte_offset != -1) { 374 | writer_p->buf_p[self_p->first_byte_offset] = 0; 375 | } 376 | } 377 | 378 | void bitstream_writer_bounds_restore(struct bitstream_writer_bounds_t *self_p) 379 | { 380 | if (self_p->first_byte_offset != -1) { 381 | self_p->writer_p->buf_p[self_p->first_byte_offset] |= self_p->first_byte; 382 | } 383 | 384 | if (self_p->last_byte_offset != -1) { 385 | self_p->writer_p->buf_p[self_p->last_byte_offset] |= self_p->last_byte; 386 | } 387 | } 388 | 389 | void bitstream_reader_init(struct bitstream_reader_t *self_p, 390 | const uint8_t *buf_p) 391 | { 392 | self_p->buf_p = buf_p; 393 | self_p->byte_offset = 0; 394 | self_p->bit_offset = 0; 395 | } 396 | 397 | int bitstream_reader_read_bit(struct bitstream_reader_t *self_p) 398 | { 399 | int value; 400 | 401 | if (self_p->bit_offset == 0) { 402 | value = (self_p->buf_p[self_p->byte_offset] >> 7); 403 | self_p->bit_offset = 1; 404 | } else { 405 | value = ((self_p->buf_p[self_p->byte_offset] >> (7 - self_p->bit_offset)) & 0x1); 406 | 407 | if (self_p->bit_offset == 7) { 408 | self_p->bit_offset = 0; 409 | self_p->byte_offset++; 410 | } else { 411 | self_p->bit_offset++; 412 | } 413 | } 414 | 415 | return (value); 416 | } 417 | 418 | void bitstream_reader_read_bytes(struct bitstream_reader_t *self_p, 419 | uint8_t *buf_p, 420 | int length) 421 | { 422 | int i; 423 | const uint8_t *src_p; 424 | 425 | src_p = &self_p->buf_p[self_p->byte_offset]; 426 | 427 | if (self_p->bit_offset == 0) { 428 | memcpy(buf_p, src_p, sizeof(uint8_t) * length); 429 | } else { 430 | for (i = 0; i < length; i++) { 431 | buf_p[i] = (src_p[i] << self_p->bit_offset); 432 | buf_p[i] |= (src_p[i + 1] >> (8 - self_p->bit_offset)); 433 | } 434 | } 435 | 436 | self_p->byte_offset += length; 437 | } 438 | 439 | uint8_t bitstream_reader_read_u8(struct bitstream_reader_t *self_p) 440 | { 441 | uint8_t value; 442 | 443 | value = (self_p->buf_p[self_p->byte_offset] << self_p->bit_offset); 444 | self_p->byte_offset++; 445 | 446 | if (self_p->bit_offset != 0) { 447 | value |= (self_p->buf_p[self_p->byte_offset] >> (8 - self_p->bit_offset)); 448 | } 449 | 450 | return (value); 451 | } 452 | 453 | uint16_t bitstream_reader_read_u16(struct bitstream_reader_t *self_p) 454 | { 455 | uint16_t value; 456 | int i; 457 | int offset; 458 | const uint8_t *src_p; 459 | 460 | src_p = &self_p->buf_p[self_p->byte_offset]; 461 | offset = (16 + self_p->bit_offset); 462 | value = 0; 463 | 464 | for (i = 0; i < 2; i++) { 465 | offset -= 8; 466 | value |= ((uint16_t)src_p[i] << offset); 467 | } 468 | 469 | if (offset != 0) { 470 | value |= (src_p[2] >> (8 - offset)); 471 | } 472 | 473 | self_p->byte_offset += 2; 474 | 475 | return (value); 476 | } 477 | 478 | uint32_t bitstream_reader_read_u32(struct bitstream_reader_t *self_p) 479 | { 480 | uint32_t value; 481 | int i; 482 | int offset; 483 | const uint8_t *src_p; 484 | 485 | src_p = &self_p->buf_p[self_p->byte_offset]; 486 | offset = (32 + self_p->bit_offset); 487 | value = 0; 488 | 489 | for (i = 0; i < 4; i++) { 490 | offset -= 8; 491 | value |= ((uint32_t)src_p[i] << offset); 492 | } 493 | 494 | if (offset != 0) { 495 | value |= (src_p[4] >> (8 - offset)); 496 | } 497 | 498 | self_p->byte_offset += 4; 499 | 500 | return (value); 501 | } 502 | 503 | uint64_t bitstream_reader_read_u64(struct bitstream_reader_t *self_p) 504 | { 505 | uint64_t value; 506 | int i; 507 | int offset; 508 | const uint8_t *src_p; 509 | 510 | src_p = &self_p->buf_p[self_p->byte_offset]; 511 | offset = (64 + self_p->bit_offset); 512 | value = 0; 513 | 514 | for (i = 0; i < 8; i++) { 515 | offset -= 8; 516 | value |= ((uint64_t)src_p[i] << offset); 517 | } 518 | 519 | if (offset != 0) { 520 | value |= ((uint64_t)src_p[8] >> (8 - offset)); 521 | } 522 | 523 | self_p->byte_offset += 8; 524 | 525 | return (value); 526 | } 527 | 528 | uint64_t bitstream_reader_read_u64_bits(struct bitstream_reader_t *self_p, 529 | int number_of_bits) 530 | { 531 | uint64_t value; 532 | int i; 533 | int first_byte_bits; 534 | int last_byte_bits; 535 | int full_bytes; 536 | 537 | if (number_of_bits == 0) { 538 | return (0); 539 | } 540 | 541 | /* Align beginning. */ 542 | first_byte_bits = (8 - self_p->bit_offset); 543 | 544 | if (first_byte_bits != 8) { 545 | if (number_of_bits < first_byte_bits) { 546 | value = (self_p->buf_p[self_p->byte_offset] >> (first_byte_bits 547 | - number_of_bits)); 548 | value &= ((1 << number_of_bits) - 1); 549 | self_p->bit_offset += number_of_bits; 550 | } else { 551 | value = self_p->buf_p[self_p->byte_offset]; 552 | value &= ((1 << first_byte_bits) - 1); 553 | self_p->byte_offset++; 554 | self_p->bit_offset = 0; 555 | } 556 | 557 | number_of_bits -= first_byte_bits; 558 | 559 | if (number_of_bits <= 0) { 560 | return (value); 561 | } 562 | } else { 563 | value = 0; 564 | } 565 | 566 | /* Copy middle bytes. */ 567 | full_bytes = (number_of_bits / 8); 568 | 569 | for (i = 0; i < full_bytes; i++) { 570 | value <<= 8; 571 | value |= self_p->buf_p[self_p->byte_offset + i]; 572 | } 573 | 574 | /* Last byte. */ 575 | last_byte_bits = (number_of_bits % 8); 576 | 577 | if (last_byte_bits != 0) { 578 | value <<= last_byte_bits; 579 | value |= (self_p->buf_p[self_p->byte_offset + full_bytes] 580 | >> (8 - last_byte_bits)); 581 | self_p->bit_offset = last_byte_bits; 582 | } 583 | 584 | self_p->byte_offset += full_bytes; 585 | 586 | return (value); 587 | } 588 | 589 | void bitstream_reader_seek(struct bitstream_reader_t *self_p, 590 | int offset) 591 | { 592 | offset += ((8 * self_p->byte_offset) + self_p->bit_offset); 593 | self_p->byte_offset = (offset / 8); 594 | self_p->bit_offset = (offset % 8); 595 | } 596 | 597 | int bitstream_reader_tell(struct bitstream_reader_t *self_p) 598 | { 599 | return ((8 * self_p->byte_offset) + self_p->bit_offset); 600 | } 601 | -------------------------------------------------------------------------------- /src/bitstruct/bitstream.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Erik Moqvist 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation 8 | * files (the "Software"), to deal in the Software without 9 | * restriction, including without limitation the rights to use, copy, 10 | * modify, merge, publish, distribute, sublicense, and/or sell copies 11 | * of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | #ifndef BITSTREAM_H 28 | #define BITSTREAM_H 29 | 30 | #include 31 | 32 | #define BITSTREAM_VERSION "0.8.0" 33 | 34 | struct bitstream_writer_t { 35 | uint8_t *buf_p; 36 | int byte_offset; 37 | int bit_offset; 38 | }; 39 | 40 | struct bitstream_writer_bounds_t { 41 | struct bitstream_writer_t *writer_p; 42 | int first_byte_offset; 43 | uint8_t first_byte; 44 | int last_byte_offset; 45 | uint8_t last_byte; 46 | }; 47 | 48 | struct bitstream_reader_t { 49 | const uint8_t *buf_p; 50 | int byte_offset; 51 | int bit_offset; 52 | }; 53 | 54 | /* 55 | * The writer. 56 | */ 57 | 58 | void bitstream_writer_init(struct bitstream_writer_t *self_p, 59 | uint8_t *buf_p); 60 | 61 | int bitstream_writer_size_in_bits(struct bitstream_writer_t *self_p); 62 | 63 | int bitstream_writer_size_in_bytes(struct bitstream_writer_t *self_p); 64 | 65 | /* Write bits to the stream. Clears each byte before bits are 66 | written. */ 67 | void bitstream_writer_write_bit(struct bitstream_writer_t *self_p, 68 | int value); 69 | 70 | void bitstream_writer_write_bytes(struct bitstream_writer_t *self_p, 71 | const uint8_t *buf_p, 72 | int length); 73 | 74 | void bitstream_writer_write_u8(struct bitstream_writer_t *self_p, 75 | uint8_t value); 76 | 77 | void bitstream_writer_write_u16(struct bitstream_writer_t *self_p, 78 | uint16_t value); 79 | 80 | void bitstream_writer_write_u32(struct bitstream_writer_t *self_p, 81 | uint32_t value); 82 | 83 | void bitstream_writer_write_u64(struct bitstream_writer_t *self_p, 84 | uint64_t value); 85 | 86 | /* Upper unused bits must be zero. */ 87 | void bitstream_writer_write_u64_bits(struct bitstream_writer_t *self_p, 88 | uint64_t value, 89 | int number_of_bits); 90 | 91 | void bitstream_writer_write_repeated_bit(struct bitstream_writer_t *self_p, 92 | int value, 93 | int length); 94 | 95 | void bitstream_writer_write_repeated_u8(struct bitstream_writer_t *self_p, 96 | uint8_t value, 97 | int length); 98 | 99 | /* Insert bits into the stream. Leaves all other bits unmodified. */ 100 | void bitstream_writer_insert_bit(struct bitstream_writer_t *self_p, 101 | int value); 102 | 103 | void bitstream_writer_insert_bytes(struct bitstream_writer_t *self_p, 104 | const uint8_t *buf_p, 105 | int length); 106 | 107 | void bitstream_writer_insert_u8(struct bitstream_writer_t *self_p, 108 | uint8_t value); 109 | 110 | void bitstream_writer_insert_u16(struct bitstream_writer_t *self_p, 111 | uint16_t value); 112 | 113 | void bitstream_writer_insert_u32(struct bitstream_writer_t *self_p, 114 | uint32_t value); 115 | 116 | void bitstream_writer_insert_u64(struct bitstream_writer_t *self_p, 117 | uint64_t value); 118 | 119 | void bitstream_writer_insert_u64_bits(struct bitstream_writer_t *self_p, 120 | uint64_t value, 121 | int number_of_bits); 122 | 123 | /* Move write position. Seeking backwards makes the written size 124 | smaller. Use write with care after seek, as seek does not clear 125 | bytes. */ 126 | void bitstream_writer_seek(struct bitstream_writer_t *self_p, 127 | int offset); 128 | 129 | /* Save-restore first and last bytes in given range, so write can be 130 | used in given range. */ 131 | void bitstream_writer_bounds_save(struct bitstream_writer_bounds_t *self_p, 132 | struct bitstream_writer_t *writer_p, 133 | int bit_offset, 134 | int length); 135 | 136 | void bitstream_writer_bounds_restore(struct bitstream_writer_bounds_t *self_p); 137 | 138 | /* 139 | * The reader. 140 | */ 141 | 142 | void bitstream_reader_init(struct bitstream_reader_t *self_p, 143 | const uint8_t *buf_p); 144 | 145 | /* Read bits from the stream. */ 146 | int bitstream_reader_read_bit(struct bitstream_reader_t *self_p); 147 | 148 | void bitstream_reader_read_bytes(struct bitstream_reader_t *self_p, 149 | uint8_t *buf_p, 150 | int length); 151 | 152 | uint8_t bitstream_reader_read_u8(struct bitstream_reader_t *self_p); 153 | 154 | uint16_t bitstream_reader_read_u16(struct bitstream_reader_t *self_p); 155 | 156 | uint32_t bitstream_reader_read_u32(struct bitstream_reader_t *self_p); 157 | 158 | uint64_t bitstream_reader_read_u64(struct bitstream_reader_t *self_p); 159 | 160 | uint64_t bitstream_reader_read_u64_bits(struct bitstream_reader_t *self_p, 161 | int number_of_bits); 162 | 163 | /* Move read position. */ 164 | void bitstream_reader_seek(struct bitstream_reader_t *self_p, 165 | int offset); 166 | 167 | /* Get read position. */ 168 | int bitstream_reader_tell(struct bitstream_reader_t *self_p); 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /src/bitstruct/c.c: -------------------------------------------------------------------------------- 1 | /** 2 | * CPython 3 C extension. 3 | */ 4 | 5 | #include 6 | #include 7 | #include "bitstream.h" 8 | 9 | #include 10 | 11 | struct field_info_t; 12 | 13 | typedef void (*pack_field_t)(struct bitstream_writer_t *self_p, 14 | PyObject *value_p, 15 | struct field_info_t *field_info_p); 16 | 17 | typedef PyObject *(*unpack_field_t)(struct bitstream_reader_t *self_p, 18 | struct field_info_t *field_info_p); 19 | 20 | struct field_info_t { 21 | pack_field_t pack; 22 | unpack_field_t unpack; 23 | int number_of_bits; 24 | bool is_padding; 25 | union { 26 | struct { 27 | int64_t lower; 28 | int64_t upper; 29 | } s; 30 | struct { 31 | uint64_t upper; 32 | } u; 33 | } limits; 34 | }; 35 | 36 | struct info_t { 37 | int number_of_bits; 38 | int number_of_fields; 39 | int number_of_non_padding_fields; 40 | struct field_info_t fields[1]; 41 | }; 42 | 43 | struct compiled_format_t { 44 | PyObject_HEAD 45 | struct info_t *info_p; 46 | PyObject *format_p; 47 | }; 48 | 49 | struct compiled_format_dict_t { 50 | PyObject_HEAD 51 | struct info_t *info_p; 52 | PyObject *format_p; 53 | PyObject *names_p; 54 | }; 55 | 56 | static const char* pickle_version_key = "_pickle_version"; 57 | static int pickle_version = 1; 58 | 59 | static PyObject *compiled_format_new(PyTypeObject *type_p, 60 | PyObject *args_p, 61 | PyObject *kwargs_p); 62 | 63 | static int compiled_format_init(struct compiled_format_t *self_p, 64 | PyObject *args_p, 65 | PyObject *kwargs_p); 66 | 67 | static int compiled_format_init_inner(struct compiled_format_t *self_p, 68 | PyObject *format_p); 69 | 70 | static void compiled_format_dealloc(struct compiled_format_t *self_p); 71 | 72 | static PyObject *m_compiled_format_pack(struct compiled_format_t *self_p, 73 | PyObject *args_p); 74 | 75 | static PyObject *m_compiled_format_unpack(struct compiled_format_t *self_p, 76 | PyObject *args_p, 77 | PyObject *kwargs_p); 78 | 79 | static PyObject *m_compiled_format_pack_into(struct compiled_format_t *self_p, 80 | PyObject *args_p, 81 | PyObject *kwargs_p); 82 | 83 | static PyObject *m_compiled_format_unpack_from(struct compiled_format_t *self_p, 84 | PyObject *args_p, 85 | PyObject *kwargs_p); 86 | 87 | static PyObject *m_compiled_format_calcsize(struct compiled_format_t *self_p); 88 | 89 | static PyObject *m_compiled_format_copy(struct compiled_format_t *self_p); 90 | 91 | static PyObject *m_compiled_format_deepcopy(struct compiled_format_t *self_p, 92 | PyObject *args_p); 93 | 94 | static PyObject *m_compiled_format_getstate(struct compiled_format_t *self_p, 95 | PyObject *args_p); 96 | 97 | static PyObject *m_compiled_format_setstate(struct compiled_format_t *self_p, 98 | PyObject *args_p); 99 | 100 | static PyObject *compiled_format_dict_new(PyTypeObject *type_p, 101 | PyObject *args_p, 102 | PyObject *kwargs_p); 103 | 104 | static int compiled_format_dict_init(struct compiled_format_dict_t *self_p, 105 | PyObject *args_p, 106 | PyObject *kwargs_p); 107 | 108 | static int compiled_format_dict_init_inner(struct compiled_format_dict_t *self_p, 109 | PyObject *format_p, 110 | PyObject *names_p); 111 | 112 | static void compiled_format_dict_dealloc(struct compiled_format_dict_t *self_p); 113 | 114 | static PyObject *m_compiled_format_dict_pack(struct compiled_format_dict_t *self_p, 115 | PyObject *data_p); 116 | 117 | static PyObject *m_compiled_format_dict_unpack( 118 | struct compiled_format_dict_t *self_p, 119 | PyObject *args_p, 120 | PyObject *kwargs_p); 121 | 122 | static PyObject *m_compiled_format_dict_pack_into( 123 | struct compiled_format_dict_t *self_p, 124 | PyObject *args_p, 125 | PyObject *kwargs_p); 126 | 127 | static PyObject *m_compiled_format_dict_unpack_from( 128 | struct compiled_format_dict_t *self_p, 129 | PyObject *args_p, 130 | PyObject *kwargs_p); 131 | 132 | static PyObject *m_compiled_format_dict_calcsize( 133 | struct compiled_format_dict_t *self_p); 134 | 135 | static PyObject *m_compiled_format_dict_copy( 136 | struct compiled_format_dict_t *self_p); 137 | 138 | static PyObject *m_compiled_format_dict_deepcopy( 139 | struct compiled_format_dict_t *self_p, 140 | PyObject *args_p); 141 | 142 | static PyObject *m_compiled_format_dict_getstate(struct compiled_format_dict_t *self_p, 143 | PyObject *args_p); 144 | 145 | static PyObject *m_compiled_format_dict_setstate(struct compiled_format_dict_t *self_p, 146 | PyObject *args_p); 147 | 148 | PyDoc_STRVAR(pack___doc__, 149 | "pack(fmt, *args)\n" 150 | "--\n" 151 | "\n"); 152 | PyDoc_STRVAR(compiled_format_pack___doc__, 153 | "pack(*args)\n" 154 | "--\n" 155 | "\n"); 156 | 157 | PyDoc_STRVAR(unpack___doc__, 158 | "unpack(fmt, data, allow_truncated=False)\n" 159 | "--\n" 160 | "\n"); 161 | PyDoc_STRVAR(compiled_format_unpack___doc__, 162 | "unpack(data, allow_truncated=False)\n" 163 | "--\n" 164 | "\n"); 165 | 166 | PyDoc_STRVAR(pack_into___doc__, 167 | "pack_into(fmt, buf, offset, *args, **kwargs)\n" 168 | "--\n" 169 | "\n"); 170 | PyDoc_STRVAR(compiled_format_pack_into___doc__, 171 | "pack_into(buf, offset, *args, **kwargs)\n" 172 | "--\n" 173 | "\n"); 174 | 175 | PyDoc_STRVAR(unpack_from___doc__, 176 | "unpack_from(fmt, data, offset=0, allow_truncated=False)\n" 177 | "--\n" 178 | "\n"); 179 | PyDoc_STRVAR(compiled_format_unpack_from___doc__, 180 | "unpack_from(data, offset=0, allow_truncated=False)\n" 181 | "--\n" 182 | "\n"); 183 | 184 | PyDoc_STRVAR(calcsize___doc__, 185 | "calcsize(fmt)\n" 186 | "--\n" 187 | "\n"); 188 | PyDoc_STRVAR(compiled_format_calcsize___doc__, 189 | "calcsize()\n" 190 | "--\n" 191 | "\n"); 192 | 193 | static PyObject *py_zero_p = NULL; 194 | 195 | static struct PyMethodDef compiled_format_methods[] = { 196 | { 197 | "pack", 198 | (PyCFunction)m_compiled_format_pack, 199 | METH_VARARGS, 200 | compiled_format_pack___doc__ 201 | }, 202 | { 203 | "unpack", 204 | (PyCFunction)m_compiled_format_unpack, 205 | METH_VARARGS | METH_KEYWORDS, 206 | compiled_format_unpack___doc__ 207 | }, 208 | { 209 | "pack_into", 210 | (PyCFunction)m_compiled_format_pack_into, 211 | METH_VARARGS | METH_KEYWORDS, 212 | compiled_format_pack_into___doc__ 213 | }, 214 | { 215 | "unpack_from", 216 | (PyCFunction)m_compiled_format_unpack_from, 217 | METH_VARARGS | METH_KEYWORDS, 218 | compiled_format_unpack_from___doc__ 219 | }, 220 | { 221 | "calcsize", 222 | (PyCFunction)m_compiled_format_calcsize, 223 | METH_NOARGS, 224 | compiled_format_calcsize___doc__ 225 | }, 226 | { 227 | "__copy__", 228 | (PyCFunction)m_compiled_format_copy, 229 | METH_NOARGS 230 | }, 231 | { 232 | "__deepcopy__", 233 | (PyCFunction)m_compiled_format_deepcopy, 234 | METH_VARARGS 235 | }, 236 | { 237 | "__getstate__", 238 | (PyCFunction)m_compiled_format_getstate, 239 | METH_NOARGS 240 | }, 241 | { 242 | "__setstate__", 243 | (PyCFunction)m_compiled_format_setstate, 244 | METH_O 245 | }, 246 | { NULL } 247 | }; 248 | 249 | static PyTypeObject compiled_format_type = { 250 | PyVarObject_HEAD_INIT(NULL, 0) 251 | .tp_name = "bitstruct.c.CompiledFormat", 252 | .tp_doc = NULL, 253 | .tp_basicsize = sizeof(struct compiled_format_t), 254 | .tp_itemsize = 0, 255 | .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 256 | .tp_new = compiled_format_new, 257 | .tp_init = (initproc)compiled_format_init, 258 | .tp_dealloc = (destructor)compiled_format_dealloc, 259 | .tp_methods = compiled_format_methods, 260 | }; 261 | 262 | static struct PyMethodDef compiled_format_dict_methods[] = { 263 | { 264 | "pack", 265 | (PyCFunction)m_compiled_format_dict_pack, 266 | METH_O, 267 | pack___doc__ 268 | }, 269 | { 270 | "unpack", 271 | (PyCFunction)m_compiled_format_dict_unpack, 272 | METH_VARARGS | METH_KEYWORDS, 273 | unpack___doc__ 274 | }, 275 | { 276 | "pack_into", 277 | (PyCFunction)m_compiled_format_dict_pack_into, 278 | METH_VARARGS | METH_KEYWORDS, 279 | pack_into___doc__ 280 | }, 281 | { 282 | "unpack_from", 283 | (PyCFunction)m_compiled_format_dict_unpack_from, 284 | METH_VARARGS | METH_KEYWORDS, 285 | unpack_from___doc__ 286 | }, 287 | { 288 | "calcsize", 289 | (PyCFunction)m_compiled_format_dict_calcsize, 290 | METH_NOARGS, 291 | calcsize___doc__ 292 | }, 293 | { 294 | "__copy__", 295 | (PyCFunction)m_compiled_format_dict_copy, 296 | METH_NOARGS 297 | }, 298 | { 299 | "__deepcopy__", 300 | (PyCFunction)m_compiled_format_dict_deepcopy, 301 | METH_VARARGS 302 | }, 303 | { 304 | "__getstate__", 305 | (PyCFunction)m_compiled_format_dict_getstate, 306 | METH_NOARGS 307 | }, 308 | { 309 | "__setstate__", 310 | (PyCFunction)m_compiled_format_dict_setstate, 311 | METH_O 312 | }, 313 | { NULL } 314 | }; 315 | 316 | static PyTypeObject compiled_format_dict_type = { 317 | PyVarObject_HEAD_INIT(NULL, 0) 318 | .tp_name = "bitstruct.c.CompiledFormatDict", 319 | .tp_doc = NULL, 320 | .tp_basicsize = sizeof(struct compiled_format_dict_t), 321 | .tp_itemsize = 0, 322 | .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 323 | .tp_new = compiled_format_dict_new, 324 | .tp_init = (initproc)compiled_format_dict_init, 325 | .tp_dealloc = (destructor)compiled_format_dict_dealloc, 326 | .tp_methods = compiled_format_dict_methods, 327 | }; 328 | 329 | static bool is_names_list(PyObject *names_p) 330 | { 331 | if (!PyList_Check(names_p)) { 332 | PyErr_SetString(PyExc_TypeError, "Names is not a list."); 333 | 334 | return (false); 335 | } 336 | 337 | return (true); 338 | } 339 | 340 | static void pack_signed_integer(struct bitstream_writer_t *self_p, 341 | PyObject *value_p, 342 | struct field_info_t *field_info_p) 343 | { 344 | int64_t value; 345 | int64_t lower; 346 | int64_t upper; 347 | 348 | value = PyLong_AsLongLong(value_p); 349 | 350 | if ((value == -1) && PyErr_Occurred()) { 351 | return; 352 | } 353 | 354 | if (field_info_p->number_of_bits < 64) { 355 | lower = field_info_p->limits.s.lower; 356 | upper = field_info_p->limits.s.upper; 357 | 358 | if ((value < lower) || (value > upper)) { 359 | PyErr_Format(PyExc_OverflowError, 360 | "Signed integer value %lld out of range.", 361 | (long long)value); 362 | } 363 | 364 | value &= ((1ull << field_info_p->number_of_bits) - 1); 365 | } 366 | 367 | bitstream_writer_write_u64_bits(self_p, 368 | (uint64_t)value, 369 | field_info_p->number_of_bits); 370 | } 371 | 372 | static PyObject *unpack_signed_integer(struct bitstream_reader_t *self_p, 373 | struct field_info_t *field_info_p) 374 | { 375 | uint64_t value; 376 | uint64_t sign_bit; 377 | 378 | value = bitstream_reader_read_u64_bits(self_p, field_info_p->number_of_bits); 379 | sign_bit = (1ull << (field_info_p->number_of_bits - 1)); 380 | 381 | if (value & sign_bit) { 382 | value |= ~(((sign_bit) << 1) - 1); 383 | } 384 | 385 | return (PyLong_FromLongLong((long long)value)); 386 | } 387 | 388 | static void pack_unsigned_integer(struct bitstream_writer_t *self_p, 389 | PyObject *value_p, 390 | struct field_info_t *field_info_p) 391 | { 392 | uint64_t value; 393 | 394 | value = PyLong_AsUnsignedLongLong(value_p); 395 | 396 | if ((value == (uint64_t)-1) && PyErr_Occurred()) { 397 | return; 398 | } 399 | 400 | if (value > field_info_p->limits.u.upper) { 401 | PyErr_Format(PyExc_OverflowError, 402 | "Unsigned integer value %llu out of range.", 403 | (unsigned long long)value); 404 | } 405 | 406 | bitstream_writer_write_u64_bits(self_p, 407 | value, 408 | field_info_p->number_of_bits); 409 | } 410 | 411 | static PyObject *unpack_unsigned_integer(struct bitstream_reader_t *self_p, 412 | struct field_info_t *field_info_p) 413 | { 414 | uint64_t value; 415 | 416 | value = bitstream_reader_read_u64_bits(self_p, 417 | field_info_p->number_of_bits); 418 | 419 | return (PyLong_FromUnsignedLongLong(value)); 420 | } 421 | 422 | #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 6 423 | 424 | static void pack_float_16(struct bitstream_writer_t *self_p, 425 | PyObject *value_p, 426 | struct field_info_t *field_info_p) 427 | { 428 | uint8_t buf[2]; 429 | 430 | #if PY_VERSION_HEX >= 0x030B00A7 431 | PyFloat_Pack2(PyFloat_AsDouble(value_p), 432 | (char*)&buf[0], 433 | PY_BIG_ENDIAN); 434 | #else 435 | _PyFloat_Pack2(PyFloat_AsDouble(value_p), 436 | &buf[0], 437 | PY_BIG_ENDIAN); 438 | #endif 439 | bitstream_writer_write_bytes(self_p, &buf[0], sizeof(buf)); 440 | } 441 | 442 | static PyObject *unpack_float_16(struct bitstream_reader_t *self_p, 443 | struct field_info_t *field_info_p) 444 | { 445 | uint8_t buf[2]; 446 | double value; 447 | 448 | bitstream_reader_read_bytes(self_p, &buf[0], sizeof(buf)); 449 | #if PY_VERSION_HEX >= 0x030B00A7 450 | value = PyFloat_Unpack2((const char*)&buf[0], PY_BIG_ENDIAN); 451 | #else 452 | value = _PyFloat_Unpack2(&buf[0], PY_BIG_ENDIAN); 453 | #endif 454 | 455 | return (PyFloat_FromDouble(value)); 456 | } 457 | 458 | #endif 459 | 460 | static void pack_float_32(struct bitstream_writer_t *self_p, 461 | PyObject *value_p, 462 | struct field_info_t *field_info_p) 463 | { 464 | float value; 465 | uint32_t data; 466 | 467 | value = (float)PyFloat_AsDouble(value_p); 468 | memcpy(&data, &value, sizeof(data)); 469 | bitstream_writer_write_u32(self_p, data); 470 | } 471 | 472 | static PyObject *unpack_float_32(struct bitstream_reader_t *self_p, 473 | struct field_info_t *field_info_p) 474 | { 475 | float value; 476 | uint32_t data; 477 | 478 | data = bitstream_reader_read_u32(self_p); 479 | memcpy(&value, &data, sizeof(value)); 480 | 481 | return (PyFloat_FromDouble(value)); 482 | } 483 | 484 | static void pack_float_64(struct bitstream_writer_t *self_p, 485 | PyObject *value_p, 486 | struct field_info_t *field_info_p) 487 | { 488 | double value; 489 | uint64_t data; 490 | 491 | value = PyFloat_AsDouble(value_p); 492 | memcpy(&data, &value, sizeof(data)); 493 | bitstream_writer_write_u64_bits(self_p, 494 | data, 495 | field_info_p->number_of_bits); 496 | } 497 | 498 | static PyObject *unpack_float_64(struct bitstream_reader_t *self_p, 499 | struct field_info_t *field_info_p) 500 | { 501 | double value; 502 | uint64_t data; 503 | 504 | data = bitstream_reader_read_u64(self_p); 505 | memcpy(&value, &data, sizeof(value)); 506 | 507 | return (PyFloat_FromDouble(value)); 508 | } 509 | 510 | static void pack_bool(struct bitstream_writer_t *self_p, 511 | PyObject *value_p, 512 | struct field_info_t *field_info_p) 513 | { 514 | bitstream_writer_write_u64_bits(self_p, 515 | PyObject_IsTrue(value_p), 516 | field_info_p->number_of_bits); 517 | } 518 | 519 | static PyObject *unpack_bool(struct bitstream_reader_t *self_p, 520 | struct field_info_t *field_info_p) 521 | { 522 | return (PyBool_FromLong((long)bitstream_reader_read_u64_bits( 523 | self_p, 524 | field_info_p->number_of_bits))); 525 | } 526 | 527 | static void pack_text(struct bitstream_writer_t *self_p, 528 | PyObject *value_p, 529 | struct field_info_t *field_info_p) 530 | { 531 | Py_ssize_t size; 532 | const char* buf_p; 533 | 534 | buf_p = PyUnicode_AsUTF8AndSize(value_p, &size); 535 | 536 | if (buf_p != NULL) { 537 | if (size < (field_info_p->number_of_bits / 8)) { 538 | PyErr_SetString(PyExc_NotImplementedError, "Short text."); 539 | } else { 540 | bitstream_writer_write_bytes(self_p, 541 | (uint8_t *)buf_p, 542 | field_info_p->number_of_bits / 8); 543 | } 544 | } 545 | } 546 | 547 | static PyObject *unpack_text(struct bitstream_reader_t *self_p, 548 | struct field_info_t *field_info_p) 549 | { 550 | uint8_t *buf_p; 551 | PyObject *value_p; 552 | int number_of_bytes; 553 | 554 | number_of_bytes = (field_info_p->number_of_bits / 8); 555 | buf_p = PyMem_RawMalloc(number_of_bytes); 556 | 557 | if (buf_p == NULL) { 558 | return (NULL); 559 | } 560 | 561 | bitstream_reader_read_bytes(self_p, buf_p, number_of_bytes); 562 | value_p = PyUnicode_FromStringAndSize((const char *)buf_p, number_of_bytes); 563 | PyMem_RawFree(buf_p); 564 | 565 | return (value_p); 566 | } 567 | 568 | static void pack_raw(struct bitstream_writer_t *self_p, 569 | PyObject *value_p, 570 | struct field_info_t *field_info_p) 571 | { 572 | Py_ssize_t size; 573 | char* buf_p; 574 | int res; 575 | 576 | res = PyBytes_AsStringAndSize(value_p, &buf_p, &size); 577 | 578 | if (res != -1) { 579 | if (size < (field_info_p->number_of_bits / 8)) { 580 | PyErr_SetString(PyExc_NotImplementedError, "Short raw data."); 581 | } else { 582 | bitstream_writer_write_bytes(self_p, 583 | (uint8_t *)buf_p, 584 | field_info_p->number_of_bits / 8); 585 | } 586 | } 587 | } 588 | 589 | static PyObject *unpack_raw(struct bitstream_reader_t *self_p, 590 | struct field_info_t *field_info_p) 591 | { 592 | uint8_t *buf_p; 593 | PyObject *value_p; 594 | int number_of_bytes; 595 | 596 | number_of_bytes = (field_info_p->number_of_bits / 8); 597 | value_p = PyBytes_FromStringAndSize(NULL, number_of_bytes); 598 | buf_p = (uint8_t *)PyBytes_AS_STRING(value_p); 599 | bitstream_reader_read_bytes(self_p, buf_p, number_of_bytes); 600 | 601 | return (value_p); 602 | } 603 | 604 | static void pack_zero_padding(struct bitstream_writer_t *self_p, 605 | PyObject *value_p, 606 | struct field_info_t *field_info_p) 607 | { 608 | bitstream_writer_write_repeated_bit(self_p, 609 | 0, 610 | field_info_p->number_of_bits); 611 | } 612 | 613 | static void pack_one_padding(struct bitstream_writer_t *self_p, 614 | PyObject *value_p, 615 | struct field_info_t *field_info_p) 616 | { 617 | bitstream_writer_write_repeated_bit(self_p, 618 | 1, 619 | field_info_p->number_of_bits); 620 | } 621 | 622 | static PyObject *unpack_padding(struct bitstream_reader_t *self_p, 623 | struct field_info_t *field_info_p) 624 | { 625 | bitstream_reader_seek(self_p, field_info_p->number_of_bits); 626 | 627 | return (NULL); 628 | } 629 | 630 | static int field_info_init_signed(struct field_info_t *self_p, 631 | int number_of_bits) 632 | { 633 | uint64_t limit; 634 | 635 | self_p->pack = pack_signed_integer; 636 | self_p->unpack = unpack_signed_integer; 637 | 638 | if (number_of_bits > 64) { 639 | PyErr_SetString(PyExc_NotImplementedError, 640 | "Signed integer over 64 bits."); 641 | return (-1); 642 | } 643 | 644 | limit = (1ull << (number_of_bits - 1)); 645 | self_p->limits.s.lower = -limit; 646 | self_p->limits.s.upper = (limit - 1); 647 | 648 | return (0); 649 | } 650 | 651 | static int field_info_init_unsigned(struct field_info_t *self_p, 652 | int number_of_bits) 653 | { 654 | self_p->pack = pack_unsigned_integer; 655 | self_p->unpack = unpack_unsigned_integer; 656 | 657 | if (number_of_bits > 64) { 658 | PyErr_SetString(PyExc_NotImplementedError, 659 | "Unsigned integer over 64 bits."); 660 | return (-1); 661 | } 662 | 663 | if (number_of_bits < 64) { 664 | self_p->limits.u.upper = ((1ull << number_of_bits) - 1); 665 | } else { 666 | self_p->limits.u.upper = (uint64_t)-1; 667 | } 668 | 669 | return (0); 670 | } 671 | 672 | static int field_info_init_float(struct field_info_t *self_p, 673 | int number_of_bits) 674 | { 675 | switch (number_of_bits) { 676 | 677 | #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 6 678 | case 16: 679 | self_p->pack = pack_float_16; 680 | self_p->unpack = unpack_float_16; 681 | break; 682 | #endif 683 | 684 | case 32: 685 | self_p->pack = pack_float_32; 686 | self_p->unpack = unpack_float_32; 687 | break; 688 | 689 | case 64: 690 | self_p->pack = pack_float_64; 691 | self_p->unpack = unpack_float_64; 692 | break; 693 | 694 | default: 695 | #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 6 696 | PyErr_SetString(PyExc_NotImplementedError, 697 | "Float not 16, 32 or 64 bits."); 698 | #else 699 | PyErr_SetString(PyExc_NotImplementedError, "Float not 32 or 64 bits."); 700 | #endif 701 | return (-1); 702 | } 703 | 704 | return (0); 705 | } 706 | 707 | static int field_info_init_bool(struct field_info_t *self_p, 708 | int number_of_bits) 709 | { 710 | self_p->pack = pack_bool; 711 | self_p->unpack = unpack_bool; 712 | 713 | if (number_of_bits > 64) { 714 | PyErr_SetString(PyExc_NotImplementedError, "Bool over 64 bits."); 715 | return (-1); 716 | } 717 | 718 | return (0); 719 | } 720 | 721 | static int field_info_init_text(struct field_info_t *self_p, 722 | int number_of_bits) 723 | { 724 | self_p->pack = pack_text; 725 | self_p->unpack = unpack_text; 726 | 727 | if ((number_of_bits % 8) != 0) { 728 | PyErr_SetString(PyExc_NotImplementedError, 729 | "Text not multiple of 8 bits."); 730 | return (-1); 731 | } 732 | 733 | return (0); 734 | } 735 | 736 | static int field_info_init_raw(struct field_info_t *self_p, 737 | int number_of_bits) 738 | { 739 | self_p->pack = pack_raw; 740 | self_p->unpack = unpack_raw; 741 | 742 | if ((number_of_bits % 8) != 0) { 743 | PyErr_SetString(PyExc_NotImplementedError, 744 | "Raw not multiple of 8 bits."); 745 | return (-1); 746 | } 747 | 748 | return (0); 749 | } 750 | 751 | static int field_info_init_zero_padding(struct field_info_t *self_p) 752 | { 753 | self_p->pack = pack_zero_padding; 754 | self_p->unpack = unpack_padding; 755 | 756 | return (0); 757 | } 758 | 759 | static int field_info_init_one_padding(struct field_info_t *self_p) 760 | { 761 | self_p->pack = pack_one_padding; 762 | self_p->unpack = unpack_padding; 763 | 764 | return (0); 765 | } 766 | 767 | static int field_info_init(struct field_info_t *self_p, 768 | int kind, 769 | int number_of_bits) 770 | { 771 | int res; 772 | bool is_padding; 773 | 774 | is_padding = false; 775 | 776 | switch (kind) { 777 | 778 | case 's': 779 | res = field_info_init_signed(self_p, number_of_bits); 780 | break; 781 | 782 | case 'u': 783 | res = field_info_init_unsigned(self_p, number_of_bits); 784 | break; 785 | 786 | case 'f': 787 | res = field_info_init_float(self_p, number_of_bits); 788 | break; 789 | 790 | case 'b': 791 | res = field_info_init_bool(self_p, number_of_bits); 792 | break; 793 | 794 | case 't': 795 | res = field_info_init_text(self_p, number_of_bits); 796 | break; 797 | 798 | case 'r': 799 | res = field_info_init_raw(self_p, number_of_bits); 800 | break; 801 | 802 | case 'p': 803 | is_padding = true; 804 | res = field_info_init_zero_padding(self_p); 805 | break; 806 | 807 | case 'P': 808 | is_padding = true; 809 | res = field_info_init_one_padding(self_p); 810 | break; 811 | 812 | default: 813 | PyErr_Format(PyExc_ValueError, "Bad format field type '%c'.", kind); 814 | res = -1; 815 | break; 816 | } 817 | 818 | self_p->number_of_bits = number_of_bits; 819 | self_p->is_padding = is_padding; 820 | 821 | return (res); 822 | } 823 | 824 | static int count_number_of_fields(const char *format_p, 825 | int *number_of_padding_fields_p) 826 | { 827 | int count; 828 | 829 | count = 0; 830 | *number_of_padding_fields_p = 0; 831 | 832 | while (*format_p != '\0') { 833 | if ((*format_p >= 'A') && (*format_p <= 'z')) { 834 | count++; 835 | 836 | if ((*format_p == 'p') || (*format_p == 'P')) { 837 | (*number_of_padding_fields_p)++; 838 | } 839 | } 840 | 841 | format_p++; 842 | } 843 | 844 | return (count); 845 | } 846 | 847 | const char *parse_field(const char *format_p, 848 | int *kind_p, 849 | int *number_of_bits_p) 850 | { 851 | while (isspace(*format_p)) { 852 | format_p++; 853 | } 854 | 855 | *kind_p = *format_p; 856 | *number_of_bits_p = 0; 857 | format_p++; 858 | 859 | while (isdigit(*format_p)) { 860 | if (*number_of_bits_p > (INT_MAX / 100)) { 861 | PyErr_SetString(PyExc_ValueError, "Field too long."); 862 | 863 | return (NULL); 864 | } 865 | 866 | *number_of_bits_p *= 10; 867 | *number_of_bits_p += (*format_p - '0'); 868 | format_p++; 869 | } 870 | 871 | if (*number_of_bits_p == 0) { 872 | PyErr_SetString(PyExc_ValueError, "Field of size 0."); 873 | format_p = NULL; 874 | } 875 | 876 | return (format_p); 877 | } 878 | 879 | static struct info_t *parse_format(PyObject *format_obj_p) 880 | { 881 | int number_of_fields; 882 | struct info_t *info_p; 883 | const char *format_p; 884 | int i; 885 | int kind; 886 | int number_of_bits; 887 | int number_of_padding_fields; 888 | int res; 889 | 890 | format_p = PyUnicode_AsUTF8(format_obj_p); 891 | 892 | if (format_p == NULL) { 893 | return (NULL); 894 | } 895 | 896 | number_of_fields = count_number_of_fields(format_p, 897 | &number_of_padding_fields); 898 | 899 | info_p = PyMem_RawMalloc( 900 | sizeof(*info_p) + number_of_fields * sizeof(info_p->fields[0])); 901 | 902 | if (info_p == NULL) { 903 | return (NULL); 904 | } 905 | 906 | info_p->number_of_bits = 0; 907 | info_p->number_of_fields = number_of_fields; 908 | info_p->number_of_non_padding_fields = ( 909 | number_of_fields - number_of_padding_fields); 910 | 911 | for (i = 0; i < info_p->number_of_fields; i++) { 912 | format_p = parse_field(format_p, &kind, &number_of_bits); 913 | 914 | if (format_p == NULL) { 915 | PyMem_RawFree(info_p); 916 | 917 | return (NULL); 918 | } 919 | 920 | res = field_info_init(&info_p->fields[i], kind, number_of_bits); 921 | 922 | if (res != 0) { 923 | PyMem_RawFree(info_p); 924 | 925 | return (NULL); 926 | } 927 | 928 | info_p->number_of_bits += number_of_bits; 929 | } 930 | 931 | return (info_p); 932 | } 933 | 934 | static void pack_pack(struct info_t *info_p, 935 | PyObject *args_p, 936 | int consumed_args, 937 | struct bitstream_writer_t *writer_p) 938 | { 939 | PyObject *value_p; 940 | int i; 941 | struct field_info_t *field_p; 942 | 943 | for (i = 0; i < info_p->number_of_fields; i++) { 944 | field_p = &info_p->fields[i]; 945 | 946 | if (field_p->is_padding) { 947 | value_p = NULL; 948 | } else { 949 | value_p = PyTuple_GET_ITEM(args_p, consumed_args); 950 | consumed_args++; 951 | } 952 | 953 | info_p->fields[i].pack(writer_p, value_p, field_p); 954 | } 955 | } 956 | 957 | static PyObject *pack_prepare(struct info_t *info_p, 958 | struct bitstream_writer_t *writer_p) 959 | { 960 | PyObject *packed_p; 961 | 962 | packed_p = PyBytes_FromStringAndSize(NULL, (info_p->number_of_bits + 7) / 8); 963 | 964 | if (packed_p == NULL) { 965 | return (NULL); 966 | } 967 | 968 | bitstream_writer_init(writer_p, (uint8_t *)PyBytes_AS_STRING(packed_p)); 969 | 970 | return (packed_p); 971 | } 972 | 973 | static PyObject *pack_finalize(PyObject *packed_p) 974 | { 975 | if (PyErr_Occurred() != NULL) { 976 | Py_DECREF(packed_p); 977 | packed_p = NULL; 978 | } 979 | 980 | return (packed_p); 981 | } 982 | 983 | static PyObject *pack(struct info_t *info_p, 984 | PyObject *args_p, 985 | int consumed_args, 986 | Py_ssize_t number_of_args) 987 | { 988 | struct bitstream_writer_t writer; 989 | PyObject *packed_p; 990 | 991 | if (number_of_args < info_p->number_of_non_padding_fields) { 992 | PyErr_SetString(PyExc_ValueError, "Too few arguments."); 993 | 994 | return (NULL); 995 | } 996 | 997 | packed_p = pack_prepare(info_p, &writer); 998 | 999 | if (packed_p == NULL) { 1000 | return (NULL); 1001 | } 1002 | 1003 | pack_pack(info_p, args_p, consumed_args, &writer); 1004 | 1005 | return (pack_finalize(packed_p)); 1006 | } 1007 | 1008 | static PyObject *m_pack(PyObject *module_p, PyObject *args_p) 1009 | { 1010 | Py_ssize_t number_of_args; 1011 | PyObject *packed_p; 1012 | struct info_t *info_p; 1013 | 1014 | number_of_args = PyTuple_GET_SIZE(args_p); 1015 | 1016 | if (number_of_args < 1) { 1017 | PyErr_SetString(PyExc_ValueError, "No format string."); 1018 | 1019 | return (NULL); 1020 | } 1021 | 1022 | info_p = parse_format(PyTuple_GET_ITEM(args_p, 0)); 1023 | 1024 | if (info_p == NULL) { 1025 | return (NULL); 1026 | } 1027 | 1028 | packed_p = pack(info_p, args_p, 1, number_of_args - 1); 1029 | PyMem_RawFree(info_p); 1030 | 1031 | return (packed_p); 1032 | } 1033 | 1034 | static PyObject *unpack(struct info_t *info_p, 1035 | PyObject *data_p, 1036 | long offset, 1037 | PyObject *allow_truncated_p) 1038 | { 1039 | struct bitstream_reader_t reader; 1040 | PyObject *unpacked_p = NULL; 1041 | PyObject *value_p; 1042 | Py_buffer view = {NULL, NULL}; 1043 | int i; 1044 | int tmp; 1045 | int produced_args; 1046 | int res; 1047 | int allow_truncated; 1048 | int num_result_fields; 1049 | 1050 | res = PyObject_GetBuffer(data_p, &view, PyBUF_C_CONTIGUOUS); 1051 | if (res == -1) { 1052 | return (NULL); 1053 | } 1054 | 1055 | allow_truncated = PyObject_IsTrue(allow_truncated_p); 1056 | 1057 | if (allow_truncated) { 1058 | num_result_fields = 0; 1059 | tmp = 0; 1060 | for (i = 0; i < info_p->number_of_fields; i++) { 1061 | if (view.len*8 < tmp + info_p->fields[i].number_of_bits) { 1062 | break; 1063 | } 1064 | 1065 | tmp += info_p->fields[i].number_of_bits; 1066 | 1067 | if (!info_p->fields[i].is_padding) { 1068 | ++num_result_fields; 1069 | } 1070 | } 1071 | } 1072 | else { 1073 | num_result_fields = info_p->number_of_non_padding_fields; 1074 | 1075 | if (view.len < ((info_p->number_of_bits + offset + 7) / 8)) { 1076 | PyErr_SetString(PyExc_ValueError, "Short data."); 1077 | goto exit; 1078 | } 1079 | } 1080 | 1081 | unpacked_p = PyTuple_New(num_result_fields); 1082 | 1083 | if (unpacked_p == NULL) { 1084 | goto exit; 1085 | } 1086 | 1087 | bitstream_reader_init(&reader, (uint8_t *)view.buf); 1088 | bitstream_reader_seek(&reader, offset); 1089 | produced_args = 0; 1090 | 1091 | for (i = 0; i < info_p->number_of_fields; i++) { 1092 | if (produced_args == num_result_fields) { 1093 | break; 1094 | } 1095 | 1096 | value_p = info_p->fields[i].unpack(&reader, &info_p->fields[i]); 1097 | 1098 | if (value_p != NULL) { 1099 | PyTuple_SET_ITEM(unpacked_p, produced_args, value_p); 1100 | produced_args++; 1101 | } 1102 | } 1103 | 1104 | /* 1105 | out1: 1106 | if (PyErr_Occurred() != NULL) { 1107 | Py_DECREF(unpacked_p); 1108 | unpacked_p = NULL; 1109 | } 1110 | */ 1111 | exit: 1112 | PyBuffer_Release(&view); 1113 | return (unpacked_p); 1114 | } 1115 | 1116 | static PyObject *m_unpack(PyObject *module_p, 1117 | PyObject *args_p, 1118 | PyObject *kwargs_p) 1119 | { 1120 | PyObject *format_p; 1121 | PyObject *data_p; 1122 | PyObject *unpacked_p; 1123 | PyObject *allow_truncated_p; 1124 | struct info_t *info_p; 1125 | int res; 1126 | static char *keywords[] = { 1127 | "fmt", 1128 | "data", 1129 | "allow_truncated", 1130 | NULL 1131 | }; 1132 | 1133 | allow_truncated_p = py_zero_p; 1134 | res = PyArg_ParseTupleAndKeywords(args_p, 1135 | kwargs_p, 1136 | "OO|O", 1137 | &keywords[0], 1138 | &format_p, 1139 | &data_p, 1140 | &allow_truncated_p); 1141 | 1142 | if (res == 0) { 1143 | return (NULL); 1144 | } 1145 | 1146 | info_p = parse_format(format_p); 1147 | 1148 | if (info_p == NULL) { 1149 | return (NULL); 1150 | } 1151 | 1152 | unpacked_p = unpack(info_p, data_p, 0, allow_truncated_p); 1153 | PyMem_RawFree(info_p); 1154 | 1155 | return (unpacked_p); 1156 | } 1157 | 1158 | static long parse_offset(PyObject *offset_p) 1159 | { 1160 | unsigned long offset; 1161 | 1162 | offset = PyLong_AsUnsignedLong(offset_p); 1163 | 1164 | if (offset == (unsigned long)-1) { 1165 | return (-1); 1166 | } 1167 | 1168 | if (offset > 0x7fffffff) { 1169 | PyErr_Format(PyExc_ValueError, 1170 | "Offset must be less or equal to %d bits.", 1171 | 0x7fffffff); 1172 | 1173 | return (-1); 1174 | } 1175 | 1176 | return (offset); 1177 | } 1178 | 1179 | static int pack_into_prepare(struct info_t *info_p, 1180 | PyObject *buf_p, 1181 | PyObject *offset_p, 1182 | struct bitstream_writer_t *writer_p, 1183 | struct bitstream_writer_bounds_t *bounds_p) 1184 | { 1185 | uint8_t *packed_p; 1186 | Py_ssize_t size; 1187 | long offset; 1188 | 1189 | offset = parse_offset(offset_p); 1190 | 1191 | if (offset == -1) { 1192 | return (-1); 1193 | } 1194 | 1195 | if (!PyByteArray_Check(buf_p)) { 1196 | PyErr_SetString(PyExc_TypeError, "Bytearray needed."); 1197 | 1198 | return (-1); 1199 | } 1200 | 1201 | packed_p = (uint8_t *)PyByteArray_AsString(buf_p); 1202 | 1203 | if (packed_p == NULL) { 1204 | return (-1); 1205 | } 1206 | 1207 | size = PyByteArray_GET_SIZE(buf_p); 1208 | 1209 | if (size < ((info_p->number_of_bits + offset + 7) / 8)) { 1210 | PyErr_Format(PyExc_ValueError, 1211 | "pack_into requires a buffer of at least %ld bits", 1212 | info_p->number_of_bits + offset); 1213 | 1214 | return (-1); 1215 | } 1216 | 1217 | bitstream_writer_init(writer_p, packed_p); 1218 | bitstream_writer_bounds_save(bounds_p, 1219 | writer_p, 1220 | offset, 1221 | info_p->number_of_bits); 1222 | bitstream_writer_seek(writer_p, offset); 1223 | 1224 | return (0); 1225 | } 1226 | 1227 | static PyObject *pack_into_finalize(struct bitstream_writer_bounds_t *bounds_p) 1228 | { 1229 | bitstream_writer_bounds_restore(bounds_p); 1230 | 1231 | if (PyErr_Occurred() != NULL) { 1232 | return (NULL); 1233 | } 1234 | 1235 | Py_RETURN_NONE; 1236 | } 1237 | 1238 | static PyObject *pack_into(struct info_t *info_p, 1239 | PyObject *buf_p, 1240 | PyObject *offset_p, 1241 | PyObject *args_p, 1242 | Py_ssize_t consumed_args, 1243 | Py_ssize_t number_of_args) 1244 | { 1245 | struct bitstream_writer_t writer; 1246 | struct bitstream_writer_bounds_t bounds; 1247 | int res; 1248 | 1249 | if ((number_of_args - consumed_args) < info_p->number_of_non_padding_fields) { 1250 | PyErr_SetString(PyExc_ValueError, "Too few arguments."); 1251 | 1252 | return (NULL); 1253 | } 1254 | 1255 | res = pack_into_prepare(info_p, buf_p, offset_p, &writer, &bounds); 1256 | 1257 | if (res != 0) { 1258 | return (NULL); 1259 | } 1260 | 1261 | pack_pack(info_p, args_p, consumed_args, &writer); 1262 | 1263 | return (pack_into_finalize(&bounds)); 1264 | } 1265 | 1266 | static PyObject *m_pack_into(PyObject *module_p, 1267 | PyObject *args_p, 1268 | PyObject *kwargs_p) 1269 | { 1270 | PyObject *format_p; 1271 | PyObject *buf_p; 1272 | PyObject *offset_p; 1273 | PyObject *res_p; 1274 | Py_ssize_t number_of_args; 1275 | struct info_t *info_p; 1276 | 1277 | number_of_args = PyTuple_GET_SIZE(args_p); 1278 | 1279 | if (number_of_args < 3) { 1280 | PyErr_SetString(PyExc_ValueError, "Too few arguments."); 1281 | 1282 | return (NULL); 1283 | } 1284 | 1285 | format_p = PyTuple_GET_ITEM(args_p, 0); 1286 | buf_p = PyTuple_GET_ITEM(args_p, 1); 1287 | offset_p = PyTuple_GET_ITEM(args_p, 2); 1288 | info_p = parse_format(format_p); 1289 | 1290 | if (info_p == NULL) { 1291 | return (NULL); 1292 | } 1293 | 1294 | res_p = pack_into(info_p, 1295 | buf_p, 1296 | offset_p, 1297 | args_p, 1298 | 3, 1299 | number_of_args); 1300 | PyMem_RawFree(info_p); 1301 | 1302 | return (res_p); 1303 | } 1304 | 1305 | static PyObject *unpack_from(struct info_t *info_p, 1306 | PyObject *data_p, 1307 | PyObject *offset_p, 1308 | PyObject *allow_truncated_p) 1309 | { 1310 | long offset; 1311 | 1312 | offset = parse_offset(offset_p); 1313 | 1314 | if (offset == -1) { 1315 | return (NULL); 1316 | } 1317 | 1318 | return (unpack(info_p, data_p, offset, allow_truncated_p)); 1319 | } 1320 | 1321 | static PyObject *m_unpack_from(PyObject *module_p, 1322 | PyObject *args_p, 1323 | PyObject *kwargs_p) 1324 | { 1325 | PyObject *format_p; 1326 | PyObject *data_p; 1327 | PyObject *offset_p; 1328 | PyObject *unpacked_p; 1329 | PyObject *allow_truncated_p; 1330 | struct info_t *info_p; 1331 | int res; 1332 | static char *keywords[] = { 1333 | "fmt", 1334 | "data", 1335 | "offset", 1336 | "allow_truncated", 1337 | NULL 1338 | }; 1339 | 1340 | offset_p = py_zero_p; 1341 | allow_truncated_p = py_zero_p; 1342 | res = PyArg_ParseTupleAndKeywords(args_p, 1343 | kwargs_p, 1344 | "OO|OO", 1345 | &keywords[0], 1346 | &format_p, 1347 | &data_p, 1348 | &offset_p, 1349 | &allow_truncated_p); 1350 | 1351 | if (res == 0) { 1352 | return (NULL); 1353 | } 1354 | 1355 | info_p = parse_format(format_p); 1356 | 1357 | if (info_p == NULL) { 1358 | return (NULL); 1359 | } 1360 | 1361 | unpacked_p = unpack_from(info_p, data_p, offset_p, allow_truncated_p); 1362 | PyMem_RawFree(info_p); 1363 | 1364 | return (unpacked_p); 1365 | } 1366 | 1367 | static void pack_dict_pack(struct info_t *info_p, 1368 | PyObject *names_p, 1369 | PyObject *data_p, 1370 | struct bitstream_writer_t *writer_p) 1371 | { 1372 | PyObject *value_p; 1373 | int i; 1374 | int consumed_args; 1375 | struct field_info_t *field_p; 1376 | 1377 | consumed_args = 0; 1378 | 1379 | for (i = 0; i < info_p->number_of_fields; i++) { 1380 | field_p = &info_p->fields[i]; 1381 | 1382 | if (field_p->is_padding) { 1383 | value_p = NULL; 1384 | } else { 1385 | value_p = PyDict_GetItem(data_p, 1386 | PyList_GET_ITEM(names_p, consumed_args)); 1387 | consumed_args++; 1388 | 1389 | if (value_p == NULL) { 1390 | PyErr_SetString(PyExc_KeyError, "Missing value."); 1391 | break; 1392 | } 1393 | } 1394 | 1395 | info_p->fields[i].pack(writer_p, value_p, field_p); 1396 | } 1397 | } 1398 | 1399 | static PyObject *pack_dict(struct info_t *info_p, 1400 | PyObject *names_p, 1401 | PyObject *data_p) 1402 | { 1403 | struct bitstream_writer_t writer; 1404 | PyObject *packed_p; 1405 | 1406 | if (PyList_GET_SIZE(names_p) < info_p->number_of_non_padding_fields) { 1407 | PyErr_SetString(PyExc_ValueError, "Too few names."); 1408 | 1409 | return (NULL); 1410 | } 1411 | 1412 | packed_p = pack_prepare(info_p, &writer); 1413 | 1414 | if (packed_p == NULL) { 1415 | return (NULL); 1416 | } 1417 | 1418 | pack_dict_pack(info_p, names_p, data_p, &writer); 1419 | 1420 | return (pack_finalize(packed_p)); 1421 | } 1422 | 1423 | PyDoc_STRVAR(pack_dict___doc__, 1424 | "pack_dict(fmt, names, data)\n" 1425 | "--\n" 1426 | "\n"); 1427 | 1428 | static PyObject *m_pack_dict(PyObject *module_p, PyObject *args_p) 1429 | { 1430 | PyObject *format_p; 1431 | PyObject *names_p; 1432 | PyObject *data_p; 1433 | PyObject *packed_p; 1434 | struct info_t *info_p; 1435 | int res; 1436 | 1437 | res = PyArg_ParseTuple(args_p, "OOO", &format_p, &names_p, &data_p); 1438 | 1439 | if (res == 0) { 1440 | return (NULL); 1441 | } 1442 | 1443 | info_p = parse_format(format_p); 1444 | 1445 | if (info_p == NULL) { 1446 | return (NULL); 1447 | } 1448 | 1449 | if (!is_names_list(names_p)) { 1450 | return (NULL); 1451 | } 1452 | 1453 | packed_p = pack_dict(info_p, names_p, data_p); 1454 | PyMem_RawFree(info_p); 1455 | 1456 | return (packed_p); 1457 | } 1458 | 1459 | static PyObject *unpack_dict(struct info_t *info_p, 1460 | PyObject *names_p, 1461 | PyObject *data_p, 1462 | long offset, 1463 | PyObject *allow_truncated_p) 1464 | { 1465 | struct bitstream_reader_t reader; 1466 | PyObject *unpacked_p; 1467 | PyObject *value_p; 1468 | Py_buffer view = {NULL, NULL}; 1469 | int i; 1470 | int res; 1471 | int produced_args; 1472 | int allow_truncated; 1473 | 1474 | if (PyList_GET_SIZE(names_p) < info_p->number_of_non_padding_fields) { 1475 | PyErr_SetString(PyExc_ValueError, "Too few names."); 1476 | 1477 | return (NULL); 1478 | } 1479 | 1480 | unpacked_p = PyDict_New(); 1481 | 1482 | if (unpacked_p == NULL) { 1483 | return (NULL); 1484 | } 1485 | 1486 | res = PyObject_GetBuffer(data_p, &view, PyBUF_C_CONTIGUOUS); 1487 | 1488 | if (res == -1) { 1489 | goto out1; 1490 | } 1491 | 1492 | allow_truncated = PyObject_IsTrue(allow_truncated_p); 1493 | 1494 | if (!allow_truncated && view.len < ((info_p->number_of_bits + offset + 7) / 8)) { 1495 | PyErr_SetString(PyExc_ValueError, "Short data."); 1496 | 1497 | goto out1; 1498 | } 1499 | 1500 | bitstream_reader_init(&reader, (uint8_t *)view.buf); 1501 | bitstream_reader_seek(&reader, offset); 1502 | produced_args = 0; 1503 | 1504 | for (i = 0; i < info_p->number_of_fields; i++) { 1505 | if (view.len*8 < reader.bit_offset + info_p->fields[i].number_of_bits) 1506 | break; 1507 | 1508 | value_p = info_p->fields[i].unpack(&reader, &info_p->fields[i]); 1509 | 1510 | if (value_p != NULL) { 1511 | PyDict_SetItem(unpacked_p, 1512 | PyList_GET_ITEM(names_p, produced_args), 1513 | value_p); 1514 | Py_DECREF(value_p); 1515 | produced_args++; 1516 | } 1517 | } 1518 | 1519 | out1: 1520 | if (PyErr_Occurred() != NULL) { 1521 | Py_DECREF(unpacked_p); 1522 | unpacked_p = NULL; 1523 | } 1524 | 1525 | if (view.obj != NULL) { 1526 | PyBuffer_Release(&view); 1527 | } 1528 | 1529 | return (unpacked_p); 1530 | } 1531 | 1532 | PyDoc_STRVAR(unpack_dict___doc__, 1533 | "unpack_dict(fmt, names, data, allow_truncated=False)\n" 1534 | "--\n" 1535 | "\n"); 1536 | 1537 | static PyObject *m_unpack_dict(PyObject *module_p, 1538 | PyObject *args_p, 1539 | PyObject *kwargs_p) 1540 | { 1541 | PyObject *format_p; 1542 | PyObject *names_p; 1543 | PyObject *data_p; 1544 | PyObject *allow_truncated_p; 1545 | PyObject *unpacked_p; 1546 | struct info_t *info_p; 1547 | int res; 1548 | static char *keywords[] = { 1549 | "fmt", 1550 | "names", 1551 | "data", 1552 | "allow_truncated", 1553 | NULL 1554 | }; 1555 | 1556 | allow_truncated_p = py_zero_p; 1557 | res = PyArg_ParseTupleAndKeywords(args_p, 1558 | kwargs_p, 1559 | "OOO|O", 1560 | &keywords[0], 1561 | &format_p, 1562 | &names_p, 1563 | &data_p, 1564 | &allow_truncated_p); 1565 | 1566 | if (res == 0) { 1567 | return (NULL); 1568 | } 1569 | 1570 | info_p = parse_format(format_p); 1571 | 1572 | if (info_p == NULL) { 1573 | return (NULL); 1574 | } 1575 | 1576 | if (!is_names_list(names_p)) { 1577 | return (NULL); 1578 | } 1579 | 1580 | unpacked_p = unpack_dict(info_p, names_p, data_p, 0, allow_truncated_p); 1581 | PyMem_RawFree(info_p); 1582 | 1583 | return (unpacked_p); 1584 | } 1585 | 1586 | static PyObject *unpack_from_dict(struct info_t *info_p, 1587 | PyObject *names_p, 1588 | PyObject *data_p, 1589 | PyObject *offset_p, 1590 | PyObject *allow_truncated_p) 1591 | { 1592 | long offset; 1593 | 1594 | offset = parse_offset(offset_p); 1595 | 1596 | if (offset == -1) { 1597 | return (NULL); 1598 | } 1599 | 1600 | return (unpack_dict(info_p, names_p, data_p, offset, allow_truncated_p)); 1601 | } 1602 | 1603 | static PyObject *pack_into_dict(struct info_t *info_p, 1604 | PyObject *names_p, 1605 | PyObject *buf_p, 1606 | PyObject *offset_p, 1607 | PyObject *data_p) 1608 | { 1609 | struct bitstream_writer_t writer; 1610 | struct bitstream_writer_bounds_t bounds; 1611 | int res; 1612 | 1613 | res = pack_into_prepare(info_p, buf_p, offset_p, &writer, &bounds); 1614 | 1615 | if (res != 0) { 1616 | return (NULL); 1617 | } 1618 | 1619 | pack_dict_pack(info_p, names_p, data_p, &writer); 1620 | 1621 | return (pack_into_finalize(&bounds)); 1622 | } 1623 | 1624 | PyDoc_STRVAR(pack_into_dict___doc__, 1625 | "pack_into_dict(fmt, names, buf, offset, data, **kwargs)\n" 1626 | "--\n" 1627 | "\n"); 1628 | 1629 | static PyObject *m_pack_into_dict(PyObject *module_p, 1630 | PyObject *args_p, 1631 | PyObject *kwargs_p) 1632 | { 1633 | PyObject *format_p; 1634 | PyObject *names_p; 1635 | PyObject *buf_p; 1636 | PyObject *offset_p; 1637 | PyObject *data_p; 1638 | PyObject *res_p; 1639 | struct info_t *info_p; 1640 | int res; 1641 | static char *keywords[] = { 1642 | "fmt", 1643 | "names", 1644 | "buf", 1645 | "offset", 1646 | "data", 1647 | NULL 1648 | }; 1649 | 1650 | offset_p = py_zero_p; 1651 | res = PyArg_ParseTupleAndKeywords(args_p, 1652 | kwargs_p, 1653 | "OOOOO", 1654 | &keywords[0], 1655 | &format_p, 1656 | &names_p, 1657 | &buf_p, 1658 | &offset_p, 1659 | &data_p); 1660 | 1661 | if (res == 0) { 1662 | return (NULL); 1663 | } 1664 | 1665 | info_p = parse_format(format_p); 1666 | 1667 | if (info_p == NULL) { 1668 | return (NULL); 1669 | } 1670 | 1671 | if (!is_names_list(names_p)) { 1672 | return (NULL); 1673 | } 1674 | 1675 | res_p = pack_into_dict(info_p, names_p, buf_p, offset_p, data_p); 1676 | PyMem_RawFree(info_p); 1677 | 1678 | return (res_p); 1679 | } 1680 | 1681 | PyDoc_STRVAR(unpack_from_dict___doc__, 1682 | "unpack_from_dict(fmt, names, data, offset=0, allow_truncated=False)\n" 1683 | "--\n" 1684 | "\n"); 1685 | 1686 | static PyObject *m_unpack_from_dict(PyObject *module_p, 1687 | PyObject *args_p, 1688 | PyObject *kwargs_p) 1689 | { 1690 | PyObject *format_p; 1691 | PyObject *names_p; 1692 | PyObject *data_p; 1693 | PyObject *offset_p; 1694 | PyObject *allow_truncated_p; 1695 | PyObject *unpacked_p; 1696 | struct info_t *info_p; 1697 | int res; 1698 | static char *keywords[] = { 1699 | "fmt", 1700 | "names", 1701 | "data", 1702 | "offset", 1703 | "allow_truncated", 1704 | NULL 1705 | }; 1706 | 1707 | offset_p = py_zero_p; 1708 | allow_truncated_p = py_zero_p; 1709 | res = PyArg_ParseTupleAndKeywords(args_p, 1710 | kwargs_p, 1711 | "OOO|OO", 1712 | &keywords[0], 1713 | &format_p, 1714 | &names_p, 1715 | &data_p, 1716 | &offset_p, 1717 | &allow_truncated_p); 1718 | 1719 | if (res == 0) { 1720 | return (NULL); 1721 | } 1722 | 1723 | info_p = parse_format(format_p); 1724 | 1725 | if (info_p == NULL) { 1726 | return (NULL); 1727 | } 1728 | 1729 | if (!is_names_list(names_p)) { 1730 | return (NULL); 1731 | } 1732 | 1733 | unpacked_p = unpack_from_dict(info_p, names_p, data_p, offset_p, allow_truncated_p); 1734 | PyMem_RawFree(info_p); 1735 | 1736 | return (unpacked_p); 1737 | } 1738 | 1739 | static PyObject *calcsize(struct info_t *info_p) 1740 | { 1741 | return (PyLong_FromLong(info_p->number_of_bits)); 1742 | } 1743 | 1744 | static PyObject *m_calcsize(PyObject *module_p, PyObject *format_p) 1745 | { 1746 | PyObject *size_p; 1747 | struct info_t *info_p; 1748 | 1749 | info_p = parse_format(format_p); 1750 | 1751 | if (info_p == NULL) { 1752 | return (NULL); 1753 | } 1754 | 1755 | size_p = calcsize(info_p); 1756 | PyMem_RawFree(info_p); 1757 | 1758 | return (size_p); 1759 | } 1760 | 1761 | PyDoc_STRVAR(byteswap___doc__, 1762 | "byteswap(fmt, data, offset=0)\n" 1763 | "--\n" 1764 | "\n"); 1765 | 1766 | static PyObject *m_byteswap(PyObject *module_p, 1767 | PyObject *args_p, 1768 | PyObject *kwargs_p) 1769 | { 1770 | PyObject *format_p; 1771 | PyObject *data_p; 1772 | PyObject *swapped_p; 1773 | const char *c_format_p; 1774 | uint8_t *src_p; 1775 | uint8_t *dst_p; 1776 | Py_ssize_t size; 1777 | int res; 1778 | int offset; 1779 | 1780 | static char *keywords[] = { 1781 | "fmt", 1782 | "data", 1783 | NULL 1784 | }; 1785 | 1786 | res = PyArg_ParseTupleAndKeywords(args_p, 1787 | kwargs_p, 1788 | "OO", 1789 | &keywords[0], 1790 | &format_p, 1791 | &data_p); 1792 | 1793 | if (res == 0) { 1794 | return (NULL); 1795 | } 1796 | 1797 | c_format_p = PyUnicode_AsUTF8(format_p); 1798 | 1799 | if (c_format_p == NULL) { 1800 | return (NULL); 1801 | } 1802 | 1803 | res = PyBytes_AsStringAndSize(data_p, (char **)&src_p, &size); 1804 | 1805 | if (res == -1) { 1806 | return (NULL); 1807 | } 1808 | 1809 | swapped_p = PyBytes_FromStringAndSize(NULL, size); 1810 | 1811 | if (swapped_p == NULL) { 1812 | return (NULL); 1813 | } 1814 | 1815 | dst_p = (uint8_t *)PyBytes_AS_STRING(swapped_p); 1816 | offset = 0; 1817 | 1818 | while (*c_format_p != '\0') { 1819 | switch (*c_format_p) { 1820 | 1821 | case '1': 1822 | if ((size - offset) < 1) { 1823 | goto out1; 1824 | } 1825 | 1826 | dst_p[offset] = src_p[offset]; 1827 | offset += 1; 1828 | break; 1829 | 1830 | case '2': 1831 | if ((size - offset) < 2) { 1832 | goto out1; 1833 | } 1834 | 1835 | dst_p[offset + 0] = src_p[offset + 1]; 1836 | dst_p[offset + 1] = src_p[offset + 0]; 1837 | offset += 2; 1838 | break; 1839 | 1840 | case '4': 1841 | if ((size - offset) < 4) { 1842 | goto out1; 1843 | } 1844 | 1845 | dst_p[offset + 0] = src_p[offset + 3]; 1846 | dst_p[offset + 1] = src_p[offset + 2]; 1847 | dst_p[offset + 2] = src_p[offset + 1]; 1848 | dst_p[offset + 3] = src_p[offset + 0]; 1849 | offset += 4; 1850 | break; 1851 | 1852 | case '8': 1853 | if ((size - offset) < 8) { 1854 | goto out1; 1855 | } 1856 | 1857 | dst_p[offset + 0] = src_p[offset + 7]; 1858 | dst_p[offset + 1] = src_p[offset + 6]; 1859 | dst_p[offset + 2] = src_p[offset + 5]; 1860 | dst_p[offset + 3] = src_p[offset + 4]; 1861 | dst_p[offset + 4] = src_p[offset + 3]; 1862 | dst_p[offset + 5] = src_p[offset + 2]; 1863 | dst_p[offset + 6] = src_p[offset + 1]; 1864 | dst_p[offset + 7] = src_p[offset + 0]; 1865 | offset += 8; 1866 | break; 1867 | 1868 | default: 1869 | PyErr_Format(PyExc_ValueError, 1870 | "Expected 1, 2, 4 or 8, but got %c.", 1871 | (char)*c_format_p); 1872 | goto out2; 1873 | } 1874 | 1875 | c_format_p++; 1876 | } 1877 | 1878 | return (swapped_p); 1879 | 1880 | out1: 1881 | PyErr_SetString(PyExc_ValueError, "Out of data to swap."); 1882 | 1883 | out2: 1884 | 1885 | return (NULL); 1886 | } 1887 | 1888 | static PyObject *compiled_format_create(PyTypeObject *type_p, 1889 | PyObject *format_p) 1890 | { 1891 | PyObject *self_p; 1892 | 1893 | self_p = compiled_format_new(type_p, NULL, NULL); 1894 | 1895 | if (self_p == NULL) { 1896 | return (NULL); 1897 | } 1898 | 1899 | if (compiled_format_init_inner((struct compiled_format_t *)self_p, 1900 | format_p) != 0) { 1901 | return (NULL); 1902 | } 1903 | 1904 | return (self_p); 1905 | } 1906 | 1907 | static PyObject *compiled_format_new(PyTypeObject *type_p, 1908 | PyObject *args_p, 1909 | PyObject *kwargs_p) 1910 | { 1911 | return (type_p->tp_alloc(type_p, 0)); 1912 | } 1913 | 1914 | static int compiled_format_init(struct compiled_format_t *self_p, 1915 | PyObject *args_p, 1916 | PyObject *kwargs_p) 1917 | { 1918 | int res; 1919 | PyObject *format_p; 1920 | 1921 | static char *keywords[] = { 1922 | "fmt", 1923 | NULL 1924 | }; 1925 | 1926 | res = PyArg_ParseTupleAndKeywords(args_p, 1927 | kwargs_p, 1928 | "O", 1929 | &keywords[0], 1930 | &format_p); 1931 | 1932 | if (res == 0) { 1933 | return (-1); 1934 | } 1935 | 1936 | return (compiled_format_init_inner(self_p, format_p)); 1937 | } 1938 | 1939 | static int compiled_format_init_inner(struct compiled_format_t *self_p, 1940 | PyObject *format_p) 1941 | { 1942 | self_p->info_p = parse_format(format_p); 1943 | 1944 | if (self_p->info_p == NULL) { 1945 | PyObject_Free(self_p); 1946 | 1947 | return (-1); 1948 | } 1949 | 1950 | Py_INCREF(format_p); 1951 | self_p->format_p = format_p; 1952 | 1953 | return (0); 1954 | } 1955 | 1956 | static void compiled_format_dealloc(struct compiled_format_t *self_p) 1957 | { 1958 | PyMem_RawFree(self_p->info_p); 1959 | Py_DECREF(self_p->format_p); 1960 | Py_TYPE(self_p)->tp_free((PyObject *)self_p); 1961 | } 1962 | 1963 | static PyObject *m_compiled_format_pack(struct compiled_format_t *self_p, 1964 | PyObject *args_p) 1965 | { 1966 | return (pack(self_p->info_p, args_p, 0, PyTuple_GET_SIZE(args_p))); 1967 | } 1968 | 1969 | static PyObject *m_compiled_format_unpack(struct compiled_format_t *self_p, 1970 | PyObject *args_p, 1971 | PyObject *kwargs_p) 1972 | { 1973 | PyObject *data_p; 1974 | PyObject *allow_truncated_p; 1975 | int res; 1976 | static char *keywords[] = { 1977 | "data", 1978 | "allow_truncated", 1979 | NULL 1980 | }; 1981 | 1982 | allow_truncated_p = py_zero_p; 1983 | res = PyArg_ParseTupleAndKeywords(args_p, 1984 | kwargs_p, 1985 | "O|O", 1986 | &keywords[0], 1987 | &data_p, 1988 | &allow_truncated_p); 1989 | 1990 | if (res == 0) { 1991 | return (NULL); 1992 | } 1993 | 1994 | return (unpack(self_p->info_p, data_p, 0, allow_truncated_p)); 1995 | } 1996 | 1997 | static PyObject *m_compiled_format_pack_into(struct compiled_format_t *self_p, 1998 | PyObject *args_p, 1999 | PyObject *kwargs_p) 2000 | { 2001 | PyObject *buf_p; 2002 | PyObject *offset_p; 2003 | Py_ssize_t number_of_args; 2004 | 2005 | number_of_args = PyTuple_GET_SIZE(args_p); 2006 | 2007 | if (number_of_args < 2) { 2008 | PyErr_SetString(PyExc_ValueError, "Too few arguments."); 2009 | 2010 | return (NULL); 2011 | } 2012 | 2013 | buf_p = PyTuple_GET_ITEM(args_p, 0); 2014 | offset_p = PyTuple_GET_ITEM(args_p, 1); 2015 | 2016 | return (pack_into(self_p->info_p, 2017 | buf_p, 2018 | offset_p, 2019 | args_p, 2020 | 2, 2021 | number_of_args)); 2022 | } 2023 | 2024 | static PyObject *m_compiled_format_unpack_from(struct compiled_format_t *self_p, 2025 | PyObject *args_p, 2026 | PyObject *kwargs_p) 2027 | { 2028 | PyObject *data_p; 2029 | PyObject *offset_p; 2030 | PyObject *allow_truncated_p; 2031 | int res; 2032 | static char *keywords[] = { 2033 | "data", 2034 | "offset", 2035 | "allow_truncated", 2036 | NULL 2037 | }; 2038 | 2039 | offset_p = py_zero_p; 2040 | allow_truncated_p = py_zero_p; 2041 | res = PyArg_ParseTupleAndKeywords(args_p, 2042 | kwargs_p, 2043 | "O|OO", 2044 | &keywords[0], 2045 | &data_p, 2046 | &offset_p, 2047 | &allow_truncated_p); 2048 | 2049 | if (res == 0) { 2050 | return (NULL); 2051 | } 2052 | 2053 | return (unpack_from(self_p->info_p, data_p, offset_p, allow_truncated_p)); 2054 | } 2055 | 2056 | static PyObject *m_compiled_format_calcsize(struct compiled_format_t *self_p) 2057 | { 2058 | return (calcsize(self_p->info_p)); 2059 | } 2060 | 2061 | static PyObject *m_compiled_format_copy(struct compiled_format_t *self_p) 2062 | { 2063 | struct compiled_format_t *new_p; 2064 | size_t info_size; 2065 | 2066 | new_p = (struct compiled_format_t *)compiled_format_new( 2067 | &compiled_format_type, 2068 | NULL, 2069 | NULL); 2070 | 2071 | if (new_p == NULL) { 2072 | return (NULL); 2073 | } 2074 | 2075 | info_size = sizeof(*self_p->info_p); 2076 | info_size += (sizeof(self_p->info_p->fields[0]) 2077 | * (self_p->info_p->number_of_fields - 1)); 2078 | 2079 | new_p->info_p = PyMem_RawMalloc(info_size); 2080 | 2081 | if (new_p->info_p == NULL) { 2082 | /* ToDo: Free new_p. */ 2083 | return (NULL); 2084 | } 2085 | 2086 | memcpy(new_p->info_p, self_p->info_p, info_size); 2087 | Py_INCREF(self_p->format_p); 2088 | new_p->format_p = self_p->format_p; 2089 | 2090 | return ((PyObject *)new_p); 2091 | } 2092 | 2093 | static PyObject *m_compiled_format_deepcopy(struct compiled_format_t *self_p, 2094 | PyObject *args_p) 2095 | { 2096 | return (m_compiled_format_copy(self_p)); 2097 | } 2098 | 2099 | static PyObject *m_compiled_format_getstate(struct compiled_format_t *self_p, 2100 | PyObject *args_p) 2101 | { 2102 | return (Py_BuildValue("{sOsi}", 2103 | "format", 2104 | self_p->format_p, 2105 | pickle_version_key, 2106 | pickle_version)); 2107 | } 2108 | 2109 | static PyObject *m_compiled_format_setstate(struct compiled_format_t *self_p, 2110 | PyObject *state_p) 2111 | { 2112 | PyObject *version_p; 2113 | int version; 2114 | PyObject *format_p; 2115 | 2116 | if (!PyDict_CheckExact(state_p)) { 2117 | PyErr_SetString(PyExc_ValueError, "Pickled object is not a dict."); 2118 | 2119 | return (NULL); 2120 | } 2121 | 2122 | version_p = PyDict_GetItemString(state_p, pickle_version_key); 2123 | 2124 | if (version_p == NULL) { 2125 | PyErr_Format(PyExc_KeyError, 2126 | "No \"%s\" in pickled dict.", 2127 | pickle_version_key); 2128 | 2129 | return (NULL); 2130 | } 2131 | 2132 | version = (int)PyLong_AsLong(version_p); 2133 | 2134 | if (version != pickle_version) { 2135 | PyErr_Format(PyExc_ValueError, 2136 | "Pickle version mismatch. Got version %d but expected version %d.", 2137 | version, pickle_version); 2138 | 2139 | return (NULL); 2140 | } 2141 | 2142 | format_p = PyDict_GetItemString(state_p, "format"); 2143 | 2144 | if (format_p == NULL) { 2145 | PyErr_SetString(PyExc_KeyError, "No \"format\" in pickled dict."); 2146 | 2147 | return (NULL); 2148 | } 2149 | 2150 | if (compiled_format_init_inner(self_p, format_p) != 0) { 2151 | return (NULL); 2152 | } 2153 | 2154 | Py_RETURN_NONE; 2155 | } 2156 | 2157 | static PyObject *compiled_format_dict_create(PyTypeObject *type_p, 2158 | PyObject *format_p, 2159 | PyObject *names_p) 2160 | { 2161 | PyObject *self_p; 2162 | 2163 | self_p = compiled_format_dict_new(type_p, NULL, NULL); 2164 | 2165 | if (self_p == NULL) { 2166 | return (NULL); 2167 | } 2168 | 2169 | if (compiled_format_dict_init_inner((struct compiled_format_dict_t *)self_p, 2170 | format_p, 2171 | names_p) != 0) { 2172 | return (NULL); 2173 | } 2174 | 2175 | return (self_p); 2176 | } 2177 | 2178 | static PyObject *compiled_format_dict_new(PyTypeObject *type_p, 2179 | PyObject *args_p, 2180 | PyObject *kwargs_p) 2181 | { 2182 | return (type_p->tp_alloc(type_p, 0)); 2183 | } 2184 | 2185 | static int compiled_format_dict_init(struct compiled_format_dict_t *self_p, 2186 | PyObject *args_p, 2187 | PyObject *kwargs_p) 2188 | { 2189 | int res; 2190 | PyObject *format_p; 2191 | PyObject *names_p; 2192 | static char *keywords[] = { 2193 | "fmt", 2194 | "names", 2195 | NULL 2196 | }; 2197 | 2198 | res = PyArg_ParseTupleAndKeywords(args_p, 2199 | kwargs_p, 2200 | "OO", 2201 | &keywords[0], 2202 | &format_p, 2203 | &names_p); 2204 | 2205 | if (res == 0) { 2206 | return (-1); 2207 | } 2208 | 2209 | return (compiled_format_dict_init_inner(self_p, format_p, names_p)); 2210 | } 2211 | 2212 | static int compiled_format_dict_init_inner(struct compiled_format_dict_t *self_p, 2213 | PyObject *format_p, 2214 | PyObject *names_p) 2215 | { 2216 | if (!is_names_list(names_p)) { 2217 | return (-1); 2218 | } 2219 | 2220 | self_p->info_p = parse_format(format_p); 2221 | 2222 | if (self_p->info_p == NULL) { 2223 | PyObject_Free(self_p); 2224 | 2225 | return (-1); 2226 | } 2227 | 2228 | Py_INCREF(format_p); 2229 | self_p->format_p = format_p; 2230 | Py_INCREF(names_p); 2231 | self_p->names_p = names_p; 2232 | 2233 | return (0); 2234 | } 2235 | 2236 | static void compiled_format_dict_dealloc(struct compiled_format_dict_t *self_p) 2237 | { 2238 | PyMem_RawFree(self_p->info_p); 2239 | Py_DECREF(self_p->names_p); 2240 | Py_DECREF(self_p->format_p); 2241 | Py_TYPE(self_p)->tp_free((PyObject *)self_p); 2242 | } 2243 | 2244 | static PyObject *m_compiled_format_dict_pack(struct compiled_format_dict_t *self_p, 2245 | PyObject *data_p) 2246 | { 2247 | return (pack_dict(self_p->info_p, self_p->names_p, data_p)); 2248 | } 2249 | 2250 | static PyObject *m_compiled_format_dict_unpack( 2251 | struct compiled_format_dict_t *self_p, 2252 | PyObject *args_p, 2253 | PyObject *kwargs_p) 2254 | { 2255 | PyObject *data_p; 2256 | PyObject *allow_truncated_p; 2257 | int res; 2258 | static char *keywords[] = { 2259 | "data", 2260 | "allow_truncated", 2261 | NULL 2262 | }; 2263 | 2264 | allow_truncated_p = py_zero_p; 2265 | res = PyArg_ParseTupleAndKeywords(args_p, 2266 | kwargs_p, 2267 | "O|O", 2268 | &keywords[0], 2269 | &data_p, 2270 | &allow_truncated_p); 2271 | 2272 | if (res == 0) { 2273 | return (NULL); 2274 | } 2275 | 2276 | return (unpack_dict(self_p->info_p, self_p->names_p, data_p, 0, allow_truncated_p)); 2277 | } 2278 | 2279 | static PyObject *m_compiled_format_dict_pack_into( 2280 | struct compiled_format_dict_t *self_p, 2281 | PyObject *args_p, 2282 | PyObject *kwargs_p) 2283 | { 2284 | PyObject *buf_p; 2285 | PyObject *data_p; 2286 | PyObject *offset_p; 2287 | int res; 2288 | static char *keywords[] = { 2289 | "buf", 2290 | "data", 2291 | "offset", 2292 | NULL 2293 | }; 2294 | 2295 | res = PyArg_ParseTupleAndKeywords(args_p, 2296 | kwargs_p, 2297 | "OOO", 2298 | &keywords[0], 2299 | &buf_p, 2300 | &data_p, 2301 | &offset_p); 2302 | 2303 | if (res == 0) { 2304 | return (NULL); 2305 | } 2306 | 2307 | return (pack_into_dict(self_p->info_p, 2308 | self_p->names_p, 2309 | buf_p, 2310 | data_p, 2311 | offset_p)); 2312 | } 2313 | 2314 | static PyObject *m_compiled_format_dict_unpack_from( 2315 | struct compiled_format_dict_t *self_p, 2316 | PyObject *args_p, 2317 | PyObject *kwargs_p) 2318 | { 2319 | PyObject *data_p; 2320 | PyObject *offset_p; 2321 | PyObject *allow_truncated_p; 2322 | int res; 2323 | static char *keywords[] = { 2324 | "data", 2325 | "offset", 2326 | NULL 2327 | }; 2328 | 2329 | offset_p = py_zero_p; 2330 | allow_truncated_p = py_zero_p; 2331 | res = PyArg_ParseTupleAndKeywords(args_p, 2332 | kwargs_p, 2333 | "O|OO", 2334 | &keywords[0], 2335 | &data_p, 2336 | &offset_p, 2337 | &allow_truncated_p); 2338 | 2339 | if (res == 0) { 2340 | return (NULL); 2341 | } 2342 | 2343 | return (unpack_from_dict(self_p->info_p, 2344 | self_p->names_p, 2345 | data_p, 2346 | offset_p, 2347 | allow_truncated_p)); 2348 | } 2349 | 2350 | static PyObject *m_compiled_format_dict_calcsize( 2351 | struct compiled_format_dict_t *self_p) 2352 | { 2353 | return (calcsize(self_p->info_p)); 2354 | } 2355 | 2356 | static PyObject *m_compiled_format_dict_copy(struct compiled_format_dict_t *self_p) 2357 | { 2358 | struct compiled_format_dict_t *new_p; 2359 | size_t info_size; 2360 | 2361 | new_p = (struct compiled_format_dict_t *)compiled_format_dict_new( 2362 | &compiled_format_dict_type, 2363 | NULL, 2364 | NULL); 2365 | 2366 | if (new_p == NULL) { 2367 | return (NULL); 2368 | } 2369 | 2370 | info_size = sizeof(*self_p->info_p); 2371 | info_size += (sizeof(self_p->info_p->fields[0]) 2372 | * (self_p->info_p->number_of_fields - 1)); 2373 | 2374 | new_p->info_p = PyMem_RawMalloc(info_size); 2375 | 2376 | if (new_p->info_p == NULL) { 2377 | /* ToDo: Free new_p. */ 2378 | return (NULL); 2379 | } 2380 | 2381 | memcpy(new_p->info_p, self_p->info_p, info_size); 2382 | Py_INCREF(self_p->names_p); 2383 | new_p->names_p = self_p->names_p; 2384 | Py_INCREF(self_p->format_p); 2385 | new_p->format_p = self_p->format_p; 2386 | 2387 | return ((PyObject *)new_p); 2388 | } 2389 | 2390 | static PyObject *m_compiled_format_dict_deepcopy(struct compiled_format_dict_t *self_p, 2391 | PyObject *args_p) 2392 | { 2393 | return (m_compiled_format_dict_copy(self_p)); 2394 | } 2395 | 2396 | PyDoc_STRVAR(compile___doc__, 2397 | "compile(fmt, names=None)\n" 2398 | "--\n" 2399 | "\n"); 2400 | 2401 | static PyObject *m_compiled_format_dict_getstate(struct compiled_format_dict_t *self_p, 2402 | PyObject *args_p) 2403 | { 2404 | return (Py_BuildValue("{sOsOsi}", 2405 | "format", 2406 | self_p->format_p, 2407 | "names", 2408 | self_p->names_p, 2409 | pickle_version_key, 2410 | pickle_version)); 2411 | } 2412 | 2413 | static PyObject *m_compiled_format_dict_setstate(struct compiled_format_dict_t *self_p, 2414 | PyObject *state_p) 2415 | { 2416 | PyObject *version_p; 2417 | int version; 2418 | PyObject *format_p; 2419 | PyObject *names_p; 2420 | 2421 | if (!PyDict_CheckExact(state_p)) { 2422 | PyErr_SetString(PyExc_ValueError, "Pickled object is not a dict."); 2423 | 2424 | return (NULL); 2425 | } 2426 | 2427 | version_p = PyDict_GetItemString(state_p, pickle_version_key); 2428 | 2429 | if (version_p == NULL) { 2430 | PyErr_Format(PyExc_KeyError, 2431 | "No \"%s\" in pickled dict.", 2432 | pickle_version_key); 2433 | 2434 | return (NULL); 2435 | } 2436 | 2437 | version = (int)PyLong_AsLong(version_p); 2438 | 2439 | if (version != pickle_version) { 2440 | PyErr_Format(PyExc_ValueError, 2441 | "Pickle version mismatch. Got version %d but expected version %d.", 2442 | version, pickle_version); 2443 | 2444 | return (NULL); 2445 | } 2446 | 2447 | format_p = PyDict_GetItemString(state_p, "format"); 2448 | 2449 | if (format_p == NULL) { 2450 | PyErr_SetString(PyExc_KeyError, "No \"format\" in pickled dict."); 2451 | 2452 | return (NULL); 2453 | } 2454 | 2455 | names_p = PyDict_GetItemString(state_p, "names"); 2456 | 2457 | if (names_p == NULL) { 2458 | PyErr_SetString(PyExc_KeyError, "No \"names\" in pickled dict."); 2459 | 2460 | return (NULL); 2461 | } 2462 | 2463 | if (compiled_format_dict_init_inner(self_p, format_p, names_p) != 0) { 2464 | return (NULL); 2465 | } 2466 | 2467 | Py_RETURN_NONE; 2468 | } 2469 | 2470 | static PyObject *m_compile(PyObject *module_p, 2471 | PyObject *args_p, 2472 | PyObject *kwargs_p) 2473 | { 2474 | PyObject *format_p; 2475 | PyObject *names_p; 2476 | int res; 2477 | static char *keywords[] = { 2478 | "fmt", 2479 | "names", 2480 | NULL 2481 | }; 2482 | 2483 | names_p = Py_None; 2484 | res = PyArg_ParseTupleAndKeywords(args_p, 2485 | kwargs_p, 2486 | "O|O", 2487 | &keywords[0], 2488 | &format_p, 2489 | &names_p); 2490 | 2491 | if (res == 0) { 2492 | return (NULL); 2493 | } 2494 | 2495 | if (names_p == Py_None) { 2496 | return (compiled_format_create(&compiled_format_type, format_p)); 2497 | } else { 2498 | return (compiled_format_dict_create(&compiled_format_dict_type, 2499 | format_p, 2500 | names_p)); 2501 | } 2502 | } 2503 | 2504 | static struct PyMethodDef methods[] = { 2505 | { 2506 | "pack", 2507 | m_pack, 2508 | METH_VARARGS, 2509 | pack___doc__ 2510 | }, 2511 | { 2512 | "unpack", 2513 | (PyCFunction)m_unpack, 2514 | METH_VARARGS | METH_KEYWORDS, 2515 | unpack___doc__ 2516 | }, 2517 | { 2518 | "pack_into", 2519 | (PyCFunction)m_pack_into, 2520 | METH_VARARGS | METH_KEYWORDS, 2521 | pack_into___doc__ 2522 | }, 2523 | { 2524 | "unpack_from", 2525 | (PyCFunction)m_unpack_from, 2526 | METH_VARARGS | METH_KEYWORDS, 2527 | unpack_from___doc__ 2528 | }, 2529 | { 2530 | "pack_dict", 2531 | m_pack_dict, 2532 | METH_VARARGS, 2533 | pack_dict___doc__ 2534 | }, 2535 | { 2536 | "unpack_dict", 2537 | (PyCFunction)m_unpack_dict, 2538 | METH_VARARGS | METH_KEYWORDS, 2539 | unpack_dict___doc__ 2540 | }, 2541 | { 2542 | "pack_into_dict", 2543 | (PyCFunction)m_pack_into_dict, 2544 | METH_VARARGS | METH_KEYWORDS, 2545 | pack_into_dict___doc__ 2546 | }, 2547 | { 2548 | "unpack_from_dict", 2549 | (PyCFunction)m_unpack_from_dict, 2550 | METH_VARARGS | METH_KEYWORDS, 2551 | unpack_from_dict___doc__ 2552 | }, 2553 | { 2554 | "calcsize", 2555 | m_calcsize, 2556 | METH_O, 2557 | calcsize___doc__ 2558 | }, 2559 | { 2560 | "byteswap", 2561 | (PyCFunction)m_byteswap, 2562 | METH_VARARGS | METH_KEYWORDS, 2563 | byteswap___doc__ 2564 | }, 2565 | { 2566 | "compile", 2567 | (PyCFunction)m_compile, 2568 | METH_VARARGS | METH_KEYWORDS, 2569 | compile___doc__ 2570 | }, 2571 | { NULL } 2572 | }; 2573 | 2574 | static PyModuleDef module = { 2575 | PyModuleDef_HEAD_INIT, 2576 | .m_name = "bitstruct.c", 2577 | .m_doc = "bitstruct C extension", 2578 | .m_size = -1, 2579 | .m_methods = methods 2580 | }; 2581 | 2582 | PyMODINIT_FUNC PyInit_c(void) 2583 | { 2584 | PyObject *module_p; 2585 | 2586 | if (PyType_Ready(&compiled_format_type) < 0) { 2587 | return (NULL); 2588 | } 2589 | 2590 | if (PyType_Ready(&compiled_format_dict_type) < 0) { 2591 | return (NULL); 2592 | } 2593 | 2594 | py_zero_p = PyLong_FromLong(0); 2595 | module_p = PyModule_Create(&module); 2596 | 2597 | if (module_p == NULL) { 2598 | return (NULL); 2599 | } 2600 | 2601 | Py_INCREF(&compiled_format_type); 2602 | 2603 | if (PyModule_AddObject(module_p, 2604 | "CompiledFormat", 2605 | (PyObject *)&compiled_format_type) < 0) { 2606 | Py_DECREF(&compiled_format_type); 2607 | Py_DECREF(module_p); 2608 | 2609 | return (NULL); 2610 | } 2611 | 2612 | if (PyModule_AddObject(module_p, 2613 | "CompiledFormatDict", 2614 | (PyObject *)&compiled_format_dict_type) < 0) { 2615 | Py_DECREF(&compiled_format_dict_type); 2616 | Py_DECREF(module_p); 2617 | 2618 | return (NULL); 2619 | } 2620 | 2621 | return (module_p); 2622 | } 2623 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eerimoq/bitstruct/a1084e614f5d48842ae6878c0a3d9e254bd24608/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_bitstruct.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import timeit 4 | import unittest 5 | from bitstruct import * 6 | import bitstruct 7 | 8 | class BitStructTest(unittest.TestCase): 9 | 10 | def test_pack(self): 11 | """Pack values. 12 | 13 | """ 14 | 15 | packed = pack('u1u1s6u7u9', 0, 0, -2, 65, 22) 16 | self.assertEqual(packed, b'\x3e\x82\x16') 17 | 18 | packed = pack('u1', 1) 19 | self.assertEqual(packed, b'\x80') 20 | 21 | packed = pack('u77', 0x100000000001000000) 22 | ref = b'\x00\x80\x00\x00\x00\x00\x08\x00\x00\x00' 23 | self.assertEqual(packed, ref) 24 | 25 | packed = pack('u8000', int(8000 * '1', 2)) 26 | ref = 1000 * b'\xff' 27 | self.assertEqual(packed, ref) 28 | 29 | packed = pack('s4000', int(8000 * '0', 2)) 30 | ref = 500 * b'\x00' 31 | self.assertEqual(packed, ref) 32 | 33 | packed = pack('p1u1s6u7u9', 0, -2, 65, 22) 34 | self.assertEqual(packed, b'\x3e\x82\x16') 35 | 36 | packed = pack('P1u1s6u7u9', 0, -2, 65, 22) 37 | self.assertEqual(packed, b'\xbe\x82\x16') 38 | 39 | packed = pack('p1u1s6p7u9', 0, -2, 22) 40 | self.assertEqual(packed, b'\x3e\x00\x16') 41 | 42 | packed = pack('P1u1s6p7u9', 0, -2, 22) 43 | self.assertEqual(packed, b'\xbe\x00\x16') 44 | 45 | packed = pack('u1s6f32r43', 0, -2, 3.75, b'\x00\xff\x00\xff\x00\xff') 46 | self.assertEqual(packed, b'\x7c\x80\xe0\x00\x00\x01\xfe\x01\xfe\x01\xc0') 47 | 48 | packed = pack('b1', True) 49 | self.assertEqual(packed, b'\x80') 50 | 51 | packed = pack('b1p6b1', True, True) 52 | self.assertEqual(packed, b'\x81') 53 | 54 | packed = pack('b1P6b1', True, True) 55 | self.assertEqual(packed, b'\xff') 56 | 57 | packed = pack('u5b2u1', 31, False, 1) 58 | self.assertEqual(packed, b'\xf9') 59 | 60 | packed = pack('b1t24', False, u'Hi!') 61 | self.assertEqual(packed, b'$4\x90\x80') 62 | 63 | packed = pack('b1t24', False, 'Hi!') 64 | self.assertEqual(packed, b'$4\x90\x80') 65 | 66 | packed = pack('t8000', 1000 * '7') 67 | self.assertEqual(packed, 1000 * b'\x37') 68 | 69 | if sys.version_info >= (3, 6): 70 | packed = pack('f16', 1.0) 71 | self.assertEqual(packed, b'\x3c\x00') 72 | 73 | packed = pack('f32', 1.0) 74 | self.assertEqual(packed, b'\x3f\x80\x00\x00') 75 | 76 | packed = pack('f64', 1.0) 77 | self.assertEqual(packed, b'\x3f\xf0\x00\x00\x00\x00\x00\x00') 78 | 79 | # Too many values to pack. 80 | with self.assertRaises(Error) as cm: 81 | pack('b1t24', False) 82 | 83 | self.assertEqual(str(cm.exception), 84 | 'pack expected 2 item(s) for packing (got 1)') 85 | 86 | # Cannot convert argument to integer. 87 | with self.assertRaises(ValueError) as cm: 88 | pack('u1', 'foo') 89 | 90 | self.assertEqual(str(cm.exception), 91 | "invalid literal for int() with base 10: 'foo'") 92 | 93 | # Cannot convert argument to float. 94 | with self.assertRaises(ValueError) as cm: 95 | pack('f32', 'foo') 96 | 97 | if sys.version_info[0] < 3: 98 | self.assertEqual(str(cm.exception), 99 | 'could not convert string to float: foo') 100 | else: 101 | self.assertEqual(str(cm.exception), 102 | "could not convert string to float: 'foo'") 103 | 104 | # Cannot convert argument to bytearray. 105 | with self.assertRaises(TypeError) as cm: 106 | pack('r5', 1.0) 107 | 108 | self.assertIn("'float' has no", str(cm.exception)) 109 | 110 | # Cannot encode argument as utf-8. 111 | with self.assertRaises(AttributeError) as cm: 112 | pack('t8', 1.0) 113 | 114 | self.assertEqual(str(cm.exception), 115 | "'float' object has no attribute 'encode'") 116 | 117 | def test_unpack(self): 118 | """Unpack values. 119 | 120 | """ 121 | 122 | unpacked = unpack('u1u1s6u7u9', b'\x3e\x82\x16') 123 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 124 | 125 | unpacked = unpack('u1', bytearray(b'\x80')) 126 | self.assertEqual(unpacked, (1, )) 127 | 128 | packed = b'\x00\x80\x00\x00\x00\x00\x08\x00\x00\x00' 129 | unpacked = unpack('u77', packed) 130 | self.assertEqual(unpacked, (0x100000000001000000,)) 131 | 132 | packed = 1000 * b'\xff' 133 | unpacked = unpack('u8000', packed) 134 | self.assertEqual(unpacked, (int(8000 * '1', 2), )) 135 | 136 | packed = 500 * b'\x00' 137 | unpacked = unpack('s4000', packed) 138 | self.assertEqual(unpacked, (0, )) 139 | 140 | packed = b'\xbe\x82\x16' 141 | unpacked = unpack('P1u1s6u7u9', packed) 142 | self.assertEqual(unpacked, (0, -2, 65, 22)) 143 | 144 | packed = b'\x3e\x82\x16' 145 | unpacked = unpack('P1u1s6u7u9', packed) 146 | self.assertEqual(unpacked, (0, -2, 65, 22)) 147 | 148 | packed = b'\xbe\x82\x16' 149 | unpacked = unpack('p1u1s6u7u9', packed) 150 | self.assertEqual(unpacked, (0, -2, 65, 22)) 151 | 152 | packed = b'\x3e\x82\x16' 153 | unpacked = unpack('p1u1s6u7u9', packed) 154 | self.assertEqual(unpacked, (0, -2, 65, 22)) 155 | 156 | packed = b'\x3e\x82\x16' 157 | unpacked = unpack('p1u1s6p7u9', packed) 158 | self.assertEqual(unpacked, (0, -2, 22)) 159 | 160 | packed = b'\x7c\x80\xe0\x00\x00\x01\xfe\x01\xfe\x01\xc0' 161 | unpacked = unpack('u1s6f32r43', packed) 162 | self.assertEqual(unpacked, (0, -2, 3.75, b'\x00\xff\x00\xff\x00\xe0')) 163 | 164 | packed = bytearray(b'\x80') 165 | unpacked = unpack('b1', packed) 166 | self.assertEqual(unpacked, (True, )) 167 | 168 | packed = b'\x80' 169 | unpacked = unpack('b1p6b1', packed) 170 | self.assertEqual(unpacked, (True, False)) 171 | 172 | packed = b'\x06' 173 | unpacked = unpack('u5b2u1', packed) 174 | self.assertEqual(unpacked, (0, True, 0)) 175 | 176 | packed = b'\x04' 177 | unpacked = unpack('u5b2u1', packed) 178 | self.assertEqual(unpacked, (0, True, 0)) 179 | 180 | packed = b'$4\x90\x80' 181 | unpacked = unpack('b1t24', packed) 182 | self.assertEqual(unpacked, (False, u'Hi!')) 183 | 184 | packed = 1000 * b'7' 185 | unpacked = unpack('t8000', packed) 186 | self.assertEqual(packed, 1000 * b'\x37') 187 | 188 | # Bad float size. 189 | with self.assertRaises(Error) as cm: 190 | unpack('f33', b'\x00\x00\x00\x00\x00') 191 | 192 | self.assertEqual(str(cm.exception), 193 | 'expected float size of 16, 32, or 64 bits (got 33)') 194 | 195 | # Too many bits to unpack. 196 | with self.assertRaises(Error) as cm: 197 | unpack('u9', b'\x00') 198 | 199 | # partial unpacking of truncated data 200 | unpacked = unpack('u4u5', b'\x5f', allow_truncated=True) 201 | self.assertEqual(unpacked, (5, )) 202 | unpacked = unpack('p8u4u5', b'\x00\x5f', allow_truncated=True) 203 | self.assertEqual(unpacked, (5, )) 204 | 205 | self.assertEqual(str(cm.exception), 206 | 'unpack requires at least 9 bits to unpack (got 8)') 207 | 208 | # gcc packed struct with bitfields 209 | # 210 | # struct foo_t { 211 | # int a; 212 | # char b; 213 | # uint32_t c : 7; 214 | # uint32_t d : 25; 215 | # } foo; 216 | # 217 | # foo.a = 1; 218 | # foo.b = 1; 219 | # foo.c = 0x67; 220 | # foo.d = 0x12345; 221 | unpacked = unpack('s32s8u25u7', 222 | byteswap('414', 223 | b'\x01\x00\x00\x00\x01\xe7\xa2\x91\x00')) 224 | self.assertEqual(unpacked, (1, 1, 0x12345, 0x67)) 225 | 226 | def test_pack_unpack(self): 227 | """Pack and unpack values. 228 | 229 | """ 230 | 231 | packed = pack('u1u1s6u7u9', 0, 0, -2, 65, 22) 232 | unpacked = unpack('u1u1s6u7u9', packed) 233 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 234 | 235 | packed = pack('f64', 1.0) 236 | unpacked = unpack('f64', packed) 237 | self.assertEqual(unpacked, (1.0, )) 238 | 239 | if sys.version_info >= (3, 6): 240 | packed = pack('f16', 1.0) 241 | unpacked = unpack('f16', packed) 242 | self.assertEqual(unpacked, (1.0, )) 243 | 244 | def test_calcsize(self): 245 | """Calculate size. 246 | 247 | """ 248 | 249 | size = calcsize('u1u1s6u7u9') 250 | self.assertEqual(size, 24) 251 | 252 | size = calcsize('u1') 253 | self.assertEqual(size, 1) 254 | 255 | size = calcsize('u77') 256 | self.assertEqual(size, 77) 257 | 258 | size = calcsize('u1s6u7u9') 259 | self.assertEqual(size, 23) 260 | 261 | size = calcsize('b1s6u7u9p1t8') 262 | self.assertEqual(size, 32) 263 | 264 | size = calcsize('b1s6u7u9P1t8') 265 | self.assertEqual(size, 32) 266 | 267 | def test_byteswap(self): 268 | """Byte swap. 269 | 270 | """ 271 | 272 | res = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a' 273 | ref = b'\x01\x03\x02\x04\x08\x07\x06\x05\x0a\x09' 274 | self.assertEqual(byteswap('12142', ref), res) 275 | 276 | packed = pack('u1u5u2u16', 1, 2, 3, 4) 277 | unpacked = unpack('u1u5u2u16', byteswap('12', packed)) 278 | self.assertEqual(unpacked, (1, 2, 3, 1024)) 279 | 280 | def test_endianness(self): 281 | """Test pack/unpack with endianness information in the format string. 282 | 283 | """ 284 | 285 | # Big endian. 286 | ref = b'\x02\x46\x9a\xfe\x00\x00\x00' 287 | packed = pack('>u19s3f32', 0x1234, -2, -1.0) 288 | self.assertEqual(packed, ref) 289 | unpacked = unpack('>u19s3f32', packed) 290 | self.assertEqual(unpacked, (0x1234, -2, -1.0)) 291 | 292 | # Little endian. 293 | ref = b'\x2c\x48\x0c\x00\x00\x07\xf4' 294 | packed = pack('u19f64r3p4', 1, -2, 1.0, b'\x80') 302 | self.assertEqual(packed, ref) 303 | unpacked = unpack('>u19f64r3p4', packed) 304 | self.assertEqual(unpacked, (1, -2, 1.0, b'\x80')) 305 | 306 | # Opposite endianness of the 'mixed endianness' test. 307 | ref = b'\x80\x00\x1e\x00\x00\x00\x00\x00\x00\x0f\xfc\x20' 308 | packed = pack('s5s5', 0x1234, -2, -1.0) 328 | self.assertEqual(packed, ref) 329 | unpacked = unpack('u19s3f32>', packed) 330 | self.assertEqual(unpacked, (0x1234, -2, -1.0)) 331 | 332 | # Least significant byte first. 333 | ref = b'\x34\x12\x18\x00\x00\xe0\xbc' 334 | packed = pack('u19s3f32<', 0x1234, -2, -1.0) 335 | self.assertEqual(packed, ref) 336 | unpacked = unpack('u19s3f32<', packed) 337 | self.assertEqual(unpacked, (0x1234, -2, -1.0)) 338 | 339 | # Least significant byte first. 340 | ref = b'\x34\x12' 341 | packed = pack('u8s8<', 0x34, 0x12) 342 | self.assertEqual(packed, ref) 343 | unpacked = unpack('u8s8<', packed) 344 | self.assertEqual(unpacked, (0x34, 0x12)) 345 | 346 | # Least significant byte first. 347 | ref = b'\x34\x22' 348 | packed = pack('u3u12<', 1, 0x234) 349 | self.assertEqual(packed, ref) 350 | unpacked = unpack('u3s12<', packed) 351 | self.assertEqual(unpacked, (1, 0x234)) 352 | 353 | # Least significant byte first. 354 | ref = b'\x34\x11\x00' 355 | packed = pack('u3u17<', 1, 0x234) 356 | self.assertEqual(packed, ref) 357 | unpacked = unpack('u3s17<', packed) 358 | self.assertEqual(unpacked, (1, 0x234)) 359 | 360 | # Least significant byte first. 361 | ref = b'\x80' 362 | packed = pack('u1<', 1) 363 | self.assertEqual(packed, ref) 364 | unpacked = unpack('u1<', packed) 365 | self.assertEqual(unpacked, (1, )) 366 | 367 | # Least significant byte first. 368 | ref = b'\x45\x23\x25\x82' 369 | packed = pack('u19u5u1u7<', 0x12345, 5, 1, 2) 370 | self.assertEqual(packed, ref) 371 | unpacked = unpack('u19u5u1u7<', packed) 372 | self.assertEqual(unpacked, (0x12345, 5, 1, 2)) 373 | 374 | # Least significant byte first does not affect raw and text. 375 | ref = b'123abc' 376 | packed = pack('r24t24<', b'123', 'abc') 377 | self.assertEqual(packed, ref) 378 | unpacked = unpack('r24t24<', packed) 379 | self.assertEqual(unpacked, (b'123', 'abc')) 380 | 381 | def test_compile(self): 382 | cf = bitstruct.compile('u1u1s6u7u9') 383 | 384 | packed = cf.pack(0, 0, -2, 65, 22) 385 | self.assertEqual(packed, b'\x3e\x82\x16') 386 | 387 | unpacked = cf.unpack(b'\x3e\x82\x16') 388 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 389 | 390 | def test_signed_integer(self): 391 | """Pack and unpack signed integer values. 392 | 393 | """ 394 | 395 | datas = [ 396 | ('s2', 0x01, b'\x40'), 397 | ('s3', 0x03, b'\x60'), 398 | ('s4', 0x07, b'\x70'), 399 | ('s5', 0x0f, b'\x78'), 400 | ('s6', 0x1f, b'\x7c'), 401 | ('s7', 0x3f, b'\x7e'), 402 | ('s8', 0x7f, b'\x7f'), 403 | ('s9', 0xff, b'\x7f\x80'), 404 | ('s1', -1, b'\x80'), 405 | ('s2', -1, b'\xc0') 406 | ] 407 | 408 | for fmt, value, packed in datas: 409 | self.assertEqual(pack(fmt, value), packed) 410 | self.assertEqual(unpack(fmt, packed), (value, )) 411 | 412 | def test_unsigned_integer(self): 413 | """Pack and unpack unsigned integer values. 414 | 415 | """ 416 | 417 | datas = [ 418 | ('u1', 0x001, b'\x80'), 419 | ('u2', 0x003, b'\xc0'), 420 | ('u3', 0x007, b'\xe0'), 421 | ('u4', 0x00f, b'\xf0'), 422 | ('u5', 0x01f, b'\xf8'), 423 | ('u6', 0x03f, b'\xfc'), 424 | ('u7', 0x07f, b'\xfe'), 425 | ('u8', 0x0ff, b'\xff'), 426 | ('u9', 0x1ff, b'\xff\x80') 427 | ] 428 | 429 | for fmt, value, packed in datas: 430 | self.assertEqual(pack(fmt, value), packed) 431 | self.assertEqual(unpack(fmt, packed), (value, )) 432 | 433 | def test_bad_float_size(self): 434 | """Test of bad float size. 435 | 436 | """ 437 | 438 | with self.assertRaises(Error) as cm: 439 | pack('f31', 1.0) 440 | 441 | self.assertEqual(str(cm.exception), 442 | 'expected float size of 16, 32, or 64 bits (got 31)') 443 | 444 | with self.assertRaises(Error) as cm: 445 | unpack('f33', 8 * b'\x00') 446 | 447 | self.assertEqual(str(cm.exception), 448 | 'expected float size of 16, 32, or 64 bits (got 33)') 449 | 450 | def test_bad_format(self): 451 | """Test of bad format. 452 | 453 | """ 454 | 455 | formats = [ 456 | ('g1', "bad char 'g' in format"), 457 | ('s1u1f32b1t8r8G13', "bad char 'G' in format"), 458 | ('s1u1f32b1t8r8G13S3', "bad char 'G' in format"), 459 | ('s', "bad format 's'"), 460 | ('1', "bad format '1'"), 461 | ('ss1', "bad format 'ss1'"), 462 | ('1s', "bad format '1s'"), 463 | ('foo', "bad format 'foo'"), 464 | ('s>1>', "bad format 's>1>'"), 465 | ('s0', "bad format 's0'") 466 | ] 467 | 468 | for fmt, expected_error in formats: 469 | with self.assertRaises(Error) as cm: 470 | bitstruct.compile(fmt) 471 | 472 | self.assertEqual(str(cm.exception), expected_error) 473 | 474 | def test_empty_format(self): 475 | """Test of empty format type. 476 | 477 | """ 478 | 479 | cf = bitstruct.compile('') 480 | 481 | self.assertEqual(cf.pack(), b'') 482 | self.assertEqual(cf.pack(1), b'') 483 | 484 | self.assertEqual(cf.unpack(b''), ()) 485 | self.assertEqual(cf.unpack(b'\x00'), ()) 486 | 487 | def test_byte_order_format(self): 488 | """Test of a format with only byte order information. 489 | 490 | """ 491 | 492 | cf = bitstruct.compile('>') 493 | 494 | self.assertEqual(cf.pack(), b'') 495 | self.assertEqual(cf.pack(1), b'') 496 | 497 | self.assertEqual(cf.unpack(b''), ()) 498 | self.assertEqual(cf.unpack(b'\x00'), ()) 499 | 500 | def test_pack_into(self): 501 | """Pack values into a buffer. 502 | 503 | """ 504 | 505 | packed = bytearray(3) 506 | pack_into('u1u1s6u7u9', packed, 0, 0, 0, -2, 65, 22) 507 | self.assertEqual(packed, b'\x3e\x82\x16') 508 | 509 | datas = [ 510 | (0, b'\x80\x00'), 511 | (1, b'\x40\x00'), 512 | (7, b'\x01\x00'), 513 | (15, b'\x00\x01') 514 | ] 515 | 516 | for offset, expected in datas: 517 | packed = bytearray(2) 518 | pack_into('u1', packed, offset, 1) 519 | self.assertEqual(packed, expected) 520 | 521 | packed = bytearray(b'\xff\xff\xff') 522 | pack_into('p4u4p4u4p4u4', packed, 0, 1, 2, 3, fill_padding=False) 523 | self.assertEqual(packed, b'\xf1\xf2\xf3') 524 | 525 | packed = bytearray(b'\xff\xff\xff') 526 | pack_into('p4u4p4u4p4u4', packed, 0, 1, 2, 3, fill_padding=True) 527 | self.assertEqual(packed, b'\x01\x02\x03') 528 | 529 | packed = bytearray(2) 530 | 531 | with self.assertRaises(Error) as cm: 532 | pack_into('u17', packed, 0, 1) 533 | 534 | self.assertEqual(str(cm.exception), 535 | 'pack_into requires a buffer of at least 17 bits') 536 | 537 | packed = bytearray(b'\x00') 538 | pack_into('P4u4', packed, 0, 1) 539 | self.assertEqual(packed, b'\xf1') 540 | 541 | # Too many values to pack. 542 | with self.assertRaises(Error) as cm: 543 | packed = bytearray(b'\x00') 544 | pack_into('b1t24', packed, 0, False) 545 | 546 | self.assertEqual(str(cm.exception), 547 | 'pack expected 2 item(s) for packing (got 1)') 548 | 549 | def test_unpack_from(self): 550 | """Unpack values at given bit offset. 551 | 552 | """ 553 | 554 | unpacked = unpack_from('u1u1s6u7u9', b'\x1f\x41\x0b\x00', 1) 555 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 556 | 557 | with self.assertRaises(Error) as cm: 558 | unpack_from('u1u1s6u7u9', b'\x1f\x41\x0b', 1) 559 | 560 | self.assertEqual(str(cm.exception), 561 | 'unpack requires at least 24 bits to unpack ' 562 | '(got 23)') 563 | 564 | def test_pack_integers_value_checks(self): 565 | """Pack integer values range checks. 566 | 567 | """ 568 | 569 | # Formats with minimum and maximum allowed values. 570 | datas = [ 571 | ('s1', -1, 0), 572 | ('s2', -2, 1), 573 | ('s3', -4, 3), 574 | ('u1', 0, 1), 575 | ('u2', 0, 3), 576 | ('u3', 0, 7) 577 | ] 578 | 579 | for fmt, minimum, maximum in datas: 580 | # No exception should be raised for numbers in range. 581 | pack(fmt, minimum) 582 | pack(fmt, maximum) 583 | 584 | # Numbers out of range. 585 | for number in [minimum - 1, maximum + 1]: 586 | with self.assertRaises(Error) as cm: 587 | pack(fmt, number) 588 | 589 | self.assertEqual( 590 | str(cm.exception), 591 | '"{}" requires {} <= integer <= {} (got {})'.format( 592 | fmt, 593 | minimum, 594 | maximum, 595 | number)) 596 | 597 | def test_pack_unpack_raw(self): 598 | """Pack and unpack raw values. 599 | 600 | """ 601 | 602 | packed = pack('r24', b'') 603 | self.assertEqual(packed, b'\x00\x00\x00') 604 | packed = pack('r24', b'12') 605 | self.assertEqual(packed, b'12\x00') 606 | packed = pack('r24', b'123') 607 | self.assertEqual(packed, b'123') 608 | packed = pack('r24', b'1234') 609 | self.assertEqual(packed, b'123') 610 | 611 | unpacked = unpack('r24', b'\x00\x00\x00')[0] 612 | self.assertEqual(unpacked, b'\x00\x00\x00') 613 | unpacked = unpack('r24', b'12\x00')[0] 614 | self.assertEqual(unpacked, b'12\x00') 615 | unpacked = unpack('r24', b'123')[0] 616 | self.assertEqual(unpacked, b'123') 617 | unpacked = unpack('r24', b'1234')[0] 618 | self.assertEqual(unpacked, b'123') 619 | 620 | def test_pack_unpack_text(self): 621 | """Pack and unpack text values. 622 | 623 | """ 624 | 625 | packed = pack('t24', '') 626 | self.assertEqual(packed, b'\x00\x00\x00') 627 | packed = pack('t24', '12') 628 | self.assertEqual(packed, b'12\x00') 629 | packed = pack('t24', '123') 630 | self.assertEqual(packed, b'123') 631 | packed = pack('t24', '1234') 632 | self.assertEqual(packed, b'123') 633 | 634 | unpacked = unpack('t24', b'\x00\x00\x00')[0] 635 | self.assertEqual(unpacked, '\x00\x00\x00') 636 | unpacked = unpack('t24', b'12\x00')[0] 637 | self.assertEqual(unpacked, '12\x00') 638 | unpacked = unpack('t24', b'123')[0] 639 | self.assertEqual(unpacked, '123') 640 | unpacked = unpack('t24', b'1234')[0] 641 | self.assertEqual(unpacked, '123') 642 | 643 | unpacked = unpack('t8', b'\xff', text_errors='replace')[0] 644 | self.assertEqual(unpacked, '�') 645 | unpacked = unpack('t8', b'\xff', text_errors='ignore')[0] 646 | self.assertEqual(unpacked, '') 647 | 648 | cf = bitstruct.compile('t8', text_encoding='latin-1', text_errors='replace') 649 | unpacked = cf.unpack(b'\xff')[0] 650 | self.assertEqual(unpacked, 'ÿ') 651 | 652 | cf = bitstruct.compile('t8', text_encoding='utf-8', text_errors='ignore') 653 | unpacked = cf.unpack(b'\xff')[0] 654 | self.assertEqual(unpacked, '') 655 | 656 | cf = bitstruct.compile('t8', 657 | names=['a'], 658 | text_encoding='utf-8', 659 | text_errors='replace') 660 | unpacked = cf.unpack(b'\xff') 661 | self.assertEqual(unpacked, {'a': '�'}) 662 | 663 | def test_pack_unpack_dict(self): 664 | unpacked = { 665 | 'foo': 0, 666 | 'bar': 0, 667 | 'fie': -2, 668 | 'fum': 65, 669 | 'fam': 22 670 | } 671 | packed = b'\x3e\x82\x16' 672 | fmt = 'u1u1s6u7u9' 673 | names = ['foo', 'bar', 'fie', 'fum', 'fam'] 674 | 675 | self.assertEqual(pack_dict(fmt, names, unpacked), packed) 676 | self.assertEqual(unpack_dict(fmt, names, packed), unpacked) 677 | 678 | def test_pack_into_unpack_from_dict(self): 679 | unpacked = { 680 | 'foo': 0, 681 | 'bar': 0, 682 | 'fie': -2, 683 | 'fum': 65, 684 | 'fam': 22 685 | } 686 | packed = b'\x3e\x82\x16' 687 | fmt = 'u1u1s6u7u9' 688 | names = ['foo', 'bar', 'fie', 'fum', 'fam'] 689 | 690 | actual = bytearray(3) 691 | pack_into_dict(fmt, names, actual, 0, unpacked) 692 | self.assertEqual(actual, packed) 693 | self.assertEqual(unpack_from_dict(fmt, names, packed), unpacked) 694 | 695 | def test_pack_dict_missing_key(self): 696 | unpacked = { 697 | 'foo': 0, 698 | 'bar': 0, 699 | 'fie': -2, 700 | 'fum': 65 701 | } 702 | fmt = 'u1u1s6u7u9' 703 | names = ['foo', 'bar', 'fie', 'fum', 'fam'] 704 | 705 | with self.assertRaises(Error) as cm: 706 | pack_dict(fmt, names, unpacked) 707 | 708 | self.assertEqual(str(cm.exception), 709 | "'fam' not found in data dictionary") 710 | 711 | with self.assertRaises(Error) as cm: 712 | data = bytearray(3) 713 | pack_into_dict(fmt, names, data, 0, unpacked) 714 | 715 | self.assertEqual(str(cm.exception), 716 | "'fam' not found in data dictionary") 717 | 718 | def test_compile_pack_unpack_formats(self): 719 | fmts = [ 720 | ('u1s2p3', None, (1, -1)), 721 | ('u1 s2 p3', None, (1, -1)), 722 | ('u1s2p3', ['a', 'b'], {'a': 1, 'b': -1}) 723 | ] 724 | 725 | for fmt, names, decoded in fmts: 726 | if names is None: 727 | cf = bitstruct.compile(fmt) 728 | packed_1 = cf.pack(*decoded) 729 | packed_2 = pack(fmt, *decoded) 730 | else: 731 | cf = bitstruct.compile(fmt, names) 732 | packed_1 = cf.pack(decoded) 733 | packed_2 = pack_dict(fmt, names, decoded) 734 | 735 | self.assertEqual(packed_1, b'\xe0') 736 | self.assertEqual(packed_2, b'\xe0') 737 | 738 | def test_compile_formats(self): 739 | bitstruct.compile('p1u1') 740 | bitstruct.compile('p1u1', ['a']) 741 | 742 | def test_pack_unpack_signed(self): 743 | datas = [ 744 | ('s1', 0, b'\x00'), 745 | ('s1', -1, b'\x80'), 746 | ('s63', -1, b'\xff\xff\xff\xff\xff\xff\xff\xfe'), 747 | ('s64', -1, b'\xff\xff\xff\xff\xff\xff\xff\xff') 748 | ] 749 | 750 | for fmt, value, packed in datas: 751 | self.assertEqual(pack(fmt, value), packed) 752 | self.assertEqual(unpack(fmt, packed), (value, )) 753 | 754 | def test_pack_unpack_unsigned(self): 755 | datas = [ 756 | ('u1', 0, b'\x00'), 757 | ('u1', 1, b'\x80'), 758 | ('u63', 0x1234567890abcdef, b'$h\xac\xf1!W\x9b\xde'), 759 | ('u64', 0x1234567890abcdef, b'\x124Vx\x90\xab\xcd\xef') 760 | ] 761 | 762 | for fmt, value, packed in datas: 763 | self.assertEqual(pack(fmt, value), packed) 764 | self.assertEqual(unpack(fmt, packed), (value, )) 765 | 766 | 767 | if __name__ == '__main__': 768 | unittest.main() 769 | -------------------------------------------------------------------------------- /tests/test_c.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import timeit 4 | import pickle 5 | import unittest 6 | import platform 7 | import copy 8 | 9 | 10 | def is_cpython_3(): 11 | if platform.python_implementation() != 'CPython': 12 | return False 13 | 14 | if sys.version_info[0] < 3: 15 | return False 16 | 17 | return True 18 | 19 | 20 | if is_cpython_3(): 21 | import bitstruct.c 22 | from bitstruct.c import * 23 | else: 24 | print('Skipping C extension tests for non-CPython 3.') 25 | 26 | 27 | class CTest(unittest.TestCase): 28 | 29 | def test_pack(self): 30 | """Pack values. 31 | 32 | """ 33 | 34 | if not is_cpython_3(): 35 | return 36 | 37 | packed = pack('u1u1s6u7u9', 0, 0, -2, 65, 22) 38 | self.assertEqual(packed, b'\x3e\x82\x16') 39 | 40 | packed = pack('u1', 1) 41 | self.assertEqual(packed, b'\x80') 42 | 43 | with self.assertRaises(NotImplementedError): 44 | packed = pack('u77', 0x100000000001000000) 45 | ref = b'\x00\x80\x00\x00\x00\x00\x08\x00\x00\x00' 46 | self.assertEqual(packed, ref) 47 | 48 | with self.assertRaises(NotImplementedError): 49 | packed = pack('u8000', int(8000 * '1', 2)) 50 | ref = 1000 * b'\xff' 51 | self.assertEqual(packed, ref) 52 | 53 | with self.assertRaises(NotImplementedError): 54 | packed = pack('s4000', int(8000 * '0', 2)) 55 | ref = 500 * b'\x00' 56 | self.assertEqual(packed, ref) 57 | 58 | packed = pack('p1u1s6u7u9', 0, -2, 65, 22) 59 | self.assertEqual(packed, b'\x3e\x82\x16') 60 | 61 | packed = pack('P1u1s6u7u9', 0, -2, 65, 22) 62 | self.assertEqual(packed, b'\xbe\x82\x16') 63 | 64 | packed = pack('p1u1s6p7u9', 0, -2, 22) 65 | self.assertEqual(packed, b'\x3e\x00\x16') 66 | 67 | packed = pack('P1u1s6p7u9', 0, -2, 22) 68 | self.assertEqual(packed, b'\xbe\x00\x16') 69 | 70 | with self.assertRaises(NotImplementedError): 71 | packed = pack('u1s6f32r43', 0, -2, 3.75, b'\x00\xff\x00\xff\x00\xff') 72 | self.assertEqual(packed, b'\x7c\x80\xe0\x00\x00\x01\xfe\x01\xfe\x01\xc0') 73 | 74 | packed = pack('b1', True) 75 | self.assertEqual(packed, b'\x80') 76 | 77 | packed = pack('b1p6b1', True, True) 78 | self.assertEqual(packed, b'\x81') 79 | 80 | packed = pack('b1P6b1', True, True) 81 | self.assertEqual(packed, b'\xff') 82 | 83 | packed = pack('u5b2u1', 31, False, 1) 84 | self.assertEqual(packed, b'\xf9') 85 | 86 | packed = pack('b1t24', False, u'Hi!') 87 | self.assertEqual(packed, b'$4\x90\x80') 88 | 89 | packed = pack('b1t24', False, 'Hi!') 90 | self.assertEqual(packed, b'$4\x90\x80') 91 | 92 | packed = pack('t8000', 1000 * '7') 93 | self.assertEqual(packed, 1000 * b'\x37') 94 | 95 | if sys.version_info >= (3, 6): 96 | packed = pack('f16', 1.0) 97 | self.assertEqual(packed, b'\x3c\x00') 98 | 99 | packed = pack('f32', 1.0) 100 | self.assertEqual(packed, b'\x3f\x80\x00\x00') 101 | 102 | packed = pack('f64', 1.0) 103 | self.assertEqual(packed, b'\x3f\xf0\x00\x00\x00\x00\x00\x00') 104 | 105 | def test_unpack(self): 106 | """Unpack values. 107 | 108 | """ 109 | 110 | if not is_cpython_3(): 111 | return 112 | 113 | unpacked = unpack('u1u1s6u7u9', b'\x3e\x82\x16') 114 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 115 | 116 | # unpacked = unpack('u1', bytearray(b'\x80')) 117 | unpacked = unpack('u1', b'\x80') 118 | self.assertEqual(unpacked, (1, )) 119 | 120 | with self.assertRaises(NotImplementedError): 121 | packed = b'\x00\x80\x00\x00\x00\x00\x08\x00\x00\x00' 122 | unpacked = unpack('u77', packed) 123 | self.assertEqual(unpacked, (0x100000000001000000,)) 124 | 125 | with self.assertRaises(NotImplementedError): 126 | packed = 1000 * b'\xff' 127 | unpacked = unpack('u8000', packed) 128 | self.assertEqual(unpacked, (int(8000 * '1', 2), )) 129 | 130 | with self.assertRaises(NotImplementedError): 131 | packed = 500 * b'\x00' 132 | unpacked = unpack('s4000', packed) 133 | self.assertEqual(unpacked, (0, )) 134 | 135 | packed = b'\xbe\x82\x16' 136 | unpacked = unpack('P1u1s6u7u9', packed) 137 | self.assertEqual(unpacked, (0, -2, 65, 22)) 138 | 139 | packed = b'\x3e\x82\x16' 140 | unpacked = unpack('P1u1s6u7u9', packed) 141 | self.assertEqual(unpacked, (0, -2, 65, 22)) 142 | 143 | packed = b'\xbe\x82\x16' 144 | unpacked = unpack('p1u1s6u7u9', packed) 145 | self.assertEqual(unpacked, (0, -2, 65, 22)) 146 | 147 | packed = b'\x3e\x82\x16' 148 | unpacked = unpack('p1u1s6u7u9', packed) 149 | self.assertEqual(unpacked, (0, -2, 65, 22)) 150 | 151 | packed = b'\x3e\x82\x16' 152 | unpacked = unpack('p1u1s6p7u9', packed) 153 | self.assertEqual(unpacked, (0, -2, 22)) 154 | 155 | with self.assertRaises(NotImplementedError): 156 | packed = b'\x7c\x80\xe0\x00\x00\x01\xfe\x01\xfe\x01\xc0' 157 | unpacked = unpack('u1s6f32r43', packed) 158 | self.assertEqual(unpacked, (0, -2, 3.75, b'\x00\xff\x00\xff\x00\xe0')) 159 | 160 | # packed = bytearray(b'\x80') 161 | packed = b'\x80' 162 | unpacked = unpack('b1', packed) 163 | self.assertEqual(unpacked, (True, )) 164 | 165 | packed = b'\x80' 166 | unpacked = unpack('b1p6b1', packed) 167 | self.assertEqual(unpacked, (True, False)) 168 | 169 | packed = b'\x06' 170 | unpacked = unpack('u5b2u1', packed) 171 | self.assertEqual(unpacked, (0, True, 0)) 172 | 173 | packed = b'\x04' 174 | unpacked = unpack('u5b2u1', packed) 175 | self.assertEqual(unpacked, (0, True, 0)) 176 | 177 | packed = b'$4\x90\x80' 178 | unpacked = unpack('b1t24', packed) 179 | self.assertEqual(unpacked, (False, u'Hi!')) 180 | 181 | packed = 1000 * b'7' 182 | unpacked = unpack('t8000', packed) 183 | self.assertEqual(packed, 1000 * b'\x37') 184 | 185 | if sys.version_info >= (3, 6): 186 | unpacked = unpack('f16', b'\x3c\x00') 187 | self.assertEqual(unpacked, (1.0, )) 188 | 189 | unpacked = unpack('f32', b'\x3f\x80\x00\x00') 190 | self.assertEqual(unpacked, (1.0, )) 191 | 192 | unpacked = unpack('f64', b'\x3f\xf0\x00\x00\x00\x00\x00\x00') 193 | self.assertEqual(unpacked, (1.0, )) 194 | 195 | # Too many bits to unpack. 196 | with self.assertRaises(ValueError) as cm: 197 | unpack('u9', b'\x00') 198 | 199 | self.assertEqual(str(cm.exception), 200 | 'Short data.') 201 | 202 | # partial unpacking of truncated data 203 | unpacked = unpack('u4u5', b'\x5f', allow_truncated=True) 204 | self.assertEqual(unpacked, (5, )) 205 | unpacked = unpack('p8u4u5', b'\x00\x5f', allow_truncated=True) 206 | self.assertEqual(unpacked, (5, )) 207 | 208 | # test bytearray unpacking 209 | unpacked = unpack('u1u1s6u7u9', bytearray(b'\x3e\x82\x16')) 210 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 211 | 212 | def test_pack_unpack(self): 213 | """Pack and unpack values. 214 | 215 | """ 216 | 217 | if not is_cpython_3(): 218 | return 219 | 220 | packed = pack('u1u1s6u7u9', 0, 0, -2, 65, 22) 221 | unpacked = unpack('u1u1s6u7u9', packed) 222 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 223 | 224 | packed = pack('f64', 1.0) 225 | unpacked = unpack('f64', packed) 226 | self.assertEqual(unpacked, (1.0, )) 227 | 228 | if sys.version_info >= (3, 6): 229 | packed = pack('f16', 1.0) 230 | unpacked = unpack('f16', packed) 231 | self.assertEqual(unpacked, (1.0, )) 232 | 233 | def test_calcsize(self): 234 | """Calculate size. 235 | 236 | """ 237 | 238 | if not is_cpython_3(): 239 | return 240 | 241 | size = calcsize('u1u1s6u7u9') 242 | self.assertEqual(size, 24) 243 | 244 | size = calcsize('u1') 245 | self.assertEqual(size, 1) 246 | 247 | size = calcsize('u1s6u7u9') 248 | self.assertEqual(size, 23) 249 | 250 | size = calcsize('b1s6u7u9p1t8') 251 | self.assertEqual(size, 32) 252 | 253 | size = calcsize('b1s6u7u9P1t8') 254 | self.assertEqual(size, 32) 255 | 256 | def test_compiled_calcsize(self): 257 | """Calculate size. 258 | 259 | """ 260 | 261 | if not is_cpython_3(): 262 | return 263 | 264 | cf = bitstruct.c.compile('u1u1s6u7u9') 265 | self.assertEqual(cf.calcsize(), 24) 266 | 267 | cf = bitstruct.c.compile('u1u1s6u7u9', ['a', 'b', 'c', 'd', 'e']) 268 | self.assertEqual(cf.calcsize(), 24) 269 | 270 | def test_byteswap(self): 271 | """Byte swap. 272 | 273 | """ 274 | 275 | if not is_cpython_3(): 276 | return 277 | 278 | res = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a' 279 | ref = b'\x01\x03\x02\x04\x08\x07\x06\x05\x0a\x09' 280 | self.assertEqual(byteswap('12142', ref), res) 281 | 282 | packed = pack('u1u5u2u16', 1, 2, 3, 4) 283 | unpacked = unpack('u1u5u2u16', byteswap('12', packed)) 284 | self.assertEqual(unpacked, (1, 2, 3, 1024)) 285 | 286 | res = b'\x01\x02\x03\x04\x05\x06\x07\x08' 287 | ref = b'\x08\x07\x06\x05\x04\x03\x02\x01' 288 | self.assertEqual(byteswap('8', ref), res) 289 | 290 | def test_pack_into(self): 291 | """Pack values into a buffer. 292 | 293 | """ 294 | 295 | if not is_cpython_3(): 296 | return 297 | 298 | packed = bytearray(3) 299 | pack_into('u1u1s6u7u9', packed, 0, 0, 0, -2, 65, 22) 300 | self.assertEqual(packed, b'\x3e\x82\x16') 301 | 302 | datas = [ 303 | (0, b'\x80\x00'), 304 | (1, b'\x40\x00'), 305 | (7, b'\x01\x00'), 306 | (15, b'\x00\x01') 307 | ] 308 | 309 | for offset, expected in datas: 310 | packed = bytearray(2) 311 | pack_into('u1', packed, offset, 1) 312 | self.assertEqual(packed, expected) 313 | 314 | with self.assertRaises(AssertionError): 315 | packed = bytearray(b'\xff\xff\xff') 316 | pack_into('p4u4p4u4p4u4', packed, 0, 1, 2, 3, fill_padding=False) 317 | self.assertEqual(packed, b'\xf1\xf2\xf3') 318 | 319 | packed = bytearray(b'\xff\xff\xff') 320 | pack_into('p4u4p4u4p4u4', packed, 0, 1, 2, 3, fill_padding=True) 321 | self.assertEqual(packed, b'\x01\x02\x03') 322 | 323 | packed = bytearray(2) 324 | 325 | with self.assertRaises(ValueError) as cm: 326 | pack_into('u17', packed, 0, 1) 327 | 328 | self.assertEqual(str(cm.exception), 329 | 'pack_into requires a buffer of at least 17 bits') 330 | 331 | packed = bytearray(b'\x00') 332 | pack_into('P4u4', packed, 0, 1) 333 | self.assertEqual(packed, b'\xf1') 334 | 335 | datas = [ 336 | (0, b'\x7f\xff\xff\xff'), 337 | (1, b'\xbf\xff\xff\xff'), 338 | (7, b'\xfe\xff\xff\xff'), 339 | (8, b'\xff\x7f\xff\xff'), 340 | (15, b'\xff\xfe\xff\xff'), 341 | (16, b'\xff\xff\x7f\xff'), 342 | (31, b'\xff\xff\xff\xfe') 343 | ] 344 | 345 | for offset, expected in datas: 346 | packed = bytearray(b'\xff\xff\xff\xff') 347 | pack_into('u1', packed, offset, 0) 348 | self.assertEqual(packed, expected) 349 | 350 | # Check for non-writable buffer. 351 | with self.assertRaises(TypeError): 352 | pack_into('u1', b'\x00', 0, 0) 353 | 354 | def test_unpack_from(self): 355 | """Unpack values at given bit offset. 356 | 357 | """ 358 | 359 | if not is_cpython_3(): 360 | return 361 | 362 | unpacked = unpack_from('u1u1s6u7u9', b'\x1f\x41\x0b\x00', 1) 363 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 364 | 365 | unpacked = unpack_from('u1', b'\x80') 366 | self.assertEqual(unpacked, (1, )) 367 | 368 | unpacked = unpack_from('u1', b'\x08', 4) 369 | self.assertEqual(unpacked, (1, )) 370 | 371 | with self.assertRaises(ValueError): 372 | unpack_from('u1', b'\xff', 8) 373 | 374 | with self.assertRaises(TypeError): 375 | unpack_from('u1', b'\xff', None) 376 | 377 | def test_compiled_pack_into(self): 378 | """Pack values at given bit offset. 379 | 380 | """ 381 | 382 | if not is_cpython_3(): 383 | return 384 | 385 | cf = bitstruct.c.compile('u2') 386 | packed = bytearray(2) 387 | cf.pack_into(packed, 7, 3) 388 | self.assertEqual(packed, b'\x01\x80') 389 | 390 | def test_compiled_unpack_from(self): 391 | """Unpack values at given bit offset. 392 | 393 | """ 394 | 395 | if not is_cpython_3(): 396 | return 397 | 398 | cf = bitstruct.c.compile('u1') 399 | unpacked = cf.unpack_from(b'\x40', 1) 400 | self.assertEqual(unpacked, (1, )) 401 | 402 | unpacked = cf.unpack_from(b'\x80') 403 | self.assertEqual(unpacked, (1, )) 404 | 405 | def test_pack_unpack_raw(self): 406 | """Pack and unpack raw values. 407 | 408 | """ 409 | 410 | if not is_cpython_3(): 411 | return 412 | 413 | with self.assertRaises(NotImplementedError): 414 | packed = pack('r24', b'') 415 | self.assertEqual(packed, b'\x00\x00\x00') 416 | 417 | with self.assertRaises(NotImplementedError): 418 | packed = pack('r24', b'12') 419 | self.assertEqual(packed, b'12\x00') 420 | 421 | packed = pack('r24', b'123') 422 | self.assertEqual(packed, b'123') 423 | packed = pack('r24', b'1234') 424 | self.assertEqual(packed, b'123') 425 | 426 | unpacked = unpack('r24', b'\x00\x00\x00')[0] 427 | self.assertEqual(unpacked, b'\x00\x00\x00') 428 | unpacked = unpack('r24', b'12\x00')[0] 429 | self.assertEqual(unpacked, b'12\x00') 430 | unpacked = unpack('r24', b'123')[0] 431 | self.assertEqual(unpacked, b'123') 432 | unpacked = unpack('r24', b'1234')[0] 433 | self.assertEqual(unpacked, b'123') 434 | 435 | def test_pack_unpack_dict(self): 436 | if not is_cpython_3(): 437 | return 438 | 439 | unpacked = { 440 | 'foo': 0, 441 | 'bar': 0, 442 | 'fie': -2, 443 | 'fum': 65, 444 | 'fam': 22 445 | } 446 | packed = b'\x3e\x82\x16' 447 | fmt = 'u1u1s6u7u9' 448 | names = ['foo', 'bar', 'fie', 'fum', 'fam'] 449 | 450 | self.assertEqual(pack_dict(fmt, names, unpacked), packed) 451 | self.assertEqual(unpack_dict(fmt, names, packed), unpacked) 452 | 453 | def test_pack_into_unpack_from_dict(self): 454 | if not is_cpython_3(): 455 | return 456 | 457 | unpacked = { 458 | 'foo': 0, 459 | 'bar': 0, 460 | 'fie': -2, 461 | 'fum': 65, 462 | 'fam': 22 463 | } 464 | packed = b'\x3e\x82\x16' 465 | fmt = 'u1u1s6u7u9' 466 | names = ['foo', 'bar', 'fie', 'fum', 'fam'] 467 | 468 | actual = bytearray(3) 469 | pack_into_dict(fmt, names, actual, 0, unpacked) 470 | self.assertEqual(actual, packed) 471 | 472 | self.assertEqual(unpack_from_dict(fmt, names, packed), unpacked) 473 | 474 | def test_compiled_pack_into_unpack_from_dict(self): 475 | if not is_cpython_3(): 476 | return 477 | 478 | unpacked = { 479 | 'foo': 0, 480 | 'bar': 0, 481 | 'fie': -2, 482 | 'fum': 65, 483 | 'fam': 22 484 | } 485 | packed = b'\x3e\x82\x16' 486 | fmt = 'u1u1s6u7u9' 487 | names = ['foo', 'bar', 'fie', 'fum', 'fam'] 488 | cf = bitstruct.c.compile(fmt, names) 489 | 490 | actual = bytearray(3) 491 | cf.pack_into(actual, 0, unpacked) 492 | self.assertEqual(actual, packed) 493 | 494 | self.assertEqual(cf.unpack_from(packed), unpacked) 495 | 496 | def test_compile(self): 497 | if not is_cpython_3(): 498 | return 499 | 500 | cf = bitstruct.c.compile('u1u1s6u7u9') 501 | 502 | packed = cf.pack(0, 0, -2, 65, 22) 503 | self.assertEqual(packed, b'\x3e\x82\x16') 504 | 505 | unpacked = cf.unpack(b'\x3e\x82\x16') 506 | self.assertEqual(unpacked, (0, 0, -2, 65, 22)) 507 | 508 | def test_compile_pack_unpack_formats(self): 509 | if not is_cpython_3(): 510 | return 511 | 512 | fmts = [ 513 | ('u1s2p3', None, (1, -1)), 514 | ('u1 s2 p3', None, (1, -1)), 515 | ('u1s2p3', ['a', 'b'], {'a': 1, 'b': -1}) 516 | ] 517 | 518 | for fmt, names, decoded in fmts: 519 | if names is None: 520 | cf = bitstruct.c.compile(fmt) 521 | packed_1 = cf.pack(*decoded) 522 | packed_2 = pack(fmt, *decoded) 523 | else: 524 | cf = bitstruct.c.compile(fmt, names) 525 | packed_1 = cf.pack(decoded) 526 | packed_2 = pack_dict(fmt, names, decoded) 527 | 528 | self.assertEqual(packed_1, b'\xe0') 529 | self.assertEqual(packed_2, b'\xe0') 530 | 531 | if names is None: 532 | unpacked_1 = cf.unpack(packed_1) 533 | unpacked_2 = unpack(fmt, packed_2) 534 | unpacked_3 = cf.unpack(bytearray(packed_1)) # test bytearray unpacking 535 | else: 536 | unpacked_1 = cf.unpack(packed_1) 537 | unpacked_2 = unpack_dict(fmt, names, packed_2) 538 | unpacked_3 = cf.unpack(bytearray(packed_1)) # test bytearray unpacking 539 | 540 | self.assertEqual(unpacked_1, decoded) 541 | self.assertEqual(unpacked_2, decoded) 542 | self.assertEqual(unpacked_3, decoded) 543 | 544 | def test_compile_formats(self): 545 | if not is_cpython_3(): 546 | return 547 | 548 | bitstruct.c.compile('p1u1') 549 | bitstruct.c.compile('p1u1', ['a']) 550 | 551 | with self.assertRaises(TypeError): 552 | bitstruct.c.compile() 553 | 554 | def test_pack_unpack_signed(self): 555 | if not is_cpython_3(): 556 | return 557 | 558 | datas = [ 559 | ('s1', 0, b'\x00'), 560 | ('s1', -1, b'\x80'), 561 | ('s63', -1, b'\xff\xff\xff\xff\xff\xff\xff\xfe'), 562 | ('s64', -1, b'\xff\xff\xff\xff\xff\xff\xff\xff') 563 | ] 564 | 565 | for fmt, value, packed in datas: 566 | self.assertEqual(pack(fmt, value), packed) 567 | self.assertEqual(unpack(fmt, packed), (value, )) 568 | 569 | def test_pack_unpack_unsigned(self): 570 | if not is_cpython_3(): 571 | return 572 | 573 | datas = [ 574 | ('u1', 0, b'\x00'), 575 | ('u1', 1, b'\x80'), 576 | ('u63', 0x1234567890abcdef, b'$h\xac\xf1!W\x9b\xde'), 577 | ('u64', 0x1234567890abcdef, b'\x124Vx\x90\xab\xcd\xef') 578 | ] 579 | 580 | for fmt, value, packed in datas: 581 | self.assertEqual(pack(fmt, value), packed) 582 | self.assertEqual(unpack(fmt, packed), (value, )) 583 | 584 | def test_various(self): 585 | if not is_cpython_3(): 586 | return 587 | 588 | with self.assertRaises(ValueError): 589 | pack('u89999888888888888888899', 1) 590 | 591 | # Fewer names than fields in the format. 592 | with self.assertRaises(ValueError): 593 | pack_dict('u1u1', ['foo'], {'foo': 1}) 594 | 595 | # Missing value for name. 596 | with self.assertRaises(KeyError): 597 | pack_dict('u1', ['foo'], {}) 598 | 599 | # Fewer names than fields in the format. 600 | with self.assertRaises(ValueError): 601 | unpack_dict('u1u1', ['foo'], b'\xff') 602 | 603 | # Short data. 604 | with self.assertRaises(ValueError): 605 | unpack_dict('u1', ['foo'], b'') 606 | 607 | # Padding last. 608 | self.assertEqual(pack('u1p1', 1), b'\x80') 609 | self.assertEqual(pack_dict('u1p1', ['foo'], {'foo': 1}), b'\x80') 610 | 611 | # Short text. 612 | with self.assertRaises(NotImplementedError) as cm: 613 | pack('t16', '1') 614 | 615 | self.assertEqual(str(cm.exception), 'Short text.') 616 | 617 | # Bad float length. 618 | with self.assertRaises(NotImplementedError) as cm: 619 | pack('f1', 1.0) 620 | 621 | if sys.version_info >= (3, 6): 622 | self.assertEqual(str(cm.exception), 'Float not 16, 32 or 64 bits.') 623 | else: 624 | self.assertEqual(str(cm.exception), 'Float not 32 or 64 bits.') 625 | 626 | # Long bool. 627 | with self.assertRaises(NotImplementedError) as cm: 628 | pack('b65', True) 629 | 630 | self.assertEqual(str(cm.exception), 'Bool over 64 bits.') 631 | 632 | # Text not multiple of 8 bits. 633 | with self.assertRaises(NotImplementedError) as cm: 634 | pack('t1', '') 635 | 636 | self.assertEqual(str(cm.exception), 'Text not multiple of 8 bits.') 637 | 638 | # Bad format kind. 639 | with self.assertRaises(ValueError) as cm: 640 | pack('x1', '') 641 | 642 | self.assertEqual(str(cm.exception), "Bad format field type 'x'.") 643 | 644 | # Too few arguments. 645 | with self.assertRaises(ValueError) as cm: 646 | pack('u1u1', 1) 647 | 648 | self.assertEqual(str(cm.exception), 'Too few arguments.') 649 | 650 | # No format string. 651 | with self.assertRaises(ValueError) as cm: 652 | pack() 653 | 654 | self.assertEqual(str(cm.exception), 'No format string.') 655 | 656 | # No format string. 657 | with self.assertRaises(ValueError) as cm: 658 | unpack('u1', b'') 659 | 660 | self.assertEqual(str(cm.exception), 'Short data.') 661 | 662 | # Bad format in compile. 663 | with self.assertRaises(ValueError) as cm: 664 | bitstruct.c.compile('x1') 665 | 666 | self.assertEqual(str(cm.exception), "Bad format field type 'x'.") 667 | 668 | # Bad format in compile dict. 669 | with self.assertRaises(ValueError) as cm: 670 | bitstruct.c.compile('x1', ['foo']) 671 | 672 | self.assertEqual(str(cm.exception), "Bad format field type 'x'.") 673 | 674 | # Offset too big. 675 | with self.assertRaises(ValueError) as cm: 676 | packed = bytearray(1) 677 | pack_into('u1', packed, 0x80000000, 1) 678 | 679 | self.assertIn("Offset must be less or equal to ", str(cm.exception)) 680 | 681 | # Offset too big. 682 | with self.assertRaises(ValueError) as cm: 683 | packed = bytearray(1) 684 | pack_into_dict('u1', ['a'], packed, 0x80000000, {'a': 1}) 685 | 686 | self.assertIn("Offset must be less or equal to ", str(cm.exception)) 687 | 688 | # Offset too big. 689 | with self.assertRaises(ValueError) as cm: 690 | unpack_from('u1', b'\x00', 0x80000000) 691 | 692 | self.assertIn("Offset must be less or equal to ", str(cm.exception)) 693 | 694 | # Offset too big. 695 | with self.assertRaises(ValueError) as cm: 696 | packed = bytearray(1) 697 | unpack_from_dict('u1', ['a'], b'\x00', 0x80000000) 698 | 699 | self.assertIn("Offset must be less or equal to ", str(cm.exception)) 700 | 701 | # Out of data to swap. 702 | datas = ['11', '2', '4', '8'] 703 | 704 | for fmt in datas: 705 | with self.assertRaises(ValueError) as cm: 706 | byteswap(fmt, b'\x00') 707 | 708 | self.assertEqual(str(cm.exception), 'Out of data to swap.') 709 | 710 | # Bad swap format. 711 | with self.assertRaises(ValueError) as cm: 712 | byteswap('3', b'\x00') 713 | 714 | # Bad swap format type. 715 | with self.assertRaises(TypeError): 716 | byteswap(None, b'\x00') 717 | 718 | self.assertEqual(str(cm.exception), 'Expected 1, 2, 4 or 8, but got 3.') 719 | 720 | # Bad format type. 721 | with self.assertRaises(TypeError): 722 | pack(None, 1) 723 | 724 | # Out of range checks. 725 | datas = [ 726 | ('s64', (1 << 63)), 727 | ('s64', -(1 << 63) - 1), 728 | ('u64', 1 << 64), 729 | ('u64', -1), 730 | ('u1', 2), 731 | ('s1', 1), 732 | ('s1', -2), 733 | ('s2', 2) 734 | ] 735 | 736 | for fmt, value in datas: 737 | with self.assertRaises(OverflowError) as cm: 738 | pack(fmt, value) 739 | 740 | # Bad value types. 741 | with self.assertRaises(TypeError) as cm: 742 | pack('s1', None) 743 | 744 | with self.assertRaises(TypeError) as cm: 745 | pack('u1', None) 746 | 747 | with self.assertRaises(TypeError) as cm: 748 | pack('f32', None) 749 | 750 | with self.assertRaises(TypeError) as cm: 751 | pack('r8', None) 752 | 753 | with self.assertRaises(TypeError) as cm: 754 | pack('t8', None) 755 | 756 | # Everything can be bool. 757 | self.assertEqual(pack('b8', None), b'\x00') 758 | 759 | # Zero bits bool. 760 | with self.assertRaises(ValueError) as cm: 761 | pack('b0', None) 762 | 763 | self.assertEqual(str(cm.exception), 'Field of size 0.') 764 | 765 | # pack/unpack dict with names as a non-list. Caused an segfault. 766 | with self.assertRaises(TypeError) as cm: 767 | pack_into_dict('u1', None, bytearray(b'\x00'), 0, {'a': 1}) 768 | 769 | self.assertEqual(str(cm.exception), 'Names is not a list.') 770 | 771 | with self.assertRaises(TypeError) as cm: 772 | pack_dict('u1', None, {'a': 1}) 773 | 774 | self.assertEqual(str(cm.exception), 'Names is not a list.') 775 | 776 | with self.assertRaises(TypeError) as cm: 777 | bitstruct.c.compile('u1', 1) 778 | 779 | self.assertEqual(str(cm.exception), 'Names is not a list.') 780 | 781 | def test_whitespaces(self): 782 | if not is_cpython_3(): 783 | return 784 | 785 | fmts = [ 786 | ' ', 787 | ' u1 s2 p3 ' 788 | ] 789 | 790 | for fmt in fmts: 791 | bitstruct.c.compile(fmt) 792 | 793 | def test_copy(self): 794 | if not is_cpython_3(): 795 | return 796 | 797 | cf = bitstruct.c.compile('u1u1s6u7u9') 798 | 799 | cf = copy.copy(cf) 800 | packed = cf.pack(0, 0, -2, 65, 22) 801 | self.assertEqual(packed, b'\x3e\x82\x16') 802 | 803 | cf = copy.deepcopy(cf) 804 | packed = cf.pack(0, 0, -2, 65, 22) 805 | self.assertEqual(packed, b'\x3e\x82\x16') 806 | 807 | def test_copy_dict(self): 808 | if not is_cpython_3(): 809 | return 810 | 811 | unpacked = { 812 | 'foo': 0, 813 | 'bar': 0, 814 | 'fie': -2, 815 | 'fum': 65, 816 | 'fam': 22 817 | } 818 | names = ['foo', 'bar', 'fie', 'fum', 'fam'] 819 | cf = bitstruct.c.compile('u1u1s6u7u9', names) 820 | 821 | cf = copy.copy(cf) 822 | packed = cf.pack(unpacked) 823 | self.assertEqual(packed, b'\x3e\x82\x16') 824 | self.assertEqual(cf.unpack(packed), unpacked) 825 | 826 | cf = copy.deepcopy(cf) 827 | packed = cf.pack(unpacked) 828 | self.assertEqual(packed, b'\x3e\x82\x16') 829 | self.assertEqual(cf.unpack(packed), unpacked) 830 | 831 | def test_pickle(self): 832 | if not is_cpython_3(): 833 | return 834 | 835 | unpacked = (0, 0, -2, 65, 22) 836 | cf = bitstruct.c.compile('u1u1s6u7u9') 837 | 838 | cf = pickle.loads(pickle.dumps(cf)) 839 | packed = cf.pack(*unpacked) 840 | self.assertEqual(packed, b'\x3e\x82\x16') 841 | self.assertEqual(cf.unpack(packed), unpacked) 842 | 843 | # Dump and load once more. 844 | cf = pickle.loads(pickle.dumps(cf)) 845 | packed = cf.pack(*unpacked) 846 | self.assertEqual(packed, b'\x3e\x82\x16') 847 | self.assertEqual(cf.unpack(packed), unpacked) 848 | 849 | def test_pickle_dict(self): 850 | if not is_cpython_3(): 851 | return 852 | 853 | unpacked = { 854 | 'foo': 0, 855 | 'bar': 0, 856 | 'fie': -2, 857 | 'fum': 65, 858 | 'fam': 22 859 | } 860 | names = ['foo', 'bar', 'fie', 'fum', 'fam'] 861 | cf = bitstruct.c.compile('u1u1s6u7u9', names) 862 | 863 | cf = pickle.loads(pickle.dumps(cf)) 864 | packed = cf.pack(unpacked) 865 | self.assertEqual(packed, b'\x3e\x82\x16') 866 | self.assertEqual(cf.unpack(packed), unpacked) 867 | 868 | # Dump and load once more. 869 | cf = pickle.loads(pickle.dumps(cf)) 870 | packed = cf.pack(unpacked) 871 | self.assertEqual(packed, b'\x3e\x82\x16') 872 | self.assertEqual(cf.unpack(packed), unpacked) 873 | 874 | 875 | if __name__ == '__main__': 876 | unittest.main() 877 | --------------------------------------------------------------------------------