diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..be006de9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly diff --git a/.github/workflows/build_dist.yml b/.github/workflows/build_dist.yml new file mode 100644 index 00000000..d33b3520 --- /dev/null +++ b/.github/workflows/build_dist.yml @@ -0,0 +1,152 @@ +name: Build + +# on: [push, pull_request, release] + +on: + push: + branches: + release: + types: [created] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 # To ensure tags are retrieved to enable setuptools_scm to work + - name: Install Python 3.x + uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Build sdist + run: pipx run build --sdist + - name: Save sdist + uses: actions/upload-artifact@v5 + with: + name: cibw-sdist.tar.gz + path: dist/*.tar.gz + + wheels: + name: Build wheels on ${{ matrix.os }} CIBW_BUILD=${{ matrix.cibw_build }} + runs-on: ${{ matrix.os }} + strategy: + # since multiple builds run at the same time, cancelling them all when one + # fails is wasteful and forces handling build problems one by one instead + # of showing a "full picture" + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-15-intel # x86 + - macos-latest # arm + - windows-latest + cibw_build: [cp39-*, cp310-*, cp311-*, cp312-*, cp313-*, cp313t-*, cp314-*, cp314t-*] + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 # To ensure tags are retrieved to enable setuptools_scm to work + - name: Install Python 3.x + uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Set up QEMU # Needed to build aarch64 wheels + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Setup free-threading variables + if: ${{ endsWith(matrix.cibw_build, 't-*') }} + shell: bash -l {0} + run: | + echo "CIBW_BEFORE_TEST=pip install pytest pytest-run-parallel" >> "$GITHUB_ENV" + echo "CIBW_ENVIRONMENT=PYLZ4_USE_SYSTEM_LZ4=False PYTEST_ADDOPTS=--parallel-threads=4" >> "$GITHUB_ENV" + echo "CIBW_TEST_COMMAND=tox -x testenv.deps+=pytest-run-parallel -x testenv.pass_env+=PYTEST_ADDOPTS -c {project}" >> "$GITHUB_ENV" + - name: Setup environment + if: ${{ !endsWith(matrix.cibw_build, 't-*') }} + shell: bash -l {0} + run: | + echo "CIBW_ENVIRONMENT=PYLZ4_USE_SYSTEM_LZ4=False" >> "$GITHUB_ENV" + echo "CIBW_TEST_COMMAND=tox -c {project}" >> "$GITHUB_ENV" + - name: Build wheels + uses: pypa/cibuildwheel@v3.3.0 + env: + # CIBW_ARCHS_LINUX: "x86_64 i686 aarch64" + CIBW_ARCHS_LINUX: "x86_64 i686" + CIBW_ARCHS_MACOS: "auto64" # since we have both runner arches + CIBW_ARCHS_WINDOWS: "AMD64 x86 ARM64" + CIBW_ENABLE: cpython-freethreading + CIBW_BUILD: ${{ matrix.cibw_build }} + CIBW_SKIP: "cp*-musllinux*" + CIBW_TEST_SKIP: "*-macosx_arm64 *-macosx_universal2:arm64 *-*linux_{ppc64le,s390x} *-win_arm64" + CIBW_BEFORE_BUILD: "python -m pip install -U pip && python -m pip install tox" + - name: Save wheels + uses: actions/upload-artifact@v5 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + wheels_linux_arm: + name: Build wheels on ${{ matrix.os }} CIBW_BUILD=${{ matrix.cibw_build }} + runs-on: ${{ matrix.os }} + strategy: + # since multiple builds run at the same time, cancelling them all when one + # fails is wasteful and forces handling build problems one by one instead + # of showing a "full picture" + fail-fast: false + matrix: + os: + - ubuntu-24.04-arm + cibw_build: [cp39-*, cp310-*, cp311-*, cp312-*, cp313-*, cp313t-*, cp314-*, cp314t-*] + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 # To ensure tags are retrieved to enable setuptools_scm to work + - name: Install Python 3.x + uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Setup environment + shell: bash -l {0} + run: | + echo "CIBW_ENVIRONMENT=PYLZ4_USE_SYSTEM_LZ4=False" >> "$GITHUB_ENV" + echo "CIBW_TEST_COMMAND=tox -c {project}" >> "$GITHUB_ENV" + - name: Build wheels + uses: pypa/cibuildwheel@v3.3.0 + env: + CIBW_ARCHS_LINUX: "aarch64" + CIBW_BUILD: ${{ matrix.cibw_build }} + CIBW_SKIP: "cp*-musllinux*" + CIBW_ENABLE: cpython-freethreading + CIBW_BEFORE_BUILD: "python -m pip install -U pip && python -m pip install tox" + - name: Save wheels + uses: actions/upload-artifact@v5 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + upload_pypi: + name: Upload to PyPI + needs: [sdist, wheels, wheels_linux_arm] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/download-artifact@v6 + with: + pattern: cibw-* + path: dist + merge-multiple: true + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + skip-existing: true diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..1ad8c58f --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,25 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-20.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# If using Sphinx, optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: docs/requirements.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 023acd32..00000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: python -python: - - 2.7 - - 3.4 - - 3.5 - - 3.6 -install: - - pip install tox - - pip install -r docs/requirements.txt -script: - - tox - - python setup.py install - - make -C docs doctest html -deploy: - - provider: pypi - # server: https://test.pypi.org/legacy/ - user: jgu - password: - secure: SS5kSQtsfHIYG5gLKC9jQ23bnNBgoejVgdrDxfaccNqojrIy5CtuUo+a7/P16bObWp1jCnlwUyX6mQj1S+abDsUHr+2+pNjl5MP8pWdREu19fBTcwMkZLXlUdSuWwcBjmZjaVpuKQRCKwajHrGjOE4s3vO1h/IOLETEX0d1rmuavSUftqYcea/TO/MNzDuxnUe7pkGsPkfbMxHY5oXNmx5zOvtG2h0eZowjwgE5YVZfTZ82fqfCfbqmoytwmQKd3rTmo/hpYvNcGweHizKouzLArw0+DDtkJt/jT459zM3lnUEh+TfaCSeqd+cTWmEZDiMR8MxnSJkpN3RGHcvxreF+HZjCAKhvAbZcWIpRC0cNp4VxWyVGZN/dLZA0WM3gJJKCjgCA70eslC+ccZh6WyG93effiVLhHYB9vyt7SfwsUzDrZo3++Czi6drCVNyI6pBdb587yfkuJYdomNIQ2DCesUKjuoFFt34VZC5bJOK82F81jMXYGaQn3BCWPdfMTmqJ3maimfji7KXts6bbyQUzgc+610Yd8eIRGvDQwmeBjovRT5uyYDJtXfDOIYXiv3n79wtDt+ygZzLOXNMvEt6/Wsy/xrWEsHFfA5bknSenurfgQoYCkLOjgKJEqxDV4nXdwhJxwbC740Fea/cGwem5L0mfz8SGtd7giFHDpgkg= - distributions: "sdist" - on: - branch: master - tags: true - python: '3.6' diff --git a/MANIFEST.in b/MANIFEST.in index 092a3a47..9561fb10 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1 @@ include README.rst -include tests/*.py -exclude tests/*.pyc -exclude .gitignore -exclude .travis.yml -prune appveyor* -exclude appveyor.yml \ No newline at end of file diff --git a/README.rst b/README.rst index 4a7b51c6..a75bc4d4 100644 --- a/README.rst +++ b/README.rst @@ -5,28 +5,34 @@ python-lz4 Status ====== -.. image:: https://travis-ci.org/python-lz4/python-lz4.svg?branch=master - :target: https://travis-ci.org/python-lz4/python-lz4 +.. image:: https://github.com/python-lz4/python-lz4/actions/workflows/build_dist.yml/badge.svg + :target: https://github.com/python-lz4/python-lz4/actions/workflows/build_dist.yml :alt: Build Status -.. image:: https://ci.appveyor.com/api/projects/status/github/python-lz4/python-lz4?branch=master - :target: https://ci.appveyor.com/project/jonathanunderwood/python-lz4 - :alt: Build Status Windows - -.. image:: https://readthedocs.org/projects/python-lz4/badge/?version=latest +.. image:: https://readthedocs.org/projects/python-lz4/badge/?version=stable :target: https://readthedocs.org/projects/python-lz4/ :alt: Documentation +.. image:: https://codecov.io/gh/python-lz4/python-lz4/branch/codecov/graph/badge.svg + :target: https://codecov.io/gh/python-lz4/python-lz4 + :alt: CodeCov + + Introduction ============ This package provides python bindings for the `LZ4 compression library `_. -The bindings provided in this package cover the `frame format -`_ and the `block format -`_ specifications. The frame -format bindings are the recommended ones to use, as this guarantees -interoperability with other implementations and language bindings. +The production ready bindings provided in this package cover the `frame format +`_, and the +`block format `_ +specifications. The frame format bindings are the recommended ones to use, as +this guarantees interoperability with other implementations and language +bindings. + +Experimental bindings for the the `streaming format +`_ +specification are also included, but further work on those is required. The API provided by the frame format bindings follows that of the LZMA, zlib, gzip and bzip2 compression libraries which are provided with the Python standard @@ -37,10 +43,10 @@ and file handler support. The bindings drop the GIL when calling in to the underlying LZ4 library, and is thread safe. An extensive test suite is included. -Documenation -============ +Documentation +============= -.. image:: https://readthedocs.org/projects/python-lz4/badge/?version=latest +.. image:: https://readthedocs.org/projects/python-lz4/badge/?version=stable :target: https://readthedocs.org/projects/python-lz4/ :alt: Documentation diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6399da20..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,108 +0,0 @@ -# See: -# https://packaging.python.org/appveyor/ -# https://github.com/ogrisel/python-appveyor-demo -# https://www.appveyor.com/docs/appveyor-yml/ -# https://packaging.python.org/appveyor/ - -environment: - PYPI_PASSWORD: - secure: LGlG50Ta854XC2pxoPRxxQ== - - matrix: - - - PYTHON: "C:\\Python27-x64" - DISTUTILS_USE_SDK: "1" - - PYTHON: "C:\\Python34-x64" - DISTUTILS_USE_SDK: "1" - - PYTHON: "C:\\Python35-x64" - - PYTHON: "C:\\Python36-x64" - - PYTHON: "C:\\Python36" - - PYTHON: "C:\\Python27" - - PYTHON: "C:\\Python34" - - PYTHON: "C:\\Python35" - -matrix: - # Immediately finish build once one of the jobs fails. - fast_finish: true - -platform: x64 - -install: - # If there is a newer build queued for the same PR, cancel this one. - # The AppVeyor 'rollout builds' option is supposed to serve the same - # purpose but it is problematic because it tends to cancel builds pushed - # directly to master instead of just PR builds (or the converse). - # credits: JuliaLang developers. - - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` - https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` - Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` - throw "There are newer queued builds for this pull request, failing early." } - - ECHO "Filesystem root:" - - ps: "ls \"C:/\"" - - - ECHO "Installed SDKs:" - - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" - - # Prepend relevant Python installation to the PATH. Also prepend the location - # of the build.cmd file. - - "SET PATH=%CD%\\appveyor;%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - # Check that we have the expected version and architecture for Python - - "build.cmd python --version" - - "build.cmd python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - # Upgrade to the latest version of pip to avoid it displaying warnings - # about it being out of date. - - "build.cmd python -m pip install --disable-pip-version-check --user --upgrade pip" - - # We need wheel installed to build wheels, and this isn't pulled in by setup.py - - "build.cmd python -m pip install wheel" - - # We need twine installed to push wheels to PyPi - - "build.cmd python -m pip install twine" - - # We need tox to run tests - - "build.cmd python -m pip install tox" - -build_script: - # Build the compiled extension - - "build.cmd python setup.py build" - -test_script: - # Run the project tests. Test only for 64 bit Python as the 32 bit tests fail - # due to memory fragmentation resulting from many alloc/free cycles - # ps: $env:is64bit = $env:PYTHON.Contains("-x64") - # IF %is64bit% == True build.cmd tox - - "build.cmd tox" - -after_test: - # If tests are successful, create binary packages for the project. - - "build.cmd python setup.py bdist_wheel" - - ps: "ls dist" - -artifacts: - # Archive the generated packages in the ci.appveyor.com build report. - - path: dist\* - -deploy_script: - # Inspiration: https://github.com/AndrewAnnex/SpiceyPy/blob/master/appveyor.yml - - echo "Starting Artifact Deployment" - # populate pypirc file for twine - - echo [distutils] > %USERPROFILE%\\.pypirc - - echo index-servers = >> %USERPROFILE%\\.pypirc - - echo pypi >> %USERPROFILE%\\.pypirc - - echo [pypi] >> %USERPROFILE%\\.pypirc - - echo username=jgu >> %USERPROFILE%\\.pypirc - - echo password=%PYPI_PASSWORD% >> %USERPROFILE%\\.pypirc - # upload to pypi for windows - # set PATH=%BK_PATH% - # set HOME=%USERPROFILE% - - ps: >- - If ($env:APPVEYOR_REPO_TAG -eq "true") { - $env:pypi_upload = 'true' - } - Else { - write-output "Not on a tag, won't deploy to pypi" - } - - cmd: IF "%pypi_upload%"=="true" twine upload --skip-existing dist/* - - echo "Finished Artifact Deployment" diff --git a/appveyor/build.cmd b/appveyor/build.cmd deleted file mode 100644 index 243dc9a1..00000000 --- a/appveyor/build.cmd +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -:: To build extensions for 64 bit Python 3, we need to configure environment -:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: -:: MS Windows SDK for Windows 7 and .NET Framework 4 -:: -:: More details at: -:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows - -IF "%DISTUTILS_USE_SDK%"=="1" ( - ECHO Configuring environment to build with MSVC on a 64bit architecture - ECHO Using Windows SDK 7.1 - "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 - CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release - SET MSSdk=1 - REM Need the following to allow tox to see the SDK compiler - SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB -) ELSE ( - ECHO Using default MSVC build environment -) - -CALL %* diff --git a/docs/conf.py b/docs/conf.py index f752fa81..aefff88e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,17 +49,20 @@ # General information about the project. project = u'python-lz4' -copyright = u'2016, python-lz4 developers' +copyright = u'2016, 2017, 2018 python-lz4 developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -import lz4 -version = lz4.__version__ -# The full version, including alpha/beta/rc tags. -release = lz4.__version__ +try: + import importlib.metadata +except ImportError: + from pkg_resources import get_distribution + release = get_distribution('lz4').version +else: + release = importlib.metadata.version('lz4') +version = release # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/install.rst b/docs/install.rst index 1258b119..f6eeafed 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -1,22 +1,49 @@ Install ======= -The package is hosted on `PyPI `_ and so can be -installed via pip:: +The bindings to the LZ4 compression library provided by this package are in the +form of a Python extension module written in C. These extension modules need to +be compiled against the LZ4 library and the Python + +Installing from pre-built wheels +-------------------------------- + +The package is hosted on `PyPI `_ and pre-built +wheels are available for Linux, OSX and Windows. Installation using a pre-built +wheel can be achieved by:: $ pip install lz4 + +Installing from source +---------------------- + The LZ4 bindings require linking to the LZ4 library, and so if there is not a pre-compiled wheel available for your platform you will need to have a suitable -C compiler available, as well as the Python development header files. +C compiler available, as well as the Python development header files. On +Debian/Ubuntu based systems the header files for Python are found in the +distribution package ``pythonX.Y-dev`` e.g. ``python3.8-dev``. On Fedora/Red Hat +based systems, the Python header files are found in the distribution package +``python-devel``. The LZ4 library bindings provided by this package require the LZ4 library. If the system already has an LZ4 library and development header files present, and the library is a recent enough version the package will build against that. Otherwise, the package will use a bundled version of the library files to link -against. The package currently requires LZ4 version 1.7.5 or later. +against. The package currently requires LZ4 version 1.7.5 or later. + +On a system for which there are no pre-built wheels available on PyPi, running +this command will result in the extension modules being compiled from source:: + + $ pip install lz4 + +On systems for which pre-built wheels are available, the following command will +force a local compilation of the extension modules from source:: + + $ pip install --no-binary :all: --no-cache-dir lz4 -The package can also be installed manually:: +The package can also be installed manually from a checkout of the source code +git repository:: $ python setup.py install diff --git a/docs/intro.rst b/docs/intro.rst index 4bf7e2af..766950f5 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -11,8 +11,8 @@ The LZ4 library provides support for three specifications: * The `block `_ format * The `stream `_ format -This Python interface currently supports the frame and block formats. Support -for the streaming format will be available in a future release. +This Python interface currently supports the frame, block and double-buffer +stream formats. For most applications, the frame format is what you should use as this guarantees interoperability with other bindings. The frame format defines a diff --git a/docs/lz4.block.rst b/docs/lz4.block.rst index d50ead59..5c7f0c78 100644 --- a/docs/lz4.block.rst +++ b/docs/lz4.block.rst @@ -5,30 +5,95 @@ lz4.block sub-package ===================== This sub-package provides the capability to compress and decompress data using -the `block specification `_. +the `block specification `_. -Because the LZ4 block format doesn't define a container format, the Python -bindings will by default insert the original data size as an integer at the -start of the compressed payload, like most other bindings do (Java...). However, -it is possible to disable this functionality. +Because the LZ4 block format doesn't define a container format, the +Python bindings will by default insert the original data size as an +integer at the start of the compressed payload. However, it is +possible to disable this functionality, and you may wish to do so for +compatibility with other language bindings, such as the `Java bindings +`_. Example usage ------------- -To use the lz4 block format bindings is straightforward:: +To use the lz4 block format bindings is straightforward: .. doctest:: - >>> import lz4.block - >>> import os - >>> input_data = 20 * 128 * os.urandom(1024) # Read 20 * 128kb - >>> compressed_data = lz4.block.compress(input_data) - >>> output_data = lz4.block.decompress(compressed_data) - >>> input_data == output_data - True + >>> import lz4.block + >>> import os + >>> input_data = 20 * 128 * os.urandom(1024) # Read 20 * 128kb + >>> compressed_data = lz4.block.compress(input_data) + >>> output_data = lz4.block.decompress(compressed_data) + >>> input_data == output_data + True +In this simple example, the size of the uncompressed data is stored in +the compressed data, and this size is then utilized when uncompressing +the data in order to correctly size the buffer. Instead, you may want +to not store the size of the uncompressed data to ensure compatibility +with the `Java bindings `_. The +example below demonstrates how to use the block format without storing +the size of the uncompressed data. +.. doctest:: + + >>> import lz4.block + >>> data = b'0' * 255 + >>> compressed = lz4.block.compress(data, store_size=False) + >>> decompressed = lz4.block.decompress(compressed, uncompressed_size=255) + >>> decompressed == data + True + +The `uncompressed_size` argument specifies an upper bound on the size +of the uncompressed data size rather than an absolute value, such that +the following example also works. + +.. doctest:: + + >>> import lz4.block + >>> data = b'0' * 255 + >>> compressed = lz4.block.compress(data, store_size=False) + >>> decompressed = lz4.block.decompress(compressed, uncompressed_size=2048) + >>> decompressed == data + True + +A common situation is not knowing the size of the uncompressed data at +decompression time. The following example illustrates a strategy that +can be used in this case. + +.. doctest:: + + >>> import lz4.block + >>> data = b'0' * 2048 + >>> compressed = lz4.block.compress(data, store_size=False) + >>> usize = 255 + >>> max_size = 4096 + >>> while True: + ... try: + ... decompressed = lz4.block.decompress(compressed, uncompressed_size=usize) + ... break + ... except lz4.block.LZ4BlockError: + ... usize *= 2 + ... if usize > max_size: + ... print('Error: data too large or corrupt') + ... break + >>> decompressed == data + True + +In this example we are catching the `lz4.block.LZ4BlockError` +exception. This exception is raised if the LZ4 library call fails, +which can be caused by either the buffer used to store the +uncompressed data (as set by `usize`) being too small, or the input +compressed data being invalid - it is not possible to distinguish the +two cases, and this is why we set an absolute upper bound (`max_size`) +on the memory that can be allocated for the uncompressed data. If we +did not take this precaution, the code, if ppassed invalid compressed +data would continuously try to allocate a larger and larger buffer for +decompression until the system ran out of memory. + Contents ---------------- diff --git a/docs/lz4.rst b/docs/lz4.rst index be058f5a..709ba17c 100644 --- a/docs/lz4.rst +++ b/docs/lz4.rst @@ -2,8 +2,8 @@ lz4 package =========== -Most of the functionality of this package is found in the :py:mod:`lz4.frame` -and the :py:mod:`lz4.block` sub-packages. +Most of the functionality of this package is found in the :py:mod:`lz4.frame`, +the :py:mod:`lz4.block` and the :py:mod:`lz4.stream` sub-packages. Contents -------- @@ -12,5 +12,4 @@ Contents :members: library_version_number, library_version_string, - version - + __version__ diff --git a/docs/lz4.stream.rst b/docs/lz4.stream.rst new file mode 100644 index 00000000..6b9890c1 --- /dev/null +++ b/docs/lz4.stream.rst @@ -0,0 +1,104 @@ +.. default-role:: obj + + +lz4.stream sub-package +====================== + +.. warning:: + This module is unmaintained. + + This sub-package is considered experimental. It was submitted by a community + member who is not able to continue to maintain the module. + + This module is not built as part of the distributed wheels. If you wish to + build and use this module you will need to download and build from source + with the environment variable PYLZ4_EXPERIMENTAL set to TRUE. + + The module needs some re-write, and the tests need extensive work, for this + to become production ready. If you are interested in working on this, please + reach out to the package maintainers. + +This sub-package provides the capability to compress and decompress data using +the `stream specification +`_, +especially the `stream specification based on a double buffer +`_. + +Because the LZ4 stream format does not define a container format, the +Python bindings will by default insert the compressed data size as an +integer at the start of the compressed payload. However, it is +possible to set the bit depth of this compressed data size. + +So far, only the double-buffer based approach is implemented. + +Example usage +------------- +To use the lz4 stream format bindings is straightforward: + +.. doctest:: + + >>> from lz4.stream import LZ4StreamCompressor, LZ4StreamDecompressor + >>> import os + >>> block_size_length = 2 # LZ4 compressed block size stored on 2 bytes + >>> page_size = 8192 # LZ4 context double buffer page size + >>> origin_stream = 10 * 1024 * os.urandom(1024) # 10MiB + >>> # LZ4 stream compression of origin_stream into compressed_stream: + >>> compressed_stream = bytearray() + >>> with LZ4StreamCompressor("double_buffer", page_size, store_comp_size=block_size_length) as proc: + ... offset = 0 + ... while offset < len(origin_stream): + ... chunk = origin_stream[offset:offset + page_size] + ... block = proc.compress(chunk) + ... compressed_stream.extend(block) + ... offset += page_size + >>> # LZ4 stream decompression of compressed_stream into decompressed_stream: + >>> decompressed_stream = bytearray() + >>> with LZ4StreamDecompressor("double_buffer", page_size, store_comp_size=block_size_length) as proc: + ... offset = 0 + ... while offset < len(compressed_stream): + ... block = proc.get_block(compressed_stream[offset:]) + ... chunk = proc.decompress(block) + ... decompressed_stream.extend(chunk) + ... offset += block_size_length + len(block) + >>> decompressed_stream == origin_stream + True + +Out-of-band block size record example +------------------------------------- +.. doctest:: + + >>> from lz4.stream import LZ4StreamCompressor, LZ4StreamDecompressor + >>> import os + >>> page_size = 8192 # LZ4 context double buffer page size + >>> out_of_band_block_sizes = [] # Store the block sizes + >>> origin_stream = 10 * 1024 * os.urandom(1024) # 10MiB + >>> # LZ4 stream compression of origin_stream into compressed_stream: + >>> compressed_stream = bytearray() + >>> with LZ4StreamCompressor("double_buffer", page_size, store_comp_size=0) as proc: + ... offset = 0 + ... while offset < len(origin_stream): + ... chunk = origin_stream[offset:offset + page_size] + ... block = proc.compress(chunk) + ... out_of_band_block_sizes.append(len(block)) + ... compressed_stream.extend(block) + ... offset += page_size + >>> # LZ4 stream decompression of compressed_stream into decompressed_stream: + >>> decompressed_stream = bytearray() + >>> with LZ4StreamDecompressor("double_buffer", page_size, store_comp_size=0) as proc: + ... offset = 0 + ... for block_len in out_of_band_block_sizes: + ... # Sanity check: + ... if offset >= len(compressed_stream): + ... raise LZ4StreamError("Truncated stream") + ... block = compressed_stream[offset:offset + block_len] + ... chunk = proc.decompress(block) + ... decompressed_stream.extend(chunk) + ... offset += block_len + >>> decompressed_stream == origin_stream + True + +Contents +---------------- + +.. automodule:: lz4.stream + :members: LZ4StreamCompressor, LZ4StreamDecompressor diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8c4e3a52..046b9724 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -218,7 +218,7 @@ This is the default. When ``auto_flush`` is ``True``, the compression functions will return compressed data immediately. - + Availability: :py:func:`lz4.frame.compress()`, :py:func:`lz4.frame.compress_begin()`, :py:func:`lz4.frame.open()`, :py:class:`lz4.frame.LZ4FrameCompressor`, :py:class:`lz4.frame.LZ4FrameFile`. @@ -244,3 +244,42 @@ header. Availability of ``source_size``: :py:meth:`lz4.frame.LZ4FrameCompressor.begin()`, :py:func:`lz4.frame.compress_begin()`, :py:func:`lz4.frame.open()`, :py:class:`lz4.frame.LZ4FrameFile`. + + +Working with streamed compressed data +------------------------------------- + +The stream bindings provide capability for working with stream compressed LZ4 +data. This functionality is based on the usage of a ring-buffer (not implemented +yet) or a double-buffer, with the length of each block preceding the compressed +payload in the stream. + +The stream compression reuses a context between each processed block for +performance gain. + +Most of the arguments used to initialize the LZ4 stream context are shared with +the block API. Hereafter, those specific to the LZ4 stream API are detailed. + + +Controlling the buffer size +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``buffer_size`` argument represents the base buffer size used internally for +memory allocation: + +* In the case of the double-buffer strategy, this is the size of each buffer of + the double-buffer. + +When compressing, this size is the maximal length of the input uncompressed +chunks. + +When decompressing, this size is the maximal length of the decompressed data. + + +Storing the compressed data size in the block +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``store_comp_size`` argument allows tuning of the size (in bytes) of the +compressed block, which is prepended to the actual LZ4 compressed payload. +This size can be either on ``1``, ``2`` or ``4`` bytes, or ``0`` for out-of-band +block size record. diff --git a/docs/userguide.rst b/docs/userguide.rst index a8786354..9ec4b41d 100644 --- a/docs/userguide.rst +++ b/docs/userguide.rst @@ -6,3 +6,4 @@ User Guide lz4 lz4.frame lz4.block + lz4.stream diff --git a/lz4/__init__.py b/lz4/__init__.py index 69da37eb..6f2f2430 100644 --- a/lz4/__init__.py +++ b/lz4/__init__.py @@ -1,8 +1,19 @@ -# Package version info, generated on install -from .version import version as __version__ -VERSION = __version__ +# Although the canonical way to get the package version is using pkg_resources +# as below, this turns out to be very slow on systems with lots of packages. +# So, until that is remedied, we'll import the version from a local file +# created by setuptools_scm. + +# from pkg_resources import get_distribution, DistributionNotFound +# try: +# __version__ = get_distribution(__name__).version +# except DistributionNotFound: +# # package is not installed +# pass -from ._version import ( +from .version import version as __version__ +from ._version import ( # noqa: F401 library_version_number, library_version_string, ) + +VERSION = __version__ diff --git a/lz4/_version.c b/lz4/_version.c index d477add6..af606abe 100644 --- a/lz4/_version.c +++ b/lz4/_version.c @@ -29,27 +29,12 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#if defined(_WIN32) && defined(_MSC_VER) -#define inline __inline -#elif defined(__SUNPRO_C) || defined(__hpux) || defined(_AIX) -#define inline -#endif - -#include -#include +#include #include #include #include -#ifndef Py_UNUSED /* This is already defined for Python 3.4 onwards */ -#ifdef __GNUC__ -#define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) -#else -#define Py_UNUSED(name) _unused_ ## name -#endif -#endif - static PyObject * library_version_number (PyObject * Py_UNUSED (self), PyObject * Py_UNUSED (args)) { @@ -120,12 +105,17 @@ static struct PyModuleDef moduledef = module_methods }; -MODULE_INIT_FUNC (_version) +PyMODINIT_FUNC +PyInit__version(void) { PyObject *module = PyModule_Create (&moduledef); if (module == NULL) return NULL; + #ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); + #endif + return module; } diff --git a/lz4/block/__init__.py b/lz4/block/__init__.py index b1b13f9d..6662bab4 100644 --- a/lz4/block/__init__.py +++ b/lz4/block/__init__.py @@ -1 +1 @@ -from ._block import compress, decompress +from ._block import compress, decompress, LZ4BlockError # noqa: F401 diff --git a/lz4/block/_block.c b/lz4/block/_block.c index b4b5da2f..993cc44c 100644 --- a/lz4/block/_block.c +++ b/lz4/block/_block.c @@ -35,8 +35,7 @@ #define inline #endif -#include -#include +#include #include #include @@ -80,10 +79,6 @@ load_le32 (const char *c) return d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); } -#ifdef inline -#undef inline -#endif - static const size_t hdr_size = sizeof (uint32_t); typedef enum @@ -93,6 +88,42 @@ typedef enum HIGH_COMPRESSION } compression_type; +static PyObject * LZ4BlockError; + +static inline int +lz4_compress_generic (int comp, char* source, char* dest, int source_size, int dest_size, + char* dict, int dict_size, int acceleration, int compression) +{ + if (comp != HIGH_COMPRESSION) + { + LZ4_stream_t lz4_state; + LZ4_resetStream (&lz4_state); + if (dict) + { + LZ4_loadDict (&lz4_state, dict, dict_size); + } + if (comp != FAST) + { + acceleration = 1; + } + return LZ4_compress_fast_continue (&lz4_state, source, dest, source_size, dest_size, acceleration); + } + else + { + LZ4_streamHC_t lz4_state; + LZ4_resetStreamHC (&lz4_state, compression); + if (dict) + { + LZ4_loadDictHC (&lz4_state, dict, dict_size); + } + return LZ4_compress_HC_continue (&lz4_state, source, dest, source_size, dest_size); + } +} + +#ifdef inline +#undef inline +#endif + static PyObject * compress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) { @@ -104,10 +135,11 @@ compress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) PyObject *py_dest; char *dest, *dest_start; compression_type comp; - size_t output_size; + int output_size; Py_buffer source; - size_t source_size; + int source_size; int return_bytearray = 0; + Py_buffer dict = {0}; static char *argnames[] = { "source", "mode", @@ -115,40 +147,38 @@ compress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) "acceleration", "compression", "return_bytearray", + "dict", NULL }; - -#if IS_PY3 - if (!PyArg_ParseTupleAndKeywords (args, kwargs, "y*|spiip", argnames, + if (!PyArg_ParseTupleAndKeywords (args, kwargs, "y*|spiipz*", argnames, &source, &mode, &store_size, &acceleration, &compression, - &return_bytearray)) + &return_bytearray, &dict)) { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, kwargs, "s*|siiii", argnames, - &source, - &mode, &store_size, &acceleration, &compression, - &return_bytearray)) + + if (source.len > INT_MAX) { + PyBuffer_Release(&source); + PyBuffer_Release(&dict); + PyErr_Format(PyExc_OverflowError, + "Input too large for LZ4 API"); return NULL; } -#endif - source_size = (size_t) source.len; - - /* We're using 4 bytes for the size of the content in the header. This means - we can store a size as large as the maximum value of an unsinged int. */ - if (store_size && source_size > UINT_MAX) + if (dict.len > INT_MAX) { PyBuffer_Release(&source); + PyBuffer_Release(&dict); PyErr_Format(PyExc_OverflowError, - "Input too large for storing size in 4 byte header"); + "Dictionary too large for LZ4 API"); return NULL; } + source_size = (int) source.len; + if (!strncmp (mode, "default", sizeof ("default"))) { comp = DEFAULT; @@ -164,9 +194,10 @@ compress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) else { PyBuffer_Release(&source); + PyBuffer_Release(&dict); PyErr_Format (PyExc_ValueError, - "Invalid mode argument: %s. Must be one of: standard, fast, high_compression", - mode); + "Invalid mode argument: %s. Must be one of: standard, fast, high_compression", + mode); return NULL; } @@ -199,36 +230,25 @@ compress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) dest_start = dest; } - switch (comp) - { - case DEFAULT: - output_size = LZ4_compress_default (source.buf, dest_start, source_size, - dest_size); - break; - case FAST: - output_size = LZ4_compress_fast (source.buf, dest_start, source_size, - dest_size, acceleration); - break; - case HIGH_COMPRESSION: - output_size = LZ4_compress_HC (source.buf, dest_start, source_size, - dest_size, compression); - break; - } + output_size = lz4_compress_generic (comp, source.buf, dest_start, source_size, + (int) dest_size, dict.buf, (int) dict.len, + acceleration, compression); Py_END_ALLOW_THREADS PyBuffer_Release(&source); + PyBuffer_Release(&dict); if (output_size <= 0) { - PyErr_SetString (PyExc_ValueError, "Compression failed"); + PyErr_SetString (LZ4BlockError, "Compression failed"); PyMem_Free (dest); return NULL; } if (store_size) { - output_size += hdr_size; + output_size += (int) hdr_size; } if (return_bytearray) @@ -258,34 +278,46 @@ decompress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) size_t source_size; PyObject *py_dest; char *dest; - size_t output_size; + int output_size; size_t dest_size; int uncompressed_size = -1; int return_bytearray = 0; + Py_buffer dict = {0}; static char *argnames[] = { "source", "uncompressed_size", "return_bytearray", + "dict", NULL }; -#if IS_PY3 - if (!PyArg_ParseTupleAndKeywords (args, kwargs, "y*|ip", argnames, + if (!PyArg_ParseTupleAndKeywords (args, kwargs, "y*|ipz*", argnames, &source, &uncompressed_size, - &return_bytearray)) + &return_bytearray, &dict)) { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, kwargs, "s*|ii", argnames, - &source, &uncompressed_size, - &return_bytearray)) + + if (source.len > INT_MAX) { + PyBuffer_Release(&source); + PyBuffer_Release(&dict); + PyErr_Format(PyExc_OverflowError, + "Input too large for LZ4 API"); return NULL; } -#endif + + if (dict.len > INT_MAX) + { + PyBuffer_Release(&source); + PyBuffer_Release(&dict); + PyErr_Format(PyExc_OverflowError, + "Dictionary too large for LZ4 API"); + return NULL; + } + source_start = (const char *) source.buf; - source_size = (size_t) source.len; + source_size = (int) source.len; if (uncompressed_size >= 0) { @@ -296,6 +328,7 @@ decompress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) if (source_size < hdr_size) { PyBuffer_Release(&source); + PyBuffer_Release(&dict); PyErr_SetString (PyExc_ValueError, "Input source data size too small"); return NULL; } @@ -304,10 +337,11 @@ decompress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) source_size -= hdr_size; } - if (dest_size < 0 || dest_size > PY_SSIZE_T_MAX) + if (dest_size > INT_MAX) { PyBuffer_Release(&source); - PyErr_Format (PyExc_ValueError, "Invalid size in header: 0x%zu", + PyBuffer_Release(&dict); + PyErr_Format (PyExc_ValueError, "Invalid size: 0x%zu", dest_size); return NULL; } @@ -321,23 +355,26 @@ decompress (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwargs) Py_BEGIN_ALLOW_THREADS output_size = - LZ4_decompress_safe (source_start, dest, source_size, dest_size); + LZ4_decompress_safe_usingDict (source_start, dest, source_size, (int) dest_size, + dict.buf, (int) dict.len); Py_END_ALLOW_THREADS PyBuffer_Release(&source); + PyBuffer_Release(&dict); if (output_size < 0) { - PyErr_Format (PyExc_ValueError, "Corrupt input at byte %zu", -output_size); + PyErr_Format (LZ4BlockError, + "Decompression failed: corrupt input or insufficient space in destination buffer. Error code: %u", + -output_size); PyMem_Free (dest); return NULL; } - else if ((size_t)output_size != dest_size) + else if (((size_t)output_size != dest_size) && (uncompressed_size < 0)) { - /* Better to fail explicitly than to allow fishy data to pass through. */ - PyErr_Format (PyExc_ValueError, - "Decompressor wrote %zu bytes, but %zu bytes expected from header", + PyErr_Format (LZ4BlockError, + "Decompressor wrote %u bytes, but %zu bytes expected from header", output_size, dest_size); PyMem_Free (dest); return NULL; @@ -389,14 +426,16 @@ PyDoc_STRVAR(compress__doc, " block.\n" \ " return_bytearray (bool): If ``False`` (the default) then the function\n" \ " will return a bytes object. If ``True``, then the function will\n" \ - " return a bytearray object.\n\n" \ + " return a bytearray object.\n\n" \ + " dict (str, bytes or buffer-compatible object): If specified, perform\n" \ + " compression using this initial dictionary.\n" \ "Returns:\n" \ " bytes or bytearray: Compressed data.\n"); PyDoc_STRVAR(decompress__doc, - "decompress(source, uncompressed_size=-1, return_bytearray=False)\n\n" \ + "decompress(source, uncompressed_size=-1, return_bytearray=False)\n\n" \ "Decompress source, returning the uncompressed data as a string.\n" \ - "Raises an exception if any error occurs.\n" \ + "Raises an exception if any error occurs.\n" \ "\n" \ "Args:\n" \ " source (str, bytes or buffer-compatible object): Data to decompress.\n" \ @@ -404,12 +443,24 @@ PyDoc_STRVAR(decompress__doc, "Keyword Args:\n" \ " uncompressed_size (int): If not specified or negative, the uncompressed\n" \ " data size is read from the start of the source block. If specified,\n" \ - " it is assumed that the full source data is compressed data.\n" \ + " it is assumed that the full source data is compressed data. If this\n" \ + " argument is specified, it is considered to be a maximum possible size\n" \ + " for the buffer used to hold the uncompressed data, and so less data\n" \ + " may be returned. If `uncompressed_size` is too small, `LZ4BlockError`\n" \ + " will be raised. By catching `LZ4BlockError` it is possible to increase\n" \ + " `uncompressed_size` and try again.\n" \ " return_bytearray (bool): If ``False`` (the default) then the function\n" \ " will return a bytes object. If ``True``, then the function will\n" \ " return a bytearray object.\n\n" \ + " dict (str, bytes or buffer-compatible object): If specified, perform\n" \ + " decompression using this initial dictionary.\n" \ + "\n" \ "Returns:\n" \ - " bytes or bytearray: Decompressed data.\n"); + " bytes or bytearray: Decompressed data.\n" \ + "\n" \ + "Raises:\n" \ + " LZ4BlockError: raised if the call to the LZ4 library fails. This can be\n" \ + " caused by `uncompressed_size` being too small, or invalid data.\n"); PyDoc_STRVAR(lz4block__doc, "A Python wrapper for the LZ4 block protocol" @@ -446,7 +497,8 @@ static struct PyModuleDef moduledef = module_methods }; -MODULE_INIT_FUNC (_block) +PyMODINIT_FUNC +PyInit__block(void) { PyObject *module = PyModule_Create (&moduledef); @@ -458,5 +510,17 @@ MODULE_INIT_FUNC (_block) PyModule_AddIntConstant (module, "HC_LEVEL_OPT_MIN", LZ4HC_CLEVEL_OPT_MIN); PyModule_AddIntConstant (module, "HC_LEVEL_MAX", LZ4HC_CLEVEL_MAX); + LZ4BlockError = PyErr_NewExceptionWithDoc("_block.LZ4BlockError", "Call to LZ4 library failed.", NULL, NULL); + if (LZ4BlockError == NULL) + { + return NULL; + } + Py_INCREF(LZ4BlockError); + PyModule_AddObject(module, "LZ4BlockError", LZ4BlockError); + + #ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); + #endif + return module; } diff --git a/lz4/frame/__init__.py b/lz4/frame/__init__.py index 28e8e45a..2a82cd08 100644 --- a/lz4/frame/__init__.py +++ b/lz4/frame/__init__.py @@ -25,9 +25,9 @@ __doc__ = _doc try: - import _compression # Python 3.6 and later + import compression._common._streams as _compression # Python 3.14 except ImportError: - from . import _compression + import _compression # Python 3.9 - 3.13 BLOCKSIZE_DEFAULT = _BLOCKSIZE_DEFAULT @@ -219,13 +219,13 @@ def begin(self, source_size=0): block_checksum=self.block_checksum, auto_flush=self.auto_flush, return_bytearray=self.return_bytearray, - source_size=source_size + source_size=source_size, ) self._started = True return result else: raise RuntimeError( - 'LZ4FrameCompressor.begin() called after already initialized' + "LZ4FrameCompressor.begin() called after already initialized" ) def compress(self, data): # noqa: F811 @@ -268,7 +268,7 @@ def flush(self): This returns a ``bytes`` or ``bytearray`` object containing any data stored in the compressor's internal buffers and a frame footer. - The LZ4FrameCompressor instance may be re-used after this method has + The LZ4FrameCompressor instance may be reused after this method has been called to create a new frame of compressed data. Returns: @@ -287,13 +287,31 @@ def flush(self): def reset(self): """Reset the `LZ4FrameCompressor` instance. - This allows the `LZ4FrameCompression` instance to be re-used after an + This allows the `LZ4FrameCompression` instance to be reused after an error. """ self._context = None self._started = False + def has_context(self): + """Return whether the compression context exists. + + Returns: + bool: ``True`` if the compression context exists, ``False`` + otherwise. + """ + return self._context is not None + + def started(self): + """Return whether the compression frame has been started. + + Returns: + bool: ``True`` if the compression frame has been started, ``False`` + otherwise. + """ + return self._started + class LZ4FrameDecompressor(object): """Create a LZ4 frame decompressor object. @@ -342,7 +360,7 @@ def __exit__(self, exception_type, exception, traceback): def reset(self): """Reset the decompressor state. - This is useful after an error occurs, allowing re-use of the instance. + This is useful after an error occurs, allowing reuse of the instance. """ reset_decompression_context(self._context) @@ -387,6 +405,8 @@ def decompress(self, data, max_length=-1): # noqa: F811 bytes: Uncompressed data """ + if not isinstance(data, (bytes, bytearray)): + data = memoryview(data).tobytes() if self._unconsumed_data: data = self._unconsumed_data + data @@ -396,7 +416,7 @@ def decompress(self, data, max_length=-1): # noqa: F811 data, max_length=max_length, return_bytearray=self._return_bytearray, - ) + ) if bytes_read < len(data): if eoframe: @@ -523,9 +543,8 @@ def __init__(self, filename=None, mode='r', self._buffer = io.BufferedReader(raw) if self._mode == _MODE_WRITE: - self._fp.write( - self._compressor.begin(source_size=source_size) - ) + self._source_size = source_size + self._fp.write(self._compressor.begin(source_size=source_size)) def close(self): """Flush and close the file. @@ -540,7 +559,7 @@ def close(self): self._buffer.close() self._buffer = None elif self._mode == _MODE_WRITE: - self._fp.write(self._compressor.flush()) + self.flush() self._compressor = None finally: try: @@ -617,6 +636,17 @@ def peek(self, size=-1): # returns at least one byte (except at EOF) return self._buffer.peek(size) + def readall(self): + chunks = bytearray() + + while True: + data = self.read(io.DEFAULT_BUFFER_SIZE) + chunks += data + if not data: + break + + return bytes(chunks) + def read(self, size=-1): """Read up to ``size`` uncompressed bytes from the file. @@ -632,6 +662,9 @@ def read(self, size=-1): """ self._check_can_read() + + if size < 0 and sys.version_info >= (3, 10): + return self.readall() return self._buffer.read(size) def read1(self, size=-1): @@ -679,9 +712,10 @@ def readline(self, size=-1): def write(self, data): """Write a bytes object to the file. - Returns the number of uncompressed bytes written, which is always - ``len(data)``. Note that due to buffering, the file on disk may not - reflect the data written until close() is called. + Returns the number of uncompressed bytes written, which is + always the length of data in bytes. Note that due to buffering, + the file on disk may not reflect the data written until close() + is called. Args: data(bytes): uncompressed data to compress and write to the file @@ -690,11 +724,33 @@ def write(self, data): int: the number of uncompressed bytes written to the file """ + if isinstance(data, (bytes, bytearray)): + length = len(data) + else: + # accept any data that supports the buffer protocol + data = memoryview(data) + length = data.nbytes + self._check_can_write() + + if not self._compressor.started(): + header = self._compressor.begin(source_size=self._source_size) + self._fp.write(header) + compressed = self._compressor.compress(data) self._fp.write(compressed) - self._pos += len(data) - return len(data) + self._pos += length + return length + + def flush(self): + """Flush the file, keeping it open. + + May be called more than once without error. The file may continue + to be used normally after flushing. + """ + if self.writable() and self._compressor.has_context(): + self._fp.write(self._compressor.flush()) + self._fp.flush() def seek(self, offset, whence=io.SEEK_SET): """Change the file position. @@ -829,6 +885,7 @@ def open(filename, mode="rb", block_checksum=block_checksum, auto_flush=auto_flush, return_bytearray=return_bytearray, + source_size=source_size, ) if 't' in mode: diff --git a/lz4/frame/_compression.py b/lz4/frame/_compression.py deleted file mode 100644 index c6977449..00000000 --- a/lz4/frame/_compression.py +++ /dev/null @@ -1,158 +0,0 @@ -# Local python-lz4 copy of this file taken from the CPython standard library -# for earlier Python versions that don't ship with this file. This file has -# been modified to work on Python < 3.0. - -"""Internal classes used by the gzip, lzma and bz2 modules""" - -import io -# Ensure super has Python 3 semantics even on Python 2 -from builtins import super - - -BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE # Compressed data read chunk size - - -class BaseStream(io.BufferedIOBase): - """Mode-checking helper functions.""" - - def _check_not_closed(self): - if self.closed: - raise ValueError("I/O operation on closed file") - - def _check_can_read(self): - if not self.readable(): - raise io.UnsupportedOperation("File not open for reading") - - def _check_can_write(self): - if not self.writable(): - raise io.UnsupportedOperation("File not open for writing") - - def _check_can_seek(self): - if not self.readable(): - raise io.UnsupportedOperation("Seeking is only supported " - "on files open for reading") - if not self.seekable(): - raise io.UnsupportedOperation("The underlying file object " - "does not support seeking") - - -class DecompressReader(io.RawIOBase): - """Adapts the decompressor API to a RawIOBase reader API""" - - def readable(self): - return True - - def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args): - self._fp = fp - self._eof = False - self._pos = 0 # Current offset in decompressed stream - - # Set to size of decompressed stream once it is known, for SEEK_END - self._size = -1 - - # Save the decompressor factory and arguments. - # If the file contains multiple compressed streams, each - # stream will need a separate decompressor object. A new decompressor - # object is also needed when implementing a backwards seek(). - self._decomp_factory = decomp_factory - self._decomp_args = decomp_args - self._decompressor = self._decomp_factory(**self._decomp_args) - - # Exception class to catch from decompressor signifying invalid - # trailing data to ignore - self._trailing_error = trailing_error - - def close(self): - self._decompressor = None - return super().close() - - def seekable(self): - return self._fp.seekable() - - def readinto(self, b): - with memoryview(b) as view, view.cast("B") as byte_view: - data = self.read(len(byte_view)) - byte_view[:len(data)] = data - return len(data) - - def read(self, size=-1): - if size < 0: - return self.readall() - - if not size or self._eof: - return b"" - data = None # Default if EOF is encountered - # Depending on the input data, our call to the decompressor may not - # return any data. In this case, try again after reading another block. - while True: - if self._decompressor.eof: - rawblock = (self._decompressor.unused_data or - self._fp.read(BUFFER_SIZE)) - if not rawblock: - break - # Continue to next stream. - self._decompressor = self._decomp_factory( - **self._decomp_args) - try: - data = self._decompressor.decompress(rawblock, size) - except self._trailing_error: - # Trailing data isn't a valid compressed stream; ignore it. - break - else: - if self._decompressor.needs_input: - rawblock = self._fp.read(BUFFER_SIZE) - if not rawblock: - raise EOFError("Compressed file ended before the " - "end-of-stream marker was reached") - else: - rawblock = b"" - data = self._decompressor.decompress(rawblock, size) - if data: - break - if not data: - self._eof = True - self._size = self._pos - return b"" - self._pos += len(data) - return data - - # Rewind the file to the beginning of the data stream. - def _rewind(self): - self._fp.seek(0) - self._eof = False - self._pos = 0 - self._decompressor = self._decomp_factory(**self._decomp_args) - - def seek(self, offset, whence=io.SEEK_SET): - # Recalculate offset as an absolute file position. - if whence == io.SEEK_SET: - pass - elif whence == io.SEEK_CUR: - offset = self._pos + offset - elif whence == io.SEEK_END: - # Seeking relative to EOF - we need to know the file's size. - if self._size < 0: - while self.read(io.DEFAULT_BUFFER_SIZE): - pass - offset = self._size + offset - else: - raise ValueError("Invalid value for whence: {}".format(whence)) - - # Make it so that offset is the number of bytes to skip forward. - if offset < self._pos: - self._rewind() - else: - offset -= self._pos - - # Read and discard data until we reach the desired position. - while offset > 0: - data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset)) - if not data: - break - offset -= len(data) - - return self._pos - - def tell(self): - """Return the current file position.""" - return self._pos diff --git a/lz4/frame/_frame.c b/lz4/frame/_frame.c index 5e8dbcac..440b0b50 100644 --- a/lz4/frame/_frame.c +++ b/lz4/frame/_frame.c @@ -35,21 +35,12 @@ #define inline #endif -#include -#include +#include #include #include /* Needed for LZ4_VERSION_NUMBER only. */ #include -#ifndef Py_UNUSED /* This is already defined for Python 3.4 onwards */ -#ifdef __GNUC__ -#define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) -#else -#define Py_UNUSED(name) _unused_ ## name -#endif -#endif - static const char * compression_context_capsule_name = "_frame.LZ4F_cctx"; static const char * decompression_context_capsule_name = "_frame.LZ4F_dctx"; @@ -149,7 +140,6 @@ compress (PyObject * Py_UNUSED (self), PyObject * args, memset (&preferences, 0, sizeof preferences); -#if IS_PY3 if (!PyArg_ParseTupleAndKeywords (args, keywds, "y*|iippppp", kwlist, &source, &preferences.compressionLevel, @@ -162,20 +152,6 @@ compress (PyObject * Py_UNUSED (self), PyObject * args, { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, keywds, "s*|iiiiiii", kwlist, - &source, - &preferences.compressionLevel, - &preferences.frameInfo.blockSizeID, - &content_checksum, - &block_checksum, - &block_linked, - &store_size, - &return_bytearray)) - { - return NULL; - } -#endif if (content_checksum) { @@ -290,7 +266,7 @@ compress_begin (PyObject * Py_UNUSED (self), PyObject * args, PyObject * keywds) { PyObject *py_context = NULL; - Py_ssize_t source_size = 0; + Py_ssize_t source_size = (Py_ssize_t) 0; int return_bytearray = 0; int content_checksum = 0; int block_checksum = 0; @@ -318,7 +294,6 @@ compress_begin (PyObject * Py_UNUSED (self), PyObject * args, memset (&preferences, 0, sizeof preferences); -#if IS_PY3 if (!PyArg_ParseTupleAndKeywords (args, keywds, "O|kiippppp", kwlist, &py_context, &source_size, @@ -333,22 +308,7 @@ compress_begin (PyObject * Py_UNUSED (self), PyObject * args, { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, keywds, "O|kiiiiiii", kwlist, - &py_context, - &source_size, - &preferences.compressionLevel, - &preferences.frameInfo.blockSizeID, - &content_checksum, - &block_checksum, - &block_linked, - &preferences.autoFlush, - &return_bytearray - )) - { - return NULL; - } -#endif + if (content_checksum) { preferences.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled; @@ -471,7 +431,8 @@ compress_chunk (PyObject * Py_UNUSED (self), PyObject * args, NULL }; -#if IS_PY3 + memset (&compress_options, 0, sizeof compress_options); + if (!PyArg_ParseTupleAndKeywords (args, keywds, "Oy*|p", kwlist, &py_context, &source, @@ -479,15 +440,6 @@ compress_chunk (PyObject * Py_UNUSED (self), PyObject * args, { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, keywds, "Os*|i", kwlist, - &py_context, - &source, - &return_bytearray)) - { - return NULL; - } -#endif source_size = source.len; @@ -594,7 +546,9 @@ compress_flush (PyObject * Py_UNUSED (self), PyObject * args, PyObject * keywds) "return_bytearray", NULL }; -#if IS_PY3 + + memset (&compress_options, 0, sizeof compress_options); + if (!PyArg_ParseTupleAndKeywords (args, keywds, "O|pp", kwlist, &py_context, &end_frame, @@ -602,15 +556,7 @@ compress_flush (PyObject * Py_UNUSED (self), PyObject * args, PyObject * keywds) { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, keywds, "O|ii", kwlist, - &py_context, - &end_frame, - &return_bytearray)) - { - return NULL; - } -#endif + if (!end_frame && LZ4_versionNumber() < 10800) { PyErr_SetString (PyExc_RuntimeError, @@ -712,19 +658,11 @@ get_frame_info (PyObject * Py_UNUSED (self), PyObject * args, NULL }; -#if IS_PY3 if (!PyArg_ParseTupleAndKeywords (args, keywds, "y*", kwlist, &py_source)) { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, keywds, "s*", kwlist, - &py_source)) - { - return NULL; - } -#endif Py_BEGIN_ALLOW_THREADS @@ -1016,6 +954,9 @@ __decompress(LZ4F_dctx * context, char * source, size_t source_size, LZ4F_frameInfo_t frame_info; LZ4F_decompressOptions_t options; int end_of_frame = 0; + int resize_factor = 1; + + memset(&options, 0, sizeof options); Py_BEGIN_ALLOW_THREADS @@ -1060,9 +1001,9 @@ __decompress(LZ4F_dctx * context, char * source, size_t source_size, } else { - if (max_length >= 0) + if (max_length >= (Py_ssize_t) 0) { - destination_size = max_length; + destination_size = (size_t) max_length; } else { @@ -1082,7 +1023,10 @@ __decompress(LZ4F_dctx * context, char * source, size_t source_size, Py_UNBLOCK_THREADS - if (full_frame) + /* Only set stableDst = 1 if we are sure no PyMem_Realloc will be called since + when stableDst = 1 the LZ4 library stores a pointer to the last compressed + data, which may be invalid after a PyMem_Realloc. */ + if (full_frame && max_length >= (Py_ssize_t) 0) { options.stableDst = 1; } @@ -1124,6 +1068,7 @@ __decompress(LZ4F_dctx * context, char * source, size_t source_size, PyErr_Format (PyExc_RuntimeError, "LZ4F_decompress failed with code: %s", LZ4F_getErrorName (result)); + PyMem_Free (destination); return NULL; } @@ -1147,17 +1092,24 @@ __decompress(LZ4F_dctx * context, char * source, size_t source_size, /* Destination buffer is full. So, stop decompressing if max_length is set. Otherwise expand the destination buffer. */ - if (max_length >= 0) + if (max_length >= (Py_ssize_t) 0) { break; } else { - /* Expand destination buffer. result is an indication of number of - source bytes remaining, so we'll use this to estimate the new - size of the destination buffer. */ + /* Expand the destination buffer. We've tried various strategies + here to estimate the compression ratio so far and adjust the + buffer size accordingly. However, that grows the buffer too + slowly. The best choices found were to either double the buffer + size each time, or to grow faster by multiplying the buffer + size by 2^N, where N is the number of resizes. We take the + latter approach, though the former approach may actually be + good enough in practice. */ char * buff; - destination_size += 3 * result; + + resize_factor *= 2; + destination_size *= resize_factor; Py_BLOCK_THREADS buff = PyMem_Realloc (destination, destination_size); @@ -1261,7 +1213,6 @@ decompress (PyObject * Py_UNUSED (self), PyObject * args, NULL }; -#if IS_PY3 if (!PyArg_ParseTupleAndKeywords (args, keywds, "y*|pp", kwlist, &py_source, &return_bytearray, @@ -1270,16 +1221,6 @@ decompress (PyObject * Py_UNUSED (self), PyObject * args, { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, keywds, "s*|ii", kwlist, - &py_source, - &return_bytearray, - &return_bytes_read - )) - { - return NULL; - } -#endif Py_BEGIN_ALLOW_THREADS result = LZ4F_createDecompressionContext (&context, LZ4F_VERSION); @@ -1329,7 +1270,7 @@ decompress_chunk (PyObject * Py_UNUSED (self), PyObject * args, Py_buffer py_source; char * source; size_t source_size; - Py_ssize_t max_length = -1; + Py_ssize_t max_length = (Py_ssize_t) -1; int return_bytearray = 0; static char *kwlist[] = { "context", "data", @@ -1338,8 +1279,7 @@ decompress_chunk (PyObject * Py_UNUSED (self), PyObject * args, NULL }; -#if IS_PY3 - if (!PyArg_ParseTupleAndKeywords (args, keywds, "Oy*|kp", kwlist, + if (!PyArg_ParseTupleAndKeywords (args, keywds, "Oy*|np", kwlist, &py_context, &py_source, &max_length, @@ -1348,17 +1288,6 @@ decompress_chunk (PyObject * Py_UNUSED (self), PyObject * args, { return NULL; } -#else - if (!PyArg_ParseTupleAndKeywords (args, keywds, "Os*|ki", kwlist, - &py_context, - &py_source, - &max_length, - &return_bytearray - )) - { - return NULL; - } -#endif context = (LZ4F_dctx *) PyCapsule_GetPointer (py_context, decompression_context_capsule_name); @@ -1401,7 +1330,7 @@ PyDoc_STRVAR( ); #define COMPRESS_KWARGS_DOCSTRING \ - " block_size (int): Sepcifies the maximum blocksize to use.\n" \ + " block_size (int): Specifies the maximum blocksize to use.\n" \ " Options:\n\n" \ " - `lz4.frame.BLOCKSIZE_DEFAULT`: the lz4 library default\n" \ " - `lz4.frame.BLOCKSIZE_MAX64KB`: 64 kB\n" \ @@ -1468,7 +1397,7 @@ PyDoc_STRVAR ( compress_begin__doc, "compress_begin(context, source_size=0, compression_level=0, block_size=0,\n" \ - "content_checksum=0, content_size=1, block_mode=0, frame_type=0,\n" \ + "content_checksum=0, content_size=1, block_linked=0, frame_type=0,\n" \ "auto_flush=1)\n" \ "\n" \ "Creates a frame header from a compression context.\n\n" \ @@ -1537,7 +1466,7 @@ PyDoc_STRVAR "data will also be included in the returned data.\n" \ "\n" \ "If the ``end_frame`` argument is ``True``, the compression context will be\n" \ - "reset and can be re-used.\n" \ + "reset and can be reused.\n" \ "\n" \ "Args:\n" \ " context (cCtx): Compression context\n" \ @@ -1644,12 +1573,12 @@ PyDoc_STRVAR PyDoc_STRVAR ( decompress_chunk__doc, - "decompress(context, data)\n" \ + "decompress_chunk(context, data, max_length=-1)\n" \ "\n" \ - "Decompresses part of a frame of compressed data.\n" \ - "\n" \ - "The returned uncompressed data should be concatenated with the data returned\n" \ - "from previous calls to `lz4.frame.decompress_chunk`\n" \ + "Decompresses part of a frame of compressed data.\n" \ + "\n" \ + "The returned uncompressed data should be concatenated with the data\n" \ + "returned from previous calls to `lz4.frame.decompress_chunk`\n" \ "\n" \ "Args:\n" \ " context (dCtx): decompression context\n" \ @@ -1657,14 +1586,14 @@ PyDoc_STRVAR " frame of compressed data\n" \ "\n" \ "Keyword Args:\n" \ - " max_length (int): if non-negative this specifies the maximum number" \ + " max_length (int): if non-negative this specifies the maximum number\n" \ " of bytes of uncompressed data to return. Default is ``-1``.\n" \ " return_bytearray (bool): If ``True`` a bytearray object will be\n" \ " returned.If ``False``, a string of bytes is returned. The\n" \ " default is ``False``.\n" \ "\n" \ "Returns:\n" \ - " tuple: uncompressed data, bytes read, end of frame indicator\n" \ + " tuple: uncompressed data, bytes read, end of frame indicator\n" \ "\n" \ " This function returns a tuple consisting of:\n" \ "\n" \ @@ -1672,8 +1601,8 @@ PyDoc_STRVAR " - The number of bytes consumed from input ``data`` as an ``int``\n" \ " - The end of frame indicator as a ``bool``.\n" \ "\n" - "The end of frame indicator is ``True`` if the end of the compressed frame\n" \ - "has been reached, or ``False`` otherwise\n" + "The end of frame indicator is ``True`` if the end of the compressed\n" \ + "frame has been reached, or ``False`` otherwise\n" ); static PyMethodDef module_methods[] = @@ -1734,7 +1663,8 @@ static struct PyModuleDef moduledef = module_methods }; -MODULE_INIT_FUNC (_frame) +PyMODINIT_FUNC +PyInit__frame(void) { PyObject *module = PyModule_Create (&moduledef); @@ -1747,5 +1677,9 @@ MODULE_INIT_FUNC (_frame) PyModule_AddIntConstant (module, "BLOCKSIZE_MAX1MB", LZ4F_max1MB); PyModule_AddIntConstant (module, "BLOCKSIZE_MAX4MB", LZ4F_max4MB); + #ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); + #endif + return module; } diff --git a/lz4/stream/__init__.py b/lz4/stream/__init__.py new file mode 100644 index 00000000..58ce298a --- /dev/null +++ b/lz4/stream/__init__.py @@ -0,0 +1,204 @@ +from ._stream import _create_context, _compress, _decompress, _get_block +from ._stream import LZ4StreamError, _compress_bound, _input_bound, LZ4_MAX_INPUT_SIZE # noqa: F401 + + +__doc__ = """\ +A Python wrapper for the LZ4 stream protocol. + +""" + + +class LZ4StreamDecompressor: + """ LZ4 stream decompression context. + + """ + def __init__(self, strategy, buffer_size, return_bytearray=False, store_comp_size=4, dictionary=""): + """ Instantiates and initializes a LZ4 stream decompression context. + + Args: + strategy (str): Buffer management strategy. Can be: ``double_buffer``. + buffer_size (int): Size of one buffer of the double-buffer used + internally for stream decompression in the case of ``double_buffer`` + strategy. + + Keyword Args: + return_bytearray (bool): If ``False`` (the default) then the function + will return a ``bytes`` object. If ``True``, then the function will + return a ``bytearray`` object. + store_comp_size (int): Specify the size in bytes of the following + compressed block. Can be: ``0`` (meaning out-of-band block size), + ``1``, ``2`` or ``4`` (default: ``4``). + dictionary (str, bytes or buffer-compatible object): If specified, + perform decompression using this initial dictionary. + + Raises: + Exceptions occurring during the context initialization. + + OverflowError: raised if the ``dictionary`` parameter is too large + for the LZ4 context. + ValueError: raised if some parameters are invalid. + MemoryError: raised if some internal resources cannot be allocated. + RuntimeError: raised if some internal resources cannot be initialized. + + """ + return_bytearray = 1 if return_bytearray else 0 + + self._context = _create_context(strategy, "decompress", buffer_size, + return_bytearray=return_bytearray, + store_comp_size=store_comp_size, + dictionary=dictionary) + + def __enter__(self): + """ Enter the LZ4 stream context. + + """ + return self + + def __exit__(self, exc_type, exc, exc_tb): + """ Exit the LZ4 stream context. + + """ + pass + + def decompress(self, chunk): + """ Decompress streamed compressed data. + + Decompress the given ``chunk``, using the given LZ4 stream context, + Raises an exception if any error occurs. + + Args: + chunk (str, bytes or buffer-compatible object): Data to decompress + + Returns: + bytes or bytearray: Decompressed data. + + Raises: + Exceptions occurring during decompression. + + ValueError: raised if the source is inconsistent with a finite LZ4 + stream block chain. + MemoryError: raised if the work output buffer cannot be allocated. + OverflowError: raised if the source is too large for being decompressed + in the given context. + LZ4StreamError: raised if the call to the LZ4 library fails. This can be + caused by ``decompressed_size`` being too small, or invalid data. + + """ + return _decompress(self._context, chunk) + + def get_block(self, stream): + """ Return the first LZ4 compressed block from ``stream``. + + Args: + stream (str, bytes or buffer-compatible object): LZ4 compressed stream. + + Returns: + bytes or bytearray: LZ4 compressed data block. + + Raises: + Exceptions occurring while getting the first block from ``stream``. + + BufferError: raised if the function cannot return a complete LZ4 + compressed block from the stream (i.e. the stream does not hold + a complete block). + MemoryError: raised if the output buffer cannot be allocated. + OverflowError: raised if the source is too large for being handled by + the given context. + LZ4StreamError: raised if used while in an out-of-band block size record + configuration. + + """ + return _get_block(self._context, stream) + + +class LZ4StreamCompressor: + """ LZ4 stream compressing context. + + """ + def __init__(self, strategy, buffer_size, mode="default", acceleration=True, compression_level=9, + return_bytearray=False, store_comp_size=4, dictionary=""): + """ Instantiates and initializes a LZ4 stream compression context. + + Args: + strategy (str): Buffer management strategy. Can be: ``double_buffer``. + buffer_size (int): Base size of the buffer(s) used internally for stream + compression/decompression. In the ``double_buffer`` strategy case, + this is the size of each buffer of the double-buffer. + + Keyword Args: + mode (str): If ``default`` or unspecified use the default LZ4 + compression mode. Set to ``fast`` to use the fast compression + LZ4 mode at the expense of compression. Set to + ``high_compression`` to use the LZ4 high-compression mode at + the expense of speed. + acceleration (int): When mode is set to ``fast`` this argument + specifies the acceleration. The larger the acceleration, the + faster the but the lower the compression. The default + compression corresponds to a value of ``1``. + compression_level (int): When mode is set to ``high_compression`` this + argument specifies the compression. Valid values are between + ``1`` and ``12``. Values between ``4-9`` are recommended, and + ``9`` is the default. Only relevant if ``mode`` is + ``high_compression``. + return_bytearray (bool): If ``False`` (the default) then the function + will return a bytes object. If ``True``, then the function will + return a bytearray object. + store_comp_size (int): Specify the size in bytes of the following + compressed block. Can be: ``0`` (meaning out-of-band block size), + ``1``, ``2`` or ``4`` (default: ``4``). + dictionary (str, bytes or buffer-compatible object): If specified, + perform compression using this initial dictionary. + + Raises: + Exceptions occurring during the context initialization. + + OverflowError: raised if the ``dictionary`` parameter is too large + for the LZ4 context. + ValueError: raised if some parameters are invalid. + MemoryError: raised if some internal resources cannot be allocated. + RuntimeError: raised if some internal resources cannot be initialized. + + """ + return_bytearray = 1 if return_bytearray else 0 + + self._context = _create_context(strategy, "compress", buffer_size, + mode=mode, + acceleration=acceleration, + compression_level=compression_level, + return_bytearray=return_bytearray, + store_comp_size=store_comp_size, + dictionary=dictionary) + + def __enter__(self): + """ Enter the LZ4 stream context. + + """ + return self + + def __exit__(self, exc_type, exc, exc_tb): + """ Exit the LZ4 stream context. + + """ + pass + + def compress(self, chunk): + """ Stream compress given ``chunk`` of data. + + Compress the given ``chunk``, using the given LZ4 stream context, + returning the compressed data as a ``bytearray`` or as a ``bytes`` object. + + Args: + chunk (str, bytes or buffer-compatible object): Data to compress + + Returns: + bytes or bytearray: Compressed data. + + Raises: + Exceptions occurring during compression. + + OverflowError: raised if the source is too large for being compressed in + the given context. + LZ4StreamError: raised if the call to the LZ4 library fails. + + """ + return _compress(self._context, chunk) diff --git a/lz4/stream/_stream.c b/lz4/stream/_stream.c new file mode 100644 index 00000000..4c51d89d --- /dev/null +++ b/lz4/stream/_stream.c @@ -0,0 +1,1657 @@ +/* + * Copyright (c) 2019, Samuel Martin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_WIN32) && defined(_MSC_VER) +#define inline __inline +#elif defined(__SUNPRO_C) || defined(__hpux) || defined(_AIX) +#define inline +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) && defined(_MSC_VER) && _MSC_VER < 1600 +/* MSVC 2008 and earlier lacks stdint.h */ +typedef signed __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef signed __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; + +#if !defined(UINT8_MAX) +#define UINT8_MAX 0xff +#endif +#if !defined(UINT16_MAX) +#define UINT16_MAX 0xffff +#endif +#if !defined(UINT32_MAX) +#define UINT32_MAX 0xffffffff +#endif +#if !defined(INT32_MAX) +#define INT32_MAX 0x7fffffff +#endif +#if !defined(CHAR_BIT) +#define CHAR_BIT 8 +#endif + +#else +/* Not MSVC, or MSVC 2010 or higher */ +#include +#endif /* _WIN32 && _MSC_VER && _MSC_VER < 1600 */ + +#define LZ4_VERSION_NUMBER_1_9_0 10900 + +static const char * stream_context_capsule_name = "_stream.LZ4S_ctx"; + +typedef enum { + DOUBLE_BUFFER, + RING_BUFFER, + + BUFFER_STRATEGY_COUNT /* must be the last entry */ +} buffer_strategy_e; + +typedef enum { + COMPRESS, + DECOMPRESS, +} direction_e; + +typedef enum { + DEFAULT, + FAST, + HIGH_COMPRESSION, +} compression_type_e; + + +/* Forward declarations */ +static PyObject * LZ4StreamError; + +#define DOUBLE_BUFFER_PAGE_COUNT (2) + +#define DOUBLE_BUFFER_INDEX_MIN (0) +#define DOUBLE_BUFFER_INDEX_INVALID (-1) + +#define _GET_MAX_UINT(byte_depth, type) (type)( ( 1ULL << ( CHAR_BIT * (byte_depth) ) ) - 1 ) +#define _GET_MAX_UINT32(byte_depth) _GET_MAX_UINT((byte_depth), uint32_t) + +typedef struct { + char * buf; + unsigned int len; +} buffer_t; + +/* forward declaration */ +typedef struct stream_context_t stream_context_t; + +typedef struct { + /** + * Release buffer strategy's resources. + * + * \param[inout] context Stream context. + */ + void (*release_resources) (stream_context_t *context); + + /** + * Reserve buffer strategy's resources. + * + * \param[inout] context Stream context. + * \param[in] buffer_size Base buffer size to allocate and initialize. + * + * \return 0 on success, non-0 otherwise + */ + int (*reserve_resources) (stream_context_t * context, unsigned int buffer_size); + + /** + * Return a pointer on the work buffer. + * + * \param[inout] context Stream context. + * + * \return A pointer on the work buffer. + */ + char * (*get_work_buffer) (const stream_context_t * context); + + /** + * Return the length of (available space in) the work buffer. + * + * \param[inout] context Stream context. + * + * \return The length of the work buffer. + */ + unsigned int (*get_work_buffer_size) (const stream_context_t * context); + + /** + * Return the length of the output buffer. + * + * \param[inout] context Stream context. + * + * \return The length the output buffer. + */ + unsigned int (*get_dest_buffer_size) (const stream_context_t * context); + + /** + * Update the stream context at the end of the LZ4 operation (a block compression or + * decompression). + * + * \param[inout] context Stream context. + * + * \return 0 on success, non-0 otherwise + */ + int (*update_context_after_process) (stream_context_t * context); +} strategy_ops_t; + +struct stream_context_t { + /* Buffer strategy resources */ + struct { + strategy_ops_t * ops; + union { + /* Double-buffer */ + struct { + char * buf; + unsigned int page_size; + char * pages[DOUBLE_BUFFER_PAGE_COUNT]; + int index; + } double_buffer; + + /* Ring-buffer (not implemented) */ + struct { + char * buf; + unsigned int size; + } ring_buffer; + } data; + } strategy; + + buffer_t output; + + /* LZ4 state */ + union { + union { + LZ4_stream_t * fast; + LZ4_streamHC_t * hc; + } compress; + LZ4_streamDecode_t * decompress; + void * context; + } lz4_state; + + /* LZ4 configuration */ + struct { + int acceleration; + int compression_level; + int store_comp_size; + int return_bytearray; + direction_e direction; + compression_type_e comp; + } config; +}; + + +#ifndef PyCapsule_Type +#define _PyCapsule_get_context(py_ctx) \ + ((stream_context_t *) PyCapsule_GetPointer((py_ctx), stream_context_capsule_name)) +#else +/* Compatibility with 2.6 via capsulethunk. */ +#define _PyCapsule_get_context(py_ctx) \ + ((stream_context_t *) (py_ctx)) +#endif + + +static inline void +store_le8 (char * c, uint8_t x) +{ + c[0] = x & 0xff; +} + + +/************************* + * block content helpers * + *************************/ +static inline uint8_t +load_le8 (const char * c) +{ + const uint8_t * d = (const uint8_t *) c; + return d[0]; +} + +static inline void +store_le16 (char * c, uint16_t x) +{ + c[0] = x & 0xff; + c[1] = (x >> 8) & 0xff; +} + +static inline uint16_t +load_le16 (const char * c) +{ + const uint8_t * d = (const uint8_t *) c; + return (d[0] | (d[1] << 8)); +} + +static inline void +store_le32 (char * c, uint32_t x) +{ + c[0] = x & 0xff; + c[1] = (x >> 8) & 0xff; + c[2] = (x >> 16) & 0xff; + c[3] = (x >> 24) & 0xff; +} + +static inline uint32_t +load_le32 (const char * c) +{ + const uint8_t * d = (const uint8_t *) c; + return (d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24)); +} + +static inline int +load_block_length (int block_length_size, const char *buf) +{ + int block_length = -1; + switch (block_length_size) + { + case 1: + block_length = load_le8 (buf); + break; + + case 2: + block_length = load_le16 (buf); + break; + + case 4: + block_length = load_le32 (buf); + break; + + case 0: + /* fallthrough */ + default: + break; + } + + return block_length; +} + +static inline int +store_block_length (int block_length, int block_length_size, char * buf) +{ + int status = 1; + + switch (block_length_size) + { + case 0: /* do nothing */ + break; + + case 1: + { + if (block_length > UINT8_MAX) + { + status = 0; + break; + } + store_le8 (buf, (uint8_t) (block_length & UINT8_MAX)); + } + break; + + case 2: + { + if (block_length > UINT16_MAX) + { + status = 0; + break; + } + store_le16 (buf, (uint16_t) (block_length & UINT16_MAX)); + } + break; + + case 4: + { + if (block_length > INT32_MAX) + { + status = 0; + break; + } + store_le32 (buf, (uint32_t) (block_length & UINT32_MAX)); + } + break; + + default: /* unsupported cases */ + status = 0; + break; + } + + if (status != 1) + { + PyErr_SetString (LZ4StreamError, "Compressed stream size too large"); + } + + return status; +} + + +/*************** + * LZ4 helpers * + ***************/ +static inline uint32_t +get_compress_bound(uint32_t input_size) +{ + /* result of LZ4_compressBound is null or positive */ + return (uint32_t)LZ4_compressBound(input_size); +} + + +static inline uint32_t +get_input_bound(uint32_t compress_max_size) +{ + uint64_t isize = 0; + uint64_t csize = (uint64_t) compress_max_size; + + /* Reversing the LZ4_COMPRESSBOUND macro gives: + * isize = ((csize - 16) * 255) / 256 + * = (((csize - 16) * 256) - (csize - 16)) / 256 + * = ((csize * 256) - (16 * 256) - (csize - 16)) / 256 + * = ((csize << 8) - (16 << 8) - csize + 16) >> 8 + * = ((csize << 8) - csize + 16 - (16 << 8)) >> 8 + * = ((csize << 8) - csize - 4080) >> 8 + * + * Notes: + * - Using 64-bit long integer for intermediate computation to avoid any + * truncation when shifting left large csize values. + * - Due to the round integer approximation, running the following + * calculation can give a non-null result: + * result = n - _LZ4_inputBound( _LZ4_compressBound( n ) ) + * but in all cases, this difference is between 0 and 1. + * Thus, the valid maximal input size returned by this funtcion is + * incremented by 1 to avoid any buffer overflow in case of decompression + * in a dynamically allocated buffer. + * - For small compressed length (shorter than 16 bytes), make sure a + * non-null size is returned. + */ + if (csize < 16) + { + csize = 17; + } + + if (csize <= get_compress_bound (LZ4_MAX_INPUT_SIZE)) + { + isize = ((csize << 8) - csize - 4080) >> 8; + + if (isize > (uint32_t)LZ4_MAX_INPUT_SIZE) + { + isize = 0; + } + else + { + isize += 1; + } + } + + return (uint32_t)(isize & UINT32_MAX); +} + + +/************************************** + * LZ4 version-compatibility wrappers * + **************************************/ +#if defined (__GNUC__) +/* Runtime detection of the support of new functions in the LZ4 API + * (old functions remain available but are deprecated and will trigger + * compilation warnings. + * + * Declare weak symbols on the required functions provided by recent versions + * of the library. + * This way, these symbols will always be available - NULL if the (old version + * of the) library does not define them. + */ + +/* Function introduced in LZ4 >= 1.9.0 */ +__attribute__ ((weak)) void +LZ4_resetStreamHC_fast (LZ4_streamHC_t* streamHCPtr, int compressionLevel); + +/* Function introduced in LZ4 >= 1.8.2 */ +__attribute__ ((weak)) void +LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +#else +/* Assuming the bundled LZ4 library sources are always used, so meet the + * LZ4 minimal version requirements. + */ +#endif + +static inline void reset_stream (LZ4_stream_t* streamPtr) +{ + if (LZ4_versionNumber () >= LZ4_VERSION_NUMBER_1_9_0) + { + if (LZ4_resetStream_fast) + { + LZ4_resetStream_fast (streamPtr); + } + else + { + PyErr_SetString (PyExc_RuntimeError, + "Inconsistent LZ4 library version/available APIs"); + } + } + else + { + LZ4_resetStream (streamPtr); + } +} + + +static inline void reset_stream_hc (LZ4_streamHC_t* streamHCPtr, int compressionLevel) +{ + if (LZ4_versionNumber () >= LZ4_VERSION_NUMBER_1_9_0) + { + if (LZ4_resetStreamHC_fast) + { + LZ4_resetStreamHC_fast (streamHCPtr, compressionLevel); + } + else + { + PyErr_SetString (PyExc_RuntimeError, + "Inconsistent LZ4 library version/available APIs"); + } + } + else + { + LZ4_resetStreamHC (streamHCPtr, compressionLevel); + } +} + + +/************************* + * Double-buffer helpers * + *************************/ +static int +double_buffer_update_index (stream_context_t * context) +{ +#if DOUBLE_BUFFER_PAGE_COUNT != 2 +#error "DOUBLE_BUFFER_PAGE_COUNT must be 2." +#endif /* DOUBLE_BUFFER_PAGE_COUNT != 2 */ + context->strategy.data.double_buffer.index = (context->strategy.data.double_buffer.index + 1) & 0x1; /* modulo 2 */ + + return 0; +} + +static char * +double_buffer_get_compression_page (const stream_context_t * context) +{ + return context->strategy.data.double_buffer.pages[context->strategy.data.double_buffer.index]; +} + +static void +double_buffer_release_resources (stream_context_t * context) +{ + unsigned int i; + for (i = DOUBLE_BUFFER_INDEX_MIN; + i < (DOUBLE_BUFFER_INDEX_MIN + DOUBLE_BUFFER_PAGE_COUNT); + ++i) + { + context->strategy.data.double_buffer.pages[i] = NULL; + } + + if (context->strategy.data.double_buffer.buf != NULL) + { + PyMem_Free (context->strategy.data.double_buffer.buf); + } + context->strategy.data.double_buffer.buf = NULL; + context->strategy.data.double_buffer.index = DOUBLE_BUFFER_INDEX_INVALID; + context->strategy.data.double_buffer.page_size = 0; +} + +static int +double_buffer_reserve_resources (stream_context_t * context, unsigned int buffer_size) +{ + int status = 0; + unsigned int i; + + context->strategy.data.double_buffer.page_size = buffer_size; + context->strategy.data.double_buffer.buf = PyMem_Malloc (buffer_size * DOUBLE_BUFFER_PAGE_COUNT); + + if (context->strategy.data.double_buffer.buf == NULL) + { + PyErr_Format (PyExc_MemoryError, + "Could not allocate double-buffer"); + status = -1; + goto exit_now; + } + + for (i = DOUBLE_BUFFER_INDEX_MIN; + i < (DOUBLE_BUFFER_INDEX_MIN + DOUBLE_BUFFER_PAGE_COUNT); + ++i) + { + context->strategy.data.double_buffer.pages[i] = context->strategy.data.double_buffer.buf + + (i * buffer_size); + } + + context->strategy.data.double_buffer.index = DOUBLE_BUFFER_INDEX_MIN; + +exit_now: + return status; +} + +static unsigned int +double_buffer_get_work_buffer_size (const stream_context_t * context) +{ + return context->strategy.data.double_buffer.page_size; +} + +static unsigned int +double_buffer_get_dest_buffer_size (const stream_context_t * context) +{ + unsigned int len; + + if (context->config.direction == COMPRESS) + { + len = context->output.len; + } + else + { + len = context->strategy.data.double_buffer.page_size; + } + + return len; +} + + +/**************************************** + * Ring-buffer helpers: Not implemented * + ****************************************/ +static void +ring_buffer_release_resources (stream_context_t * context) +{ + (void) context; /* unused */ + + /* Not implemented (yet) */ + PyErr_Format (PyExc_NotImplementedError, + "Buffer strategy not implemented: ring_buffer"); + + return; +} + +static int +ring_buffer_reserve_resources (stream_context_t * context, unsigned int buffer_size) +{ + (void) context; /* unused */ + (void) buffer_size; /* unused */ + + /* Not implemented (yet) */ + PyErr_Format (PyExc_NotImplementedError, + "Buffer strategy not implemented: ring_buffer"); + return -1; +} + +static unsigned int +ring_buffer_get_dest_buffer_size (const stream_context_t * context) +{ + (void) context; /* unused */ + + /* Not implemented (yet) */ + PyErr_Format (PyExc_NotImplementedError, + "Buffer strategy not implemented: ring_buffer"); + + return 0; +} + +static unsigned int +ring_buffer_get_work_buffer_size (const stream_context_t * context) +{ + (void) context; /* unused */ + + /* Not implemented (yet) */ + PyErr_Format (PyExc_NotImplementedError, + "Buffer strategy not implemented: ring_buffer"); + + return 0; +} + +static char * +ring_buffer_get_buffer_position (const stream_context_t * context) +{ + (void) context; /* unused */ + + /* Not implemented (yet) */ + PyErr_Format (PyExc_NotImplementedError, + "Buffer strategy not implemented: ring_buffer"); + + return NULL; +} + +static int +ring_buffer_update_context (stream_context_t * context) +{ + (void) context; /* unused */ + + /* Not implemented (yet) */ + PyErr_Format (PyExc_NotImplementedError, + "Buffer strategy not implemented: ring_buffer"); + + return -1; +} + + +/********************** + * strategy operators * + **********************/ +static strategy_ops_t strategy_ops[BUFFER_STRATEGY_COUNT] = { + /* [DOUBLE_BUFFER] = */ + { + /* .release_resources */ double_buffer_release_resources, + /* .reserve_resources */ double_buffer_reserve_resources, + /* .get_work_buffer */ double_buffer_get_compression_page, + /* .get_work_buffer_size */ double_buffer_get_work_buffer_size, + /* .get_dest_buffer_size */ double_buffer_get_dest_buffer_size, + /* .update_context_after_process */ double_buffer_update_index, + }, + /* [RING_BUFFER] = */ + { + /* .release_resources */ ring_buffer_release_resources, + /* .reserve_resources */ ring_buffer_reserve_resources, + /* .get_work_buffer */ ring_buffer_get_buffer_position, + /* .get_work_buffer_size */ ring_buffer_get_work_buffer_size, + /* .get_dest_buffer_size */ ring_buffer_get_dest_buffer_size, + /* .update_context_after_process */ ring_buffer_update_context, + }, +}; + + +/******************* + * generic helpers * + *******************/ +static void +destroy_context (stream_context_t * context) +{ + if (context == NULL) + { + return; + } + + /* Release lz4 state */ + Py_BEGIN_ALLOW_THREADS + if (context->lz4_state.context != NULL) + { + if (context->config.direction == COMPRESS) + { + if (context->config.comp == HIGH_COMPRESSION) + { + LZ4_freeStreamHC (context->lz4_state.compress.hc); + } + else + { + LZ4_freeStream (context->lz4_state.compress.fast); + } + } + else /* context->config.direction == DECOMPRESS */ + { + LZ4_freeStreamDecode (context->lz4_state.decompress); + } + } + Py_END_ALLOW_THREADS + context->lz4_state.context = NULL; + + /* Release strategy resources */ + if (context->strategy.ops != NULL) + { + context->strategy.ops->release_resources (context); + } + context->strategy.ops = NULL; + + /* Release output buffer */ + if (context->output.buf != NULL) + { + PyMem_Free (context->output.buf); + } + context->output.buf = NULL; + context->output.len = 0; + + /* Release python memory */ + PyMem_Free (context); +} + +static void +destroy_py_context (PyObject * py_context) +{ + if (py_context == NULL) + { + return; + } + + destroy_context (_PyCapsule_get_context (py_context)); +} + + +/************** + * Python API * + **************/ +static PyObject * +_create_context (PyObject * Py_UNUSED (self), PyObject * args, PyObject * kwds) +{ + stream_context_t * context = NULL; + + const char * direction = ""; + const char * strategy_name = ""; + unsigned int buffer_size; + + buffer_strategy_e strategy = BUFFER_STRATEGY_COUNT; + const char * mode = "default"; + int acceleration = 1; + int compression_level = 9; + int store_comp_size = 4; + int return_bytearray = 0; + Py_buffer dict = { NULL, NULL, }; + + int status = 0; + unsigned int total_size = 0; + uint32_t store_max_size; + + static char * argnames[] = { + "strategy", + "direction", + "buffer_size", + "mode", + "acceleration", + "compression_level", + "return_bytearray", + "store_comp_size", + "dictionary", + NULL + }; + + if (!PyArg_ParseTupleAndKeywords (args, kwds, "ssI|sIIpIz*", argnames, + &strategy_name, &direction, &buffer_size, + &mode, &acceleration, &compression_level, &return_bytearray, + &store_comp_size, &dict)) + { + goto abort_now; + } + + /* Sanity checks on arguments */ + if (dict.len > INT_MAX) + { + PyErr_Format (PyExc_OverflowError, + "Dictionary too large for LZ4 API"); + goto abort_now; + } + + /* Input max length limited to 0x7E000000 (2 113 929 216 bytes < 2GiB). + * https://github.com/lz4/lz4/blob/dev/lib/lz4.h#L161 + * + * So, restrict the block length bitwise to 32 (signed 32 bit integer). */ + if ((store_comp_size != 0) && (store_comp_size != 1) && + (store_comp_size != 2) && (store_comp_size != 4)) + { + PyErr_Format (PyExc_ValueError, + "Invalid store_comp_size, valid values: 0, 1, 2 or 4"); + goto abort_now; + } + + context = (stream_context_t *) PyMem_Malloc (sizeof (stream_context_t)); + if (context == NULL) + { + PyErr_NoMemory (); + goto abort_now; + } + + memset (context, 0x00, sizeof (stream_context_t)); + + /* Set buffer strategy */ + if (!strncmp (strategy_name, "double_buffer", sizeof ("double_buffer"))) + { + strategy = DOUBLE_BUFFER; + } + else if (!strncmp (strategy_name, "ring_buffer", sizeof ("ring_buffer"))) + { + strategy = RING_BUFFER; + } + else + { + PyErr_Format (PyExc_ValueError, + "Invalid strategy argument: %s. Must be one of: double_buffer, ring_buffer", + strategy_name); + goto abort_now; + } + + /* Set direction */ + if (!strncmp (direction, "compress", sizeof ("compress"))) + { + context->config.direction = COMPRESS; + } + else if (!strncmp (direction, "decompress", sizeof ("decompress"))) + { + context->config.direction = DECOMPRESS; + } + else + { + PyErr_Format (PyExc_ValueError, + "Invalid direction argument: %s. Must be one of: compress, decompress", + direction); + goto abort_now; + } + + /* Set compression mode */ + if (!strncmp (mode, "default", sizeof ("default"))) + { + context->config.comp = DEFAULT; + } + else if (!strncmp (mode, "fast", sizeof ("fast"))) + { + context->config.comp = FAST; + } + else if (!strncmp (mode, "high_compression", sizeof ("high_compression"))) + { + context->config.comp = HIGH_COMPRESSION; + } + else + { + PyErr_Format (PyExc_ValueError, + "Invalid mode argument: %s. Must be one of: default, fast, high_compression", + mode); + goto abort_now; + } + + /* Initialize the output buffer + * + * In out-of-band block size case, use a best-effort strategy for scaling + * buffers. + */ + if (store_comp_size == 0) + { + store_max_size = _GET_MAX_UINT32(4); + } + else + { + store_max_size = _GET_MAX_UINT32(store_comp_size); + } + + if (context->config.direction == COMPRESS) + { + context->output.len = get_compress_bound (buffer_size); + total_size = context->output.len + store_comp_size; + + if (context->output.len == 0) + { + PyErr_Format (PyExc_ValueError, + "Invalid buffer_size argument: %u. Cannot define output buffer size. " + "Must be lesser or equal to %u", + buffer_size, LZ4_MAX_INPUT_SIZE); + goto abort_now; + } + + /* Assert the output buffer size and the store_comp_size values are consistent */ + if (context->output.len > store_max_size) + { + /* The maximal/"worst case" compressed data length cannot fit in the + * store_comp_size bytes. */ + PyErr_Format (LZ4StreamError, + "Inconsistent buffer_size/store_comp_size values. " + "Maximal compressed length (%u) cannot fit in a %u byte-long integer", + buffer_size, store_comp_size); + goto abort_now; + } + } + else /* context->config.direction == DECOMPRESS */ + { + if (store_max_size > LZ4_MAX_INPUT_SIZE) + { + store_max_size = LZ4_MAX_INPUT_SIZE; + } + + context->output.len = buffer_size; + total_size = context->output.len; + + /* Here we cannot assert the maximal theoretical decompressed chunk length + * will fit in one page of the double_buffer, i.e.: + * assert( !(double_buffer.page_size < _LZ4_inputBound(store_max_size)) ) + * + * Doing such a check would require aligning the page_size on the maximal + * value of the store_comp_size prefix, i.e.: + * page_size = 256B if store_comp_size == 1 + * page_size = 64KB if store_comp_size == 2 + * page_size = 4GB if store_comp_size == 4 + * + * This constraint is too strict, so is not implemented. + * + * On the other hand, the compression logic tells the page size cannot be + * larger than the maximal value fitting in store_comp_size bytes. + * So here, the check could be checking the page_size is smaller or equal + * to the maximal decompressed chunk length, i.e.: + * assert( !(double_buffer.page_size > _LZ4_inputBound(store_max_size)) ) + * + * But this check is not really relevant and could bring other limitations. + * + * So, on the decompression case, no check regarding the page_size and the + * store_comp_size values can reliably be done during the LZ4 context + * initialization, they will be deferred in the decompression process. + */ + } + + /* Set all remaining settings in the context */ + context->config.store_comp_size = store_comp_size; + context->config.acceleration = acceleration; + context->config.compression_level = compression_level; + context->config.return_bytearray = !!return_bytearray; + + /* Set internal resources related to the buffer strategy */ + context->strategy.ops = &strategy_ops[strategy]; + + status = context->strategy.ops->reserve_resources (context, buffer_size); + if (status != 0) + { + /* Python exception already set in the strategy's resource creation helper */ + goto abort_now; + } + + /* Set output buffer */ + context->output.buf = PyMem_Malloc (total_size * sizeof (* (context->output.buf))); + if (context->output.buf == NULL) + { + PyErr_Format (PyExc_MemoryError, + "Could not allocate output buffer"); + goto abort_now; + } + + /* Initialize lz4 state */ + if (context->config.direction == COMPRESS) + { + if (context->config.comp == HIGH_COMPRESSION) + { + context->lz4_state.compress.hc = LZ4_createStreamHC (); + if (context->lz4_state.compress.hc == NULL) + { + PyErr_Format (PyExc_MemoryError, + "Could not create LZ4 state"); + goto abort_now; + } + + reset_stream_hc (context->lz4_state.compress.hc, context->config.compression_level); + + if (dict.len > 0) + { + LZ4_loadDictHC (context->lz4_state.compress.hc, dict.buf, dict.len); + } + } + else + { + context->lz4_state.compress.fast = LZ4_createStream (); + if (context->lz4_state.compress.fast == NULL) + { + PyErr_Format (PyExc_MemoryError, + "Could not create LZ4 state"); + goto abort_now; + } + + reset_stream (context->lz4_state.compress.fast); + + if (dict.len > 0) + { + LZ4_loadDict (context->lz4_state.compress.fast, dict.buf, dict.len); + } + } + } + else /* context->config.direction == DECOMPRESS */ + { + context->lz4_state.decompress = LZ4_createStreamDecode (); + if (context->lz4_state.decompress == NULL) + { + PyErr_Format (PyExc_MemoryError, + "Could not create LZ4 state"); + goto abort_now; + } + + if (!LZ4_setStreamDecode (context->lz4_state.decompress, dict.buf, dict.len)) + { + PyErr_Format (PyExc_RuntimeError, + "Could not initialize LZ4 state"); + LZ4_freeStreamDecode (context->lz4_state.decompress); + goto abort_now; + } + } + + PyBuffer_Release (&dict); + + return PyCapsule_New (context, stream_context_capsule_name, destroy_py_context); + +abort_now: + if (dict.buf != NULL) + { + PyBuffer_Release (&dict); + } + destroy_context (context); + + return NULL; +} + +static PyObject * +_compress_bound (PyObject * Py_UNUSED (self), PyObject * args) +{ + PyObject * py_dest = NULL; + uint32_t input_size; + + + /* Positional arguments: input_size + * Keyword arguments : none + */ + if (!PyArg_ParseTuple (args, "OI", &input_size)) + { + goto exit_now; + } + + py_dest = PyLong_FromUnsignedLong (get_compress_bound (input_size)); + + if (py_dest == NULL) + { + PyErr_NoMemory (); + } + +exit_now: + return py_dest; +} + +static PyObject * +_input_bound (PyObject * Py_UNUSED (self), PyObject * args) +{ + PyObject * py_dest = NULL; + uint32_t compress_max_size; + + /* Positional arguments: compress_max_size + * Keyword arguments : none + */ + if (!PyArg_ParseTuple (args, "I", &compress_max_size)) + { + goto exit_now; + } + + py_dest = PyLong_FromUnsignedLong (get_input_bound (compress_max_size)); + + if (py_dest == NULL) + { + PyErr_NoMemory (); + } + +exit_now: + return py_dest; +} + +static inline int +_compress_generic (stream_context_t * lz4_ctxt, char * source, int source_size, + char * dest, int dest_size) +{ + int comp_len; + + if (lz4_ctxt->config.comp == HIGH_COMPRESSION) + { + LZ4_streamHC_t * lz4_state = lz4_ctxt->lz4_state.compress.hc; + + comp_len = LZ4_compress_HC_continue (lz4_state, source, dest, source_size, dest_size); + } + else + { + LZ4_stream_t * lz4_state = lz4_ctxt->lz4_state.compress.fast; + int acceleration = (lz4_ctxt->config.comp != FAST) + ? 1 /* defaults */ + : lz4_ctxt->config.acceleration; + + comp_len = LZ4_compress_fast_continue (lz4_state, source, dest, source_size, dest_size, + acceleration); + } + + return comp_len; +} + +#ifdef inline +#undef inline +#endif + +static PyObject * +_compress (PyObject * Py_UNUSED (self), PyObject * args) +{ + stream_context_t * context = NULL; + PyObject * py_context = NULL; + PyObject * py_dest = NULL; + int output_size; + Py_buffer source = { NULL, NULL, }; + + /* Positional arguments: capsule_context, source + * Keyword arguments : none + */ + if (!PyArg_ParseTuple (args, "Oy*", &py_context, &source)) + { + goto exit_now; + } + + context = _PyCapsule_get_context (py_context); + if ((context == NULL) || (context->lz4_state.context == NULL)) + { + PyErr_SetString (PyExc_ValueError, "No valid LZ4 stream context supplied"); + goto exit_now; + } + + if (source.len > context->strategy.ops->get_work_buffer_size (context)) + { + PyErr_SetString (PyExc_OverflowError, + "Input too large for LZ4 API"); + goto exit_now; + } + + memcpy (context->strategy.ops->get_work_buffer (context), source.buf, source.len); + + Py_BEGIN_ALLOW_THREADS + + output_size = _compress_generic (context, + context->strategy.ops->get_work_buffer (context), + source.len, + context->output.buf + context->config.store_comp_size, + context->output.len); + + Py_END_ALLOW_THREADS + + if (output_size <= 0) + { + /* No error code set in output_size! */ + PyErr_SetString (LZ4StreamError, + "Compression failed"); + goto exit_now; + } + + if (!store_block_length (output_size, context->config.store_comp_size, context->output.buf)) + { + PyErr_SetString (LZ4StreamError, + "Compressed stream size too large"); + goto exit_now; + } + + output_size += context->config.store_comp_size; + + if (context->config.return_bytearray) + { + py_dest = PyByteArray_FromStringAndSize (context->output.buf, (Py_ssize_t) output_size); + } + else + { + py_dest = PyBytes_FromStringAndSize (context->output.buf, (Py_ssize_t) output_size); + } + + if (py_dest == NULL) + { + PyErr_NoMemory (); + goto exit_now; + } + + if (context->strategy.ops->update_context_after_process (context) != 0) + { + PyErr_Format (PyExc_RuntimeError, "Internal error"); + goto exit_now; + } + +exit_now: + if (source.buf != NULL) + { + PyBuffer_Release (&source); + } + + return py_dest; +} + +static PyObject * +_get_block (PyObject * Py_UNUSED (self), PyObject * args) +{ + stream_context_t * context = NULL; + PyObject * py_context = NULL; + PyObject * py_dest = NULL; + Py_buffer source = { NULL, NULL, }; + buffer_t block = { NULL, 0, }; + + /* Positional arguments: capsule_context, source + * Keyword arguments : none + */ + + if (!PyArg_ParseTuple (args, "Oy*", &py_context, &source)) + { + goto exit_now; + } + + context = _PyCapsule_get_context (py_context); + if ((context == NULL) || (context->lz4_state.context == NULL)) + { + PyErr_SetString (PyExc_ValueError, "No valid LZ4 stream context supplied"); + goto exit_now; + } + + if (source.len > INT_MAX) + { + PyErr_Format (PyExc_OverflowError, + "Input too large for LZ4 API"); + goto exit_now; + } + + if (context->config.store_comp_size == 0) + { + PyErr_Format (LZ4StreamError, + "LZ4 context is configured for storing block size out-of-band"); + goto exit_now; + } + + if (source.len < context->config.store_comp_size) + { + PyErr_Format (LZ4StreamError, + "Invalid source, too small for holding any block"); + goto exit_now; + } + + block.buf = (char *) source.buf + context->config.store_comp_size; + block.len = load_block_length (context->config.store_comp_size, source.buf); + + if ((source.len - context->config.store_comp_size) < block.len) + { + PyErr_Format (LZ4StreamError, + "Requested input size (%d) larger than source size (%ld)", + block.len, (source.len - context->config.store_comp_size)); + goto exit_now; + } + + if (context->config.return_bytearray) + { + py_dest = PyByteArray_FromStringAndSize (block.buf, (Py_ssize_t) block.len); + } + else + { + py_dest = PyBytes_FromStringAndSize (block.buf, (Py_ssize_t) block.len); + } + + if (py_dest == NULL) + { + PyErr_NoMemory (); + } + +exit_now: + if (source.buf != NULL) + { + PyBuffer_Release (&source); + } + + return py_dest; +} + +static PyObject * +_decompress (PyObject * Py_UNUSED (self), PyObject * args) +{ + stream_context_t * context = NULL; + PyObject * py_context = NULL; + PyObject * py_dest = NULL; + int output_size = 0; + uint32_t source_size_max = 0; + Py_buffer source = { NULL, NULL, }; + + /* Positional arguments: capsule_context, source + * Keyword arguments : none + */ + if (!PyArg_ParseTuple (args, "Oy*", &py_context, &source)) + { + goto exit_now; + } + + context = _PyCapsule_get_context (py_context); + if ((context == NULL) || (context->lz4_state.context == NULL)) + { + PyErr_SetString (PyExc_ValueError, "No valid LZ4 stream context supplied"); + goto exit_now; + } + + /* In out-of-band block size case, use a best-effort strategy for scaling + * buffers. + */ + if (context->config.store_comp_size == 0) + { + source_size_max = _GET_MAX_UINT32(4); + } + else + { + source_size_max = _GET_MAX_UINT32(context->config.store_comp_size); + } + + if (source.len > source_size_max) + { + PyErr_Format (PyExc_OverflowError, + "Source length (%ld) too large for LZ4 store_comp_size (%d) value", + source.len, context->config.store_comp_size); + goto exit_now; + } + + if ((get_input_bound (source.len) == 0) || + (get_input_bound (source.len) > context->strategy.ops->get_dest_buffer_size (context))) + { + PyErr_Format (LZ4StreamError, + "Maximal decompressed data (%d) cannot fit in LZ4 internal buffer (%u)", + get_input_bound (source.len), + context->strategy.ops->get_dest_buffer_size (context)); + goto exit_now; + } + + Py_BEGIN_ALLOW_THREADS + + output_size = LZ4_decompress_safe_continue (context->lz4_state.decompress, + (const char *) source.buf, + context->strategy.ops->get_work_buffer (context), + source.len, + context->strategy.ops->get_dest_buffer_size (context)); + + Py_END_ALLOW_THREADS + + if (output_size < 0) + { + /* In case of LZ4 decompression error, output_size holds the error code */ + PyErr_Format (LZ4StreamError, + "Decompression failed. error: %d", + -output_size); + goto exit_now; + } + + if ((unsigned int) output_size > context->output.len) + { + output_size = -1; + PyErr_Format (PyExc_OverflowError, + "Decompressed stream too large for LZ4 API"); + goto exit_now; + } + + memcpy (context->output.buf, + context->strategy.ops->get_work_buffer (context), + output_size); + + if ( context->strategy.ops->update_context_after_process (context) != 0) + { + PyErr_Format (PyExc_RuntimeError, "Internal error"); + goto exit_now; + } + + if (context->config.return_bytearray) + { + py_dest = PyByteArray_FromStringAndSize (context->output.buf, (Py_ssize_t) output_size); + } + else + { + py_dest = PyBytes_FromStringAndSize (context->output.buf, (Py_ssize_t) output_size); + } + + if (py_dest == NULL) + { + PyErr_NoMemory (); + } + +exit_now: + if (source.buf != NULL) + { + PyBuffer_Release (&source); + } + + return py_dest; +} + + +PyDoc_STRVAR (_compress_bound__doc, + "_compress_bound(input_size)\n" \ + "\n" \ + "Provides the maximum size that LZ4 compression may output in a \"worst case\"\n" \ + "scenario (input data not compressible).\n" \ + "This function is primarily useful for memory allocation purposes (destination\n" \ + "buffer size).\n" \ + "\n" \ + "Args:\n" \ + " input_size (int): Input data size.\n" \ + "\n" \ + "Returns:\n" \ + " int: Maximal (worst case) size of the compressed data;\n" \ + " or 0 if the input size is greater than 2 113 929 216.\n"); + +PyDoc_STRVAR (_input_bound__doc, + "_input_bound(compress_max_size)\n" \ + "\n" \ + "Provides the maximum size that LZ4 decompression may output in a \"worst case\"\n" \ + "scenario (compressed data with null compression ratio).\n" \ + "This function is primarily useful for memory allocation purposes (destination\n" \ + "buffer size).\n" \ + "\n" \ + "Args:\n" \ + " compress_max_size (int): Compressed data size.\n" \ + "\n" \ + "Returns:\n" \ + " int: Maximal (worst case) size of the input data;\n" \ + " or 0 if the compressed maximal size is lower than the LZ4 compression\n" \ + " minimal overhead, or if the computed input size is greater than\n" \ + " 2 113 929 216.\n"); + +PyDoc_STRVAR (_compress__doc, + "_compress(context, source)\n" \ + "\n" \ + "Compress source, using the given LZ4 stream context, returning the compressed\n" \ + "data as a bytearray or as a bytes object.\n" \ + "Raises an exception if any error occurs.\n" \ + "\n" \ + "Args:\n" \ + " context (ctx): LZ4 stream context.\n" \ + " source (str, bytes or buffer-compatible object): Data to compress.\n" \ + "\n" \ + "Returns:\n" \ + " bytes or bytearray: Compressed data.\n" \ + "\n" \ + "Raises:\n" \ + " OverflowError: raised if the source is too large for being compressed in\n" \ + " the given context.\n" \ + " RuntimeError: raised if some internal resources cannot be updated.\n" \ + " LZ4StreamError: raised if the call to the LZ4 library fails.\n"); + +PyDoc_STRVAR (_get_block__doc, + "_get_block(context, source)\n" \ + "\n" \ + "Return the first LZ4 compressed block from ``'source'``. \n" \ + "\n" \ + "Args:\n" \ + " context (ctx): LZ4 stream context.\n" \ + " source (str, bytes or buffer-compatible object): LZ4 compressed stream.\n" \ + "\n" \ + "Returns:\n" \ + " bytes or bytearray: LZ4 compressed data block.\n" \ + "\n" \ + "Raises:\n" \ + " MemoryError: raised if the output buffer cannot be allocated.\n" \ + " OverflowError: raised if the source is too large for being handled by \n" \ + " the given context.\n"); + +PyDoc_STRVAR (_decompress__doc, + "_decompress(context, source)\n" \ + "\n" \ + "Decompress source, using the given LZ4 stream context, returning the\n" \ + "uncompressed data as a bytearray or as a bytes object.\n" \ + "Raises an exception if any error occurs.\n" \ + "\n" \ + "Args:\n" \ + " context (obj): LZ4 stream context.\n" \ + " source (str, bytes or buffer-compatible object): Data to uncompress.\n" \ + "\n" \ + "Returns:\n" \ + " bytes or bytearray: Uncompressed data.\n" \ + "\n" \ + "Raises:\n" \ + " ValueError: raised if the source is inconsistent with a finite LZ4\n" \ + " stream block chain.\n" \ + " MemoryError: raised if the work output buffer cannot be allocated.\n" \ + " OverflowError: raised if the source is too large for being decompressed\n" \ + " in the given context.\n" \ + " RuntimeError: raised if some internal resources cannot be updated.\n" \ + " LZ4StreamError: raised if the call to the LZ4 library fails.\n"); + +PyDoc_STRVAR (_create_context__doc, + "_create_context(strategy, direction, buffer_size,\n" \ + " mode='default', acceleration=1, compression_level=9,\n" \ + " return_bytearray=0, store_comp_size=4, dict=None)\n" \ + "\n" \ + "Instantiates and initializes a LZ4 stream context.\n" \ + "Raises an exception if any error occurs.\n" \ + "\n" \ + "Args:\n" \ + " strategy (str): Can be ``'double_buffer'``.\n" \ + " Only ``'double_buffer'`` is currently implemented.\n" \ + " direction (str): Can be ``'compress'`` or ``'decompress'``.\n" \ + " buffer_size (int): Base size of the buffer(s) used internally for stream\n" \ + " compression/decompression.\n" \ + " For the ``'double_buffer'`` strategy, this is the size of each buffer\n" \ + " of the double-buffer.\n" \ + "\n" \ + "Keyword Args:\n" \ + " mode (str): If ``'default'`` or unspecified use the default LZ4\n" \ + " compression mode. Set to ``'fast'`` to use the fast compression\n" \ + " LZ4 mode at the expense of compression. Set to\n" \ + " ``'high_compression'`` to use the LZ4 high-compression mode at\n" \ + " the expense of speed.\n" \ + " acceleration (int): When mode is set to ``'fast'`` this argument\n" \ + " specifies the acceleration. The larger the acceleration, the\n" \ + " faster the but the lower the compression. The default\n" \ + " compression corresponds to a value of ``1``.\n" \ + " Only relevant if ``'direction'`` is ``'compress'``.\n" \ + " compression_level (int): When mode is set to ``high_compression`` this\n" \ + " argument specifies the compression. Valid values are between\n" \ + " ``1`` and ``12``. Values between ``4-9`` are recommended, and\n" \ + " ``9`` is the default.\n" \ + " Only relevant if ``'direction'`` is ``'compress'`` and ``'mode'`` .\n" \ + " is ``'high_compression'``.\n" \ + " return_bytearray (bool): If ``False`` (the default) then the function\n" \ + " will return a bytes object. If ``True``, then the function will\n" \ + " return a bytearray object.\n" \ + " store_comp_size (int): Specify the size in bytes of the following\n" \ + " compressed block. Can be: ``1``, ``2`` or ``4`` (default: ``4``).\n" \ + " dict (str, bytes or buffer-compatible object): If specified, perform\n" \ + " compression using this initial dictionary.\n" \ + "\n" \ + "Returns:\n" \ + " lz4_ctx: A LZ4 stream context.\n" \ + "\n" \ + "Raises:\n" \ + " OverflowError: raised if the ``dict`` parameter is too large for the\n" \ + " LZ4 context.\n" \ + " ValueError: raised if some parameters are invalid.\n" \ + " MemoryError: raised if some internal resources cannot be allocated.\n" \ + " RuntimeError: raised if some internal resources cannot be initialized.\n"); + +PyDoc_STRVAR (lz4stream__doc, + "A Python wrapper for the LZ4 stream protocol" + ); + +static PyMethodDef module_methods[] = { + { + "_create_context", (PyCFunction) _create_context, + METH_VARARGS | METH_KEYWORDS, + _create_context__doc + }, + + { + "_compress", + (PyCFunction) _compress, + METH_VARARGS, + _compress__doc + }, + { + "_decompress", + (PyCFunction) _decompress, + METH_VARARGS, + _decompress__doc + }, + { + "_get_block", + (PyCFunction) _get_block, + METH_VARARGS, + _get_block__doc + }, + { + "_compress_bound", + (PyCFunction) _compress_bound, + METH_VARARGS, + _compress_bound__doc + }, + { + "_input_bound", + (PyCFunction) _input_bound, + METH_VARARGS, + _input_bound__doc + }, + { + /* Sentinel */ + NULL, + NULL, + 0, + NULL + } +}; + +static PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + /* m_name */ "_stream", + /* m_doc */ lz4stream__doc, + /* m_size */ -1, + /* m_methods */ module_methods, +}; + + +PyMODINIT_FUNC +PyInit__stream(void) +{ + PyObject * module = PyModule_Create (&moduledef); + + if (module == NULL) + { + return NULL; + } + + PyModule_AddIntConstant (module, "HC_LEVEL_MIN", LZ4HC_CLEVEL_MIN); + PyModule_AddIntConstant (module, "HC_LEVEL_DEFAULT", LZ4HC_CLEVEL_DEFAULT); + PyModule_AddIntConstant (module, "HC_LEVEL_OPT_MIN", LZ4HC_CLEVEL_OPT_MIN); + PyModule_AddIntConstant (module, "HC_LEVEL_MAX", LZ4HC_CLEVEL_MAX); + PyModule_AddIntConstant (module, "LZ4_MAX_INPUT_SIZE", LZ4_MAX_INPUT_SIZE); + + LZ4StreamError = PyErr_NewExceptionWithDoc ("_stream.LZ4StreamError", + "Call to LZ4 library failed.", + NULL, NULL); + if (LZ4StreamError == NULL) + { + return NULL; + } + Py_INCREF (LZ4StreamError); + PyModule_AddObject (module, "LZ4StreamError", LZ4StreamError); + + #ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); + #endif + + return module; +} diff --git a/lz4libs/lz4.c b/lz4libs/lz4.c index 213b0851..654bfdf3 100644 --- a/lz4libs/lz4.c +++ b/lz4libs/lz4.c @@ -1,6 +1,6 @@ /* LZ4 - Fast LZ compression algorithm - Copyright (C) 2011-2017, Yann Collet. + Copyright (C) 2011-2020, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) @@ -32,7 +32,6 @@ - LZ4 source repository : https://github.com/lz4/lz4 */ - /*-************************************ * Tuning parameters **************************************/ @@ -46,10 +45,16 @@ #endif /* - * ACCELERATION_DEFAULT : + * LZ4_ACCELERATION_DEFAULT : * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 */ -#define ACCELERATION_DEFAULT 1 +#define LZ4_ACCELERATION_DEFAULT 1 +/* + * LZ4_ACCELERATION_MAX : + * Any "acceleration" value higher than this threshold + * get treated as LZ4_ACCELERATION_MAX instead (fix #876) + */ +#define LZ4_ACCELERATION_MAX 65537 /*-************************************ @@ -69,9 +74,11 @@ * Prefer these methods in priority order (0 > 1 > 2) */ #ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ -# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# if defined(__GNUC__) && \ + ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ + || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) # define LZ4_FORCE_MEMORY_ACCESS 2 -# elif defined(__INTEL_COMPILER) || defined(__GNUC__) +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) # define LZ4_FORCE_MEMORY_ACCESS 1 # endif #endif @@ -80,7 +87,8 @@ * LZ4_FORCE_SW_BITCOUNT * Define this parameter if your target system or compiler does not support hardware bit count */ -#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for Windows CE does not support Hardware bit count */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */ +# undef LZ4_FORCE_SW_BITCOUNT /* avoid double def */ # define LZ4_FORCE_SW_BITCOUNT #endif @@ -89,6 +97,23 @@ /*-************************************ * Dependency **************************************/ +/* + * LZ4_SRC_INCLUDED: + * Amalgamation flag, whether lz4.c is included + */ +#ifndef LZ4_SRC_INCLUDED +# define LZ4_SRC_INCLUDED 1 +#endif + +#ifndef LZ4_STATIC_LINKING_ONLY +#define LZ4_STATIC_LINKING_ONLY +#endif + +#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS +#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ +#endif + +#define LZ4_STATIC_LINKING_ONLY /* LZ4_DISTANCE_MAX */ #include "lz4.h" /* see also "memory routines" below */ @@ -96,10 +121,10 @@ /*-************************************ * Compiler Options **************************************/ -#ifdef _MSC_VER /* Visual Studio */ -# include -# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ -# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) /* Visual Studio 2005+ */ +# include /* only present in VS2005+ */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 6237) /* disable: C6237: conditional expression is always 0 */ #endif /* _MSC_VER */ #ifndef LZ4_FORCE_INLINE @@ -118,8 +143,8 @@ # endif /* _MSC_VER */ #endif /* LZ4_FORCE_INLINE */ -/* LZ4_FORCE_O2_GCC_PPC64LE and LZ4_FORCE_O2_INLINE_GCC_PPC64LE - * Gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy, +/* LZ4_FORCE_O2 and LZ4_FORCE_INLINE + * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8, * together with a simple 8-byte copy loop as a fall-back path. * However, this optimization hurts the decompression speed by >30%, * because the execution does not go to the optimized loop @@ -127,17 +152,17 @@ * before going to the fall-back path become useless overhead. * This optimization happens only with the -O3 flag, and -O2 generates * a simple 8-byte copy loop. - * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8 * functions are annotated with __attribute__((optimize("O2"))), - * and also LZ4_wildCopy is forcibly inlined, so that the O2 attribute - * of LZ4_wildCopy does not affect the compression speed. + * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy8 does not affect the compression speed. */ -#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) -# define LZ4_FORCE_O2_GCC_PPC64LE __attribute__((optimize("O2"))) -# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE __attribute__((optimize("O2"))) LZ4_FORCE_INLINE +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__) +# define LZ4_FORCE_O2 __attribute__((optimize("O2"))) +# undef LZ4_FORCE_INLINE +# define LZ4_FORCE_INLINE static __inline __attribute__((optimize("O2"),always_inline)) #else -# define LZ4_FORCE_O2_GCC_PPC64LE -# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE static +# define LZ4_FORCE_O2 #endif #if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) @@ -146,23 +171,132 @@ # define expect(expr,value) (expr) #endif +#ifndef likely #define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely #define unlikely(expr) expect((expr) != 0, 0) +#endif + +/* Should the alignment test prove unreliable, for some reason, + * it can be disabled by setting LZ4_ALIGN_TEST to 0 */ +#ifndef LZ4_ALIGN_TEST /* can be externally provided */ +# define LZ4_ALIGN_TEST 1 +#endif /*-************************************ * Memory routines **************************************/ -#include /* malloc, calloc, free */ -#define ALLOCATOR(n,s) calloc(n,s) -#define FREEMEM free -#include /* memset, memcpy */ -#define MEM_INIT memset + +/*! LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION : + * Disable relatively high-level LZ4/HC functions that use dynamic memory + * allocation functions (malloc(), calloc(), free()). + * + * Note that this is a compile-time switch. And since it disables + * public/stable LZ4 v1 API functions, we don't recommend using this + * symbol to generate a library for distribution. + * + * The following public functions are removed when this symbol is defined. + * - lz4 : LZ4_createStream, LZ4_freeStream, + * LZ4_createStreamDecode, LZ4_freeStreamDecode, LZ4_create (deprecated) + * - lz4hc : LZ4_createStreamHC, LZ4_freeStreamHC, + * LZ4_createHC (deprecated), LZ4_freeHC (deprecated) + * - lz4frame, lz4file : All LZ4F_* functions + */ +#if defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +# define ALLOC(s) lz4_error_memory_allocation_is_disabled +# define ALLOC_AND_ZERO(s) lz4_error_memory_allocation_is_disabled +# define FREEMEM(p) lz4_error_memory_allocation_is_disabled +#elif defined(LZ4_USER_MEMORY_FUNCTIONS) +/* memory management functions can be customized by user project. + * Below functions must exist somewhere in the Project + * and be available at link time */ +void* LZ4_malloc(size_t s); +void* LZ4_calloc(size_t n, size_t s); +void LZ4_free(void* p); +# define ALLOC(s) LZ4_malloc(s) +# define ALLOC_AND_ZERO(s) LZ4_calloc(1,s) +# define FREEMEM(p) LZ4_free(p) +#else +# include /* malloc, calloc, free */ +# define ALLOC(s) malloc(s) +# define ALLOC_AND_ZERO(s) calloc(1,s) +# define FREEMEM(p) free(p) +#endif + +#if ! LZ4_FREESTANDING +# include /* memset, memcpy */ +#endif +#if !defined(LZ4_memset) +# define LZ4_memset(p,v,s) memset((p),(v),(s)) +#endif +#define MEM_INIT(p,v,s) LZ4_memset((p),(v),(s)) + + +/*-************************************ +* Common Constants +**************************************/ +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ +#define FASTLOOP_SAFE_DISTANCE 64 +static const int LZ4_minLength = (MFLIMIT+1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define LZ4_DISTANCE_ABSOLUTE_MAX 65535 +#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */ +# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" +#endif + +#define ML_BITS 4 +#define ML_MASK ((1U<=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include + static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + +static int LZ4_isAligned(const void* ptr, size_t alignment) +{ + return ((size_t)ptr & (alignment -1)) == 0; +} /*-************************************ -* Basic Types +* Types **************************************/ +#include #if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) # include typedef uint8_t BYTE; @@ -172,6 +306,9 @@ typedef uint64_t U64; typedef uintptr_t uptrval; #else +# if UINT_MAX != 4294967295UL +# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4" +# endif typedef unsigned char BYTE; typedef unsigned short U16; typedef unsigned int U32; @@ -186,9 +323,41 @@ typedef size_t reg_t; /* 32-bits in x32 mode */ #endif +typedef enum { + notLimited = 0, + limitedOutput = 1, + fillOutput = 2 +} limitedOutput_directive; + + /*-************************************ * Reading and writing into memory **************************************/ + +/** + * LZ4 relies on memcpy with a constant size being inlined. In freestanding + * environments, the compiler can't assume the implementation of memcpy() is + * standard compliant, so it can't apply its specialized memcpy() inlining + * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze + * memcpy() as if it were standard compliant, so it can inline it in freestanding + * environments. This is needed when decompressing the Linux Kernel, for example. + */ +#if !defined(LZ4_memcpy) +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size) +# else +# define LZ4_memcpy(dst, src, size) memcpy(dst, src, size) +# endif +#endif + +#if !defined(LZ4_memmove) +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4_memmove __builtin_memmove +# else +# define LZ4_memmove memmove +# endif +#endif + static unsigned LZ4_isLittleEndian(void) { const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ @@ -210,40 +379,40 @@ static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } /* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ /* currently only defined for gcc and icc */ -typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) unalign; +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) LZ4_unalign; -static U16 LZ4_read16(const void* ptr) { return ((const unalign*)ptr)->u16; } -static U32 LZ4_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } -static reg_t LZ4_read_ARCH(const void* ptr) { return ((const unalign*)ptr)->uArch; } +static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const LZ4_unalign*)ptr)->uArch; } -static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; } -static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; } +static void LZ4_write16(void* memPtr, U16 value) { ((LZ4_unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((LZ4_unalign*)memPtr)->u32 = value; } -#else /* safe and portable access through memcpy() */ +#else /* safe and portable access using memcpy() */ static U16 LZ4_read16(const void* memPtr) { - U16 val; memcpy(&val, memPtr, sizeof(val)); return val; + U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; } static U32 LZ4_read32(const void* memPtr) { - U32 val; memcpy(&val, memPtr, sizeof(val)); return val; + U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; } static reg_t LZ4_read_ARCH(const void* memPtr) { - reg_t val; memcpy(&val, memPtr, sizeof(val)); return val; + reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; } static void LZ4_write16(void* memPtr, U16 value) { - memcpy(memPtr, &value, sizeof(value)); + LZ4_memcpy(memPtr, &value, sizeof(value)); } static void LZ4_write32(void* memPtr, U32 value) { - memcpy(memPtr, &value, sizeof(value)); + LZ4_memcpy(memPtr, &value, sizeof(value)); } #endif /* LZ4_FORCE_MEMORY_ACCESS */ @@ -270,70 +439,116 @@ static void LZ4_writeLE16(void* memPtr, U16 value) } } -static void LZ4_copy8(void* dst, const void* src) -{ - memcpy(dst,src,8); -} - /* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ -LZ4_FORCE_O2_INLINE_GCC_PPC64LE -void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) +LZ4_FORCE_INLINE +void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd) { BYTE* d = (BYTE*)dstPtr; const BYTE* s = (const BYTE*)srcPtr; BYTE* const e = (BYTE*)dstEnd; - do { LZ4_copy8(d,s); d+=8; s+=8; } while (d= 16. */ +LZ4_FORCE_INLINE void +LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; -/*-************************************ -* Error detection -**************************************/ -#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=1) -# include -#else -# ifndef assert -# define assert(condition) ((void)0) -# endif -#endif + do { LZ4_memcpy(d,s,16); LZ4_memcpy(d+16,s+16,16); d+=32; s+=32; } while (d= dstPtr + MINMATCH + * - there is at least 8 bytes available to write after dstEnd */ +LZ4_FORCE_INLINE void +LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) +{ + BYTE v[8]; -#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ + assert(dstEnd >= dstPtr + MINMATCH); -#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) -# include -static int g_debuglog_enable = 1; -# define DEBUGLOG(l, ...) { \ - if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ - fprintf(stderr, __FILE__ ": "); \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, " \n"); \ - } } -#else -# define DEBUGLOG(l, ...) {} /* disabled */ + switch(offset) { + case 1: + MEM_INIT(v, *srcPtr, 8); + break; + case 2: + LZ4_memcpy(v, srcPtr, 2); + LZ4_memcpy(&v[2], srcPtr, 2); +#if defined(_MSC_VER) && (_MSC_VER <= 1933) /* MSVC 2022 ver 17.3 or earlier */ +# pragma warning(push) +# pragma warning(disable : 6385) /* warning C6385: Reading invalid data from 'v'. */ +#endif + LZ4_memcpy(&v[4], v, 4); +#if defined(_MSC_VER) && (_MSC_VER <= 1933) /* MSVC 2022 ver 17.3 or earlier */ +# pragma warning(pop) +#endif + break; + case 4: + LZ4_memcpy(v, srcPtr, 4); + LZ4_memcpy(&v[4], srcPtr, 4); + break; + default: + LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset); + return; + } + + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + while (dstPtr < dstEnd) { + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + } +} #endif @@ -342,75 +557,103 @@ static int g_debuglog_enable = 1; **************************************/ static unsigned LZ4_NbCommonBytes (reg_t val) { + assert(val != 0); if (LZ4_isLittleEndian()) { - if (sizeof(val)==8) { -# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + if (sizeof(val) == 8) { +# if defined(_MSC_VER) && (_MSC_VER >= 1800) && (defined(_M_AMD64) && !defined(_M_ARM64EC)) && !defined(LZ4_FORCE_SW_BITCOUNT) +/*-************************************************************************************************* +* ARM64EC is a Microsoft-designed ARM64 ABI compatible with AMD64 applications on ARM64 Windows 11. +* The ARM64EC ABI does not support AVX/AVX2/AVX512 instructions, nor their relevant intrinsics +* including _tzcnt_u64. Therefore, we need to neuter the _tzcnt_u64 code path for ARM64EC. +****************************************************************************************************/ +# if defined(__clang__) && (__clang_major__ < 10) + /* Avoid undefined clang-cl intrinsics issue. + * See https://github.com/lz4/lz4/pull/1017 for details. */ + return (unsigned)__builtin_ia32_tzcnt_u64(val) >> 3; +# else + /* x64 CPUS without BMI support interpret `TZCNT` as `REP BSF` */ + return (unsigned)_tzcnt_u64(val) >> 3; +# endif +# elif defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) unsigned long r = 0; - _BitScanForward64( &r, (U64)val ); - return (int)(r>>3); -# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) - return (__builtin_ctzll((U64)val) >> 3); + _BitScanForward64(&r, (U64)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctzll((U64)val) >> 3; # else - static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, - 0, 3, 1, 3, 1, 4, 2, 7, - 0, 2, 3, 6, 1, 5, 3, 5, - 1, 3, 4, 4, 2, 5, 6, 7, - 7, 0, 1, 2, 3, 3, 4, 6, - 2, 6, 5, 5, 3, 4, 5, 6, - 7, 1, 2, 4, 6, 4, 4, 5, - 7, 2, 6, 5, 7, 6, 7, 7 }; - return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; + const U64 m = 0x0101010101010101ULL; + val ^= val - 1; + return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56); # endif } else /* 32 bits */ { -# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) +# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT) unsigned long r; - _BitScanForward( &r, (U32)val ); - return (int)(r>>3); -# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) - return (__builtin_ctz((U32)val) >> 3); + _BitScanForward(&r, (U32)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctz((U32)val) >> 3; # else - static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, - 3, 2, 2, 1, 3, 2, 0, 1, - 3, 3, 1, 2, 2, 2, 2, 0, - 3, 1, 2, 0, 1, 0, 1, 1 }; - return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; + const U32 m = 0x01010101; + return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24; # endif } } else /* Big Endian CPU */ { - if (sizeof(val)==8) { /* 64-bits */ -# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) - unsigned long r = 0; - _BitScanReverse64( &r, val ); - return (unsigned)(r>>3); -# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) - return (__builtin_clzll((U64)val) >> 3); + if (sizeof(val)==8) { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clzll((U64)val) >> 3; # else +#if 1 + /* this method is probably faster, + * but adds a 128 bytes lookup table */ + static const unsigned char ctz7_tab[128] = { + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + }; + U64 const mask = 0x0101010101010101ULL; + U64 const t = (((val >> 8) - mask) | val) & mask; + return ctz7_tab[(t * 0x0080402010080402ULL) >> 57]; +#else + /* this method doesn't consume memory space like the previous one, + * but it contains several branches, + * that may end up slowing execution */ static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. - Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. - Note that this code path is never triggered in 32-bits mode. */ + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ unsigned r; if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } r += (!val); return r; +#endif # endif } else /* 32 bits */ { -# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) - unsigned long r = 0; - _BitScanReverse( &r, (unsigned long)val ); - return (unsigned)(r>>3); -# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) - return (__builtin_clz((U32)val) >> 3); +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clz((U32)val) >> 3; # else - unsigned r; - if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } - r += (!val); - return r; + val >>= 8; + val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) | + (val + 0x00FF0000)) >> 24; + return (unsigned)val ^ 3; # endif } } } + #define STEPSIZE sizeof(reg_t) LZ4_FORCE_INLINE unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) @@ -450,15 +693,34 @@ static const U32 LZ4_skipTrigger = 6; /* Increase this value ==> compression ru /*-************************************ * Local Structures and types **************************************/ -typedef enum { notLimited = 0, limitedOutput = 1 } limitedOutput_directive; -typedef enum { byPtr, byU32, byU16 } tableType_t; - -typedef enum { noDict = 0, withPrefix64k, usingExtDict } dict_directive; +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Everything concerning the preceding content is + * in a separate context, pointed to by ctx->dictCtx. + * ctx->dictionary, ctx->dictSize, and table entries + * in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; -typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; -typedef enum { full = 0, partial = 1 } earlyEnd_directive; - /*-************************************ * Local Utils @@ -466,13 +728,32 @@ typedef enum { full = 0, partial = 1 } earlyEnd_directive; int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } -int LZ4_sizeofState() { return LZ4_STREAMSIZE; } +int LZ4_sizeofState(void) { return sizeof(LZ4_stream_t); } + +/*-**************************************** +* Internal Definitions, used only in Tests +*******************************************/ +#if defined (__cplusplus) +extern "C" { +#endif + +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize); + +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize); +int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest, + int compressedSize, int targetOutputSize, int dstCapacity, + const void* dictStart, size_t dictSize); +#if defined (__cplusplus) +} +#endif /*-****************************** * Compression functions ********************************/ -static U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +LZ4_FORCE_INLINE U32 LZ4_hash4(U32 sequence, tableType_t const tableType) { if (tableType == byU16) return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); @@ -480,15 +761,16 @@ static U32 LZ4_hash4(U32 sequence, tableType_t const tableType) return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); } -static U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType) { - static const U64 prime5bytes = 889523592379ULL; - static const U64 prime8bytes = 11400714785074694791ULL; const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; - if (LZ4_isLittleEndian()) + if (LZ4_isLittleEndian()) { + const U64 prime5bytes = 889523592379ULL; return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); - else + } else { + const U64 prime8bytes = 11400714785074694791ULL; return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } } LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) @@ -497,10 +779,37 @@ LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tab return LZ4_hash4(LZ4_read32(p), tableType); } -static void LZ4_putPositionOnHash(const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase) +LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPositionOnHash(const BYTE* p, U32 h, + void* tableBase, tableType_t const tableType, + const BYTE* srcBase) { switch (tableType) { + case clearedTable: { /* illegal! */ assert(0); return; } case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } @@ -513,71 +822,163 @@ LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_ LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); } -static const BYTE* LZ4_getPositionOnHash(U32 h, void* tableBase, tableType_t tableType, const BYTE* srcBase) +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +LZ4_FORCE_INLINE U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32* const hashTable = (const U32*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16* const hashTable = (const U16*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) { - if (tableType == byPtr) { const BYTE** hashTable = (const BYTE**) tableBase; return hashTable[h]; } - if (tableType == byU32) { const U32* const hashTable = (U32*) tableBase; return hashTable[h] + srcBase; } - { const U16* const hashTable = (U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ } -LZ4_FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +LZ4_FORCE_INLINE const BYTE* +LZ4_getPosition(const BYTE* p, + const void* tableBase, tableType_t tableType, + const BYTE* srcBase) { U32 const h = LZ4_hashPosition(p, tableType); return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); } +LZ4_FORCE_INLINE void +LZ4_prepareTable(LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if ((tableType_t)cctx->tableType != clearedTable) { + assert(inputSize >= 0); + if ((tableType_t)cctx->tableType != tableType + || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU) + || ((tableType == byU32) && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = (U32)clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, + * is faster than compressing without a gap. + * However, compressing with currentOffset == 0 is faster still, + * so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} /** LZ4_compress_generic() : - inlined, to ensure branches are decided at compilation time */ -LZ4_FORCE_INLINE int LZ4_compress_generic( + * inlined, to ensure branches are decided at compilation time. + * Presumed already validated at this stage: + * - source != NULL + * - inputSize > 0 + */ +LZ4_FORCE_INLINE int LZ4_compress_generic_validated( LZ4_stream_t_internal* const cctx, const char* const source, char* const dest, const int inputSize, + int* inputConsumed, /* only written when outputDirective == fillOutput */ const int maxOutputSize, - const limitedOutput_directive outputLimited, + const limitedOutput_directive outputDirective, const tableType_t tableType, - const dict_directive dict, + const dict_directive dictDirective, const dictIssue_directive dictIssue, - const U32 acceleration) + const int acceleration) { + int result; const BYTE* ip = (const BYTE*) source; - const BYTE* base; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; const BYTE* lowLimit; - const BYTE* const lowRefLimit = ip - cctx->dictSize; - const BYTE* const dictionary = cctx->dictionary; - const BYTE* const dictEnd = dictionary + cctx->dictSize; - const ptrdiff_t dictDelta = dictEnd - (const BYTE*)source; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary ? dictionary + dictSize : dictionary; const BYTE* anchor = (const BYTE*) source; const BYTE* const iend = ip + inputSize; - const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; const BYTE* const matchlimit = iend - LASTLITERALS; + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = (dictionary == NULL) ? NULL : + (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; + BYTE* op = (BYTE*) dest; BYTE* const olimit = op + maxOutputSize; + U32 offset = 0; U32 forwardH; - /* Init conditions */ - if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ - switch(dict) - { - case noDict: - default: - base = (const BYTE*)source; - lowLimit = (const BYTE*)source; - break; - case withPrefix64k: - base = (const BYTE*)source - cctx->currentOffset; - lowLimit = (const BYTE*)source - cctx->dictSize; - break; - case usingExtDict: - base = (const BYTE*)source - cctx->currentOffset; - lowLimit = (const BYTE*)source; - break; + DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType); + assert(ip != NULL); + /* If init conditions are not met, we don't have to mark stream + * as having dirty context, since no action was taken yet */ + if (outputDirective == fillOutput && maxOutputSize < 1) { return 0; } /* Impossible to store anything */ + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) { return 0; } /* Size too large (not within 64K limit) */ + if (tableType==byPtr) assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; } - if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ - if (inputSizecurrentOffset += (U32)inputSize; + cctx->tableType = (U32)tableType; + + if (inputSizehashTable, tableType, base); @@ -585,50 +986,113 @@ LZ4_FORCE_INLINE int LZ4_compress_generic( /* Main Loop */ for ( ; ; ) { - ptrdiff_t refDelta = 0; const BYTE* match; BYTE* token; + const BYTE* filledIp; /* Find a match */ - { const BYTE* forwardIp = ip; - unsigned step = 1; - unsigned searchMatchNb = acceleration << LZ4_skipTrigger; + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; do { U32 const h = forwardH; ip = forwardIp; forwardIp += step; step = (searchMatchNb++ >> LZ4_skipTrigger); - if (unlikely(forwardIp > mflimit)) goto _last_literals; + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); - if (dict==usingExtDict) { - if (match < (const BYTE*)source) { - refDelta = dictDelta; + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+LZ4_DISTANCE_MAX < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ lowLimit = dictionary; } else { - refDelta = 0; + match = base + matchIndex; lowLimit = (const BYTE*)source; - } } + } + } else if (dictDirective == usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + assert(startIndex - matchIndex >= MINMATCH); + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } forwardH = LZ4_hashPosition(forwardIp, tableType); - LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex); + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */ + assert(matchIndex < current); + if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) + && (matchIndex+LZ4_DISTANCE_MAX < current)) { + continue; + } /* too far */ + assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } - } while ( ((dictIssue==dictSmall) ? (match < lowRefLimit) : 0) - || ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip)) - || (LZ4_read32(match+refDelta) != LZ4_read32(ip)) ); + } while(1); } /* Catch up */ - while (((ip>anchor) & (match+refDelta > lowLimit)) && (unlikely(ip[-1]==match[refDelta-1]))) { ip--; match--; } + filledIp = ip; + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } /* Encode Literals */ { unsigned const litLength = (unsigned)(ip - anchor); token = op++; - if ((outputLimited) && /* Check output buffer overflow */ - (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) - return 0; + if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) { + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + if ((outputDirective == fillOutput) && + (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { + op--; + goto _last_literals; + } if (litLength >= RUN_MASK) { - int len = (int)litLength-RUN_MASK; + int len = (int)(litLength - RUN_MASK); *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; *op++ = (BYTE)len; @@ -636,37 +1100,87 @@ LZ4_FORCE_INLINE int LZ4_compress_generic( else *token = (BYTE)(litLength< olimit)) { + /* the match was too close to the end, rewind and go to last literals */ + op = token; + goto _last_literals; + } + /* Encode Offset */ - LZ4_writeLE16(op, (U16)(ip-match)); op+=2; + if (maybe_extMem) { /* static test */ + DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source)); + assert(offset <= LZ4_DISTANCE_MAX && offset > 0); + LZ4_writeLE16(op, (U16)offset); op+=2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip-match <= LZ4_DISTANCE_MAX); + LZ4_writeLE16(op, (U16)(ip - match)); op+=2; + } /* Encode MatchLength */ { unsigned matchCode; - if ((dict==usingExtDict) && (lowLimit==dictionary)) { - const BYTE* limit; - match += refDelta; - limit = ip + (dictEnd-match); + if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) + && (lowLimit==dictionary) /* match within extDict */ ) { + const BYTE* limit = ip + (dictEnd-match); + assert(dictEnd > match); if (limit > matchlimit) limit = matchlimit; matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); - ip += MINMATCH + matchCode; + ip += (size_t)matchCode + MINMATCH; if (ip==limit) { - unsigned const more = LZ4_count(ip, (const BYTE*)source, matchlimit); + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); matchCode += more; ip += more; } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); } else { matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); - ip += MINMATCH + matchCode; + ip += (size_t)matchCode + MINMATCH; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); } - if ( outputLimited && /* Check output buffer overflow */ - (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) - return 0; + if ((outputDirective) && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) { + if (outputDirective == fillOutput) { + /* Match description too long : reduce it */ + U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255; + ip -= matchCode - newMatchCode; + assert(newMatchCode < matchCode); + matchCode = newMatchCode; + if (unlikely(ip <= filledIp)) { + /* We have already filled up to filledIp so if ip ends up less than filledIp + * we have positions in the hash table beyond the current position. This is + * a problem if we reuse the hash table. So we have to remove these positions + * from the hash table. + */ + const BYTE* ptr; + DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip)); + for (ptr = ip; ptr <= filledIp; ++ptr) { + U32 const h = LZ4_hashPosition(ptr, tableType); + LZ4_clearHash(h, cctx->hashTable, tableType); + } + } + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } if (matchCode >= ML_MASK) { *token += ML_MASK; matchCode -= ML_MASK; @@ -681,41 +1195,90 @@ LZ4_FORCE_INLINE int LZ4_compress_generic( } else *token += (BYTE)(matchCode); } + /* Ensure we have enough space for the last literals. */ + assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit)); anchor = ip; /* Test end of chunk */ - if (ip > mflimit) break; + if (ip >= mflimitPlusOne) break; /* Fill table */ LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); /* Test next position */ - match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); - if (dict==usingExtDict) { - if (match < (const BYTE*)source) { - refDelta = dictDelta; - lowLimit = dictionary; - } else { - refDelta = 0; - lowLimit = (const BYTE*)source; - } } - LZ4_putPosition(ip, cctx->hashTable, tableType, base); - if ( ((dictIssue==dictSmall) ? (match>=lowRefLimit) : 1) - && (match+MAX_DISTANCE>=ip) - && (LZ4_read32(match+refDelta)==LZ4_read32(ip)) ) - { token=op++; *token=0; goto _next_match; } + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+LZ4_DISTANCE_MAX >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } /* Prepare next loop */ forwardH = LZ4_hashPosition(++ip, tableType); + } _last_literals: /* Encode Last Literals */ - { size_t const lastRun = (size_t)(iend - anchor); - if ( (outputLimited) && /* Check output buffer overflow */ - ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) - return 0; + { size_t lastRun = (size_t)(iend - anchor); + if ( (outputDirective) && /* Check output buffer overflow */ + (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) { + if (outputDirective == fillOutput) { + /* adapt lastRun to fill 'dst' */ + assert(olimit >= op); + lastRun = (size_t)(olimit-op) - 1/*token*/; + lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/ + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun); if (lastRun >= RUN_MASK) { size_t accumulator = lastRun - RUN_MASK; *op++ = RUN_MASK << ML_BITS; @@ -724,45 +1287,143 @@ LZ4_FORCE_INLINE int LZ4_compress_generic( } else { *op++ = (BYTE)(lastRun< 0); + DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result); + return result; } - -int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time; + * takes care of src == (NULL, 0) + * and forward the rest to LZ4_compress_generic_validated */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const src, + char* const dst, + const int srcSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int dstCapacity, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) { - LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; - LZ4_resetStream((LZ4_stream_t*)state); - if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; - - if (maxOutputSize >= LZ4_compressBound(inputSize)) { - if (inputSize < LZ4_64Klimit) - return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration); - else - return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); - } else { - if (inputSize < LZ4_64Klimit) - return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); - else - return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i", + srcSize, dstCapacity); + + if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */ + if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */ + if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */ + DEBUGLOG(5, "Generating an empty block"); + assert(outputDirective == notLimited || dstCapacity >= 1); + assert(dst != NULL); + dst[0] = 0; + if (outputDirective == fillOutput) { + assert (inputConsumed != NULL); + *inputConsumed = 0; + } + return 1; + } + assert(src != NULL); + + return LZ4_compress_generic_validated(cctx, src, dst, srcSize, + inputConsumed, /* only written into if outputDirective == fillOutput */ + dstCapacity, outputDirective, + tableType, dictDirective, dictIssue, acceleration); +} + + +int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse; + assert(ctx != NULL); + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } } } int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) { + int result; #if (LZ4_HEAPMODE) - void* ctxPtr = ALLOCATOR(1, sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + LZ4_stream_t* ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; #else LZ4_stream_t ctx; - void* const ctxPtr = &ctx; + LZ4_stream_t* const ctxPtr = &ctx; #endif - - int const result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); #if (LZ4_HEAPMODE) FREEMEM(ctxPtr); @@ -771,205 +1432,37 @@ int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutp } -int LZ4_compress_default(const char* source, char* dest, int inputSize, int maxOutputSize) -{ - return LZ4_compress_fast(source, dest, inputSize, maxOutputSize, 1); -} - - -/* hidden debug function */ -/* strangely enough, gcc generates faster code when this function is uncommented, even if unused */ -int LZ4_compress_fast_force(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +int LZ4_compress_default(const char* src, char* dst, int srcSize, int maxOutputSize) { - LZ4_stream_t ctx; - LZ4_resetStream(&ctx); - - if (inputSize < LZ4_64Klimit) - return LZ4_compress_generic(&ctx.internal_donotuse, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); - else - return LZ4_compress_generic(&ctx.internal_donotuse, source, dest, inputSize, maxOutputSize, limitedOutput, sizeof(void*)==8 ? byU32 : byPtr, noDict, noDictIssue, acceleration); -} - - -/*-****************************** -* *_destSize() variant -********************************/ - -static int LZ4_compress_destSize_generic( - LZ4_stream_t_internal* const ctx, - const char* const src, - char* const dst, - int* const srcSizePtr, - const int targetDstSize, - const tableType_t tableType) -{ - const BYTE* ip = (const BYTE*) src; - const BYTE* base = (const BYTE*) src; - const BYTE* lowLimit = (const BYTE*) src; - const BYTE* anchor = ip; - const BYTE* const iend = ip + *srcSizePtr; - const BYTE* const mflimit = iend - MFLIMIT; - const BYTE* const matchlimit = iend - LASTLITERALS; - - BYTE* op = (BYTE*) dst; - BYTE* const oend = op + targetDstSize; - BYTE* const oMaxLit = op + targetDstSize - 2 /* offset */ - 8 /* because 8+MINMATCH==MFLIMIT */ - 1 /* token */; - BYTE* const oMaxMatch = op + targetDstSize - (LASTLITERALS + 1 /* token */); - BYTE* const oMaxSeq = oMaxLit - 1 /* token */; - - U32 forwardH; - - - /* Init conditions */ - if (targetDstSize < 1) return 0; /* Impossible to store anything */ - if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size, too large (or negative) */ - if ((tableType == byU16) && (*srcSizePtr>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ - if (*srcSizePtrhashTable, tableType, base); - ip++; forwardH = LZ4_hashPosition(ip, tableType); - - /* Main Loop */ - for ( ; ; ) { - const BYTE* match; - BYTE* token; - - /* Find a match */ - { const BYTE* forwardIp = ip; - unsigned step = 1; - unsigned searchMatchNb = 1 << LZ4_skipTrigger; - - do { - U32 h = forwardH; - ip = forwardIp; - forwardIp += step; - step = (searchMatchNb++ >> LZ4_skipTrigger); - - if (unlikely(forwardIp > mflimit)) goto _last_literals; - - match = LZ4_getPositionOnHash(h, ctx->hashTable, tableType, base); - forwardH = LZ4_hashPosition(forwardIp, tableType); - LZ4_putPositionOnHash(ip, h, ctx->hashTable, tableType, base); - - } while ( ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip)) - || (LZ4_read32(match) != LZ4_read32(ip)) ); - } - - /* Catch up */ - while ((ip>anchor) && (match > lowLimit) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } - - /* Encode Literal length */ - { unsigned litLength = (unsigned)(ip - anchor); - token = op++; - if (op + ((litLength+240)/255) + litLength > oMaxLit) { - /* Not enough space for a last match */ - op--; - goto _last_literals; - } - if (litLength>=RUN_MASK) { - unsigned len = litLength - RUN_MASK; - *token=(RUN_MASK<= 255 ; len-=255) *op++ = 255; - *op++ = (BYTE)len; - } - else *token = (BYTE)(litLength< oMaxMatch) { - /* Match description too long : reduce it */ - matchLength = (15-1) + (oMaxMatch-op) * 255; - } - ip += MINMATCH + matchLength; - - if (matchLength>=ML_MASK) { - *token += ML_MASK; - matchLength -= ML_MASK; - while (matchLength >= 255) { matchLength-=255; *op++ = 255; } - *op++ = (BYTE)matchLength; - } - else *token += (BYTE)(matchLength); - } - - anchor = ip; - - /* Test end of block */ - if (ip > mflimit) break; - if (op > oMaxSeq) break; - - /* Fill table */ - LZ4_putPosition(ip-2, ctx->hashTable, tableType, base); - - /* Test next position */ - match = LZ4_getPosition(ip, ctx->hashTable, tableType, base); - LZ4_putPosition(ip, ctx->hashTable, tableType, base); - if ( (match+MAX_DISTANCE>=ip) - && (LZ4_read32(match)==LZ4_read32(ip)) ) - { token=op++; *token=0; goto _next_match; } - - /* Prepare next loop */ - forwardH = LZ4_hashPosition(++ip, tableType); - } - -_last_literals: - /* Encode Last Literals */ - { size_t lastRunSize = (size_t)(iend - anchor); - if (op + 1 /* token */ + ((lastRunSize+240)/255) /* litLength */ + lastRunSize /* literals */ > oend) { - /* adapt lastRunSize to fill 'dst' */ - lastRunSize = (oend-op) - 1; - lastRunSize -= (lastRunSize+240)/255; - } - ip = anchor + lastRunSize; - - if (lastRunSize >= RUN_MASK) { - size_t accumulator = lastRunSize - RUN_MASK; - *op++ = RUN_MASK << ML_BITS; - for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; - *op++ = (BYTE) accumulator; - } else { - *op++ = (BYTE)(lastRunSize<= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); } else { - if (*srcSizePtr < LZ4_64Klimit) - return LZ4_compress_destSize_generic(&state->internal_donotuse, src, dst, srcSizePtr, targetDstSize, byU16); - else - return LZ4_compress_destSize_generic(&state->internal_donotuse, src, dst, srcSizePtr, targetDstSize, sizeof(void*)==8 ? byU32 : byPtr); - } + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1); + } else { + tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1); + } } } int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) { #if (LZ4_HEAPMODE) - LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOCATOR(1, sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctx == NULL) return 0; #else LZ4_stream_t ctxBody; LZ4_stream_t* ctx = &ctxBody; @@ -989,69 +1482,142 @@ int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targe * Streaming functions ********************************/ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) LZ4_stream_t* LZ4_createStream(void) { - LZ4_stream_t* lz4s = (LZ4_stream_t*)ALLOCATOR(8, LZ4_STREAMSIZE_U64); - LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal)); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ - LZ4_resetStream(lz4s); + LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(sizeof(LZ4_stream_t) >= sizeof(LZ4_stream_t_internal)); + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_initStream(lz4s, sizeof(*lz4s)); return lz4s; } +#endif +static size_t LZ4_stream_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_stream_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_stream_t); +#else + return 1; /* effectively disabled */ +#endif +} + +LZ4_stream_t* LZ4_initStream (void* buffer, size_t size) +{ + DEBUGLOG(5, "LZ4_initStream"); + if (buffer == NULL) { return NULL; } + if (size < sizeof(LZ4_stream_t)) { return NULL; } + if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL; + MEM_INIT(buffer, 0, sizeof(LZ4_stream_t_internal)); + return (LZ4_stream_t*)buffer; +} + +/* resetStream is now deprecated, + * prefer initStream() which is more general */ void LZ4_resetStream (LZ4_stream_t* LZ4_stream) { - DEBUGLOG(4, "LZ4_resetStream"); - MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t_internal)); } +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) int LZ4_freeStream (LZ4_stream_t* LZ4_stream) { if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); FREEMEM(LZ4_stream); return (0); } +#endif #define HASH_UNIT sizeof(reg_t) int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) { LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; const BYTE* p = (const BYTE*)dictionary; const BYTE* const dictEnd = p + dictSize; const BYTE* base; - if ((dict->initCheck) || (dict->currentOffset > 1 GB)) /* Uninitialized structure, or reuse overflow */ - LZ4_resetStream(LZ4_dict); + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + /* It's necessary to reset the context, + * and not just continue it with prepareTable() + * to avoid any risk of generating overflowing matchIndex + * when compressing using this dictionary */ + LZ4_resetStream(LZ4_dict); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + dict->currentOffset += 64 KB; if (dictSize < (int)HASH_UNIT) { - dict->dictionary = NULL; - dict->dictSize = 0; return 0; } if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; - dict->currentOffset += 64 KB; - base = p - dict->currentOffset; + base = dictEnd - dict->currentOffset; dict->dictionary = p; dict->dictSize = (U32)(dictEnd - p); - dict->currentOffset += dict->dictSize; + dict->tableType = (U32)tableType; while (p <= dictEnd-HASH_UNIT) { - LZ4_putPosition(p, dict->hashTable, byU32, base); + LZ4_putPosition(p, dict->hashTable, tableType, base); p+=3; } - return dict->dictSize; + return (int)dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) +{ + const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL : + &(dictionaryStream->internal_donotuse); + + DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)", + workingStream, dictionaryStream, + dictCtx != NULL ? dictCtx->dictSize : 0); + + if (dictCtx != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (workingStream->internal_donotuse.currentOffset == 0) { + workingStream->internal_donotuse.currentOffset = 64 KB; + } + + /* Don't actually attach an empty dictionary. + */ + if (dictCtx->dictSize == 0) { + dictCtx = NULL; + } + } + workingStream->internal_donotuse.dictCtx = dictCtx; } -static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, const BYTE* src) +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) { - if ((LZ4_dict->currentOffset > 0x80000000) || - ((uptrval)LZ4_dict->currentOffset > (uptrval)src)) { /* address space overflow */ + assert(nextSize >= 0); + if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ /* rescale hash table */ U32 const delta = LZ4_dict->currentOffset - 64 KB; const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; int i; + DEBUGLOG(4, "LZ4_renormDictT"); for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; else LZ4_dict->hashTable[i] -= delta; @@ -1063,69 +1629,101 @@ static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, const BYTE* src) } -int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, + const char* source, char* dest, + int inputSize, int maxOutputSize, + int acceleration) { - LZ4_stream_t_internal* streamPtr = &LZ4_stream->internal_donotuse; - const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize; - - const BYTE* smallest = (const BYTE*) source; - if (streamPtr->initCheck) return 0; /* Uninitialized structure detected */ - if ((streamPtr->dictSize>0) && (smallest>dictEnd)) smallest = dictEnd; - LZ4_renormDictT(streamPtr, smallest); - if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + const tableType_t tableType = byU32; + LZ4_stream_t_internal* const streamPtr = &LZ4_stream->internal_donotuse; + const char* dictEnd = streamPtr->dictSize ? (const char*)streamPtr->dictionary + streamPtr->dictSize : NULL; + + DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i, dictSize=%u)", inputSize, streamPtr->dictSize); + + LZ4_renormDictT(streamPtr, inputSize); /* fix index overflow */ + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + /* invalidate tiny dictionaries */ + if ( (streamPtr->dictSize < 4) /* tiny dictionary : not enough for a hash */ + && (dictEnd != source) /* prefix mode */ + && (inputSize > 0) /* tolerance : don't lose history, in case next invocation would use prefix mode */ + && (streamPtr->dictCtx == NULL) /* usingDictCtx */ + ) { + DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary); + /* remove dictionary existence from history, to employ faster prefix mode */ + streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)source; + dictEnd = source; + } /* Check overlapping input/dictionary space */ - { const BYTE* sourceEnd = (const BYTE*) source + inputSize; - if ((sourceEnd > streamPtr->dictionary) && (sourceEnd < dictEnd)) { + { const char* const sourceEnd = source + inputSize; + if ((sourceEnd > (const char*)streamPtr->dictionary) && (sourceEnd < dictEnd)) { streamPtr->dictSize = (U32)(dictEnd - sourceEnd); if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; - streamPtr->dictionary = dictEnd - streamPtr->dictSize; + streamPtr->dictionary = (const BYTE*)dictEnd - streamPtr->dictSize; } } /* prefix mode : source data follows dictionary */ - if (dictEnd == (const BYTE*)source) { - int result; + if (dictEnd == source) { if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, withPrefix64k, dictSmall, acceleration); + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); else - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, withPrefix64k, noDictIssue, acceleration); - streamPtr->dictSize += (U32)inputSize; - streamPtr->currentOffset += (U32)inputSize; - return result; + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); } /* external dictionary mode */ { int result; - if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, usingExtDict, dictSmall, acceleration); - else - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, usingExtDict, noDictIssue, acceleration); + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + LZ4_memcpy(streamPtr, streamPtr->dictCtx, sizeof(*streamPtr)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { /* small data <= 4 KB */ + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } streamPtr->dictionary = (const BYTE*)source; streamPtr->dictSize = (U32)inputSize; - streamPtr->currentOffset += (U32)inputSize; return result; } } -/* Hidden debug function, to force external dictionary mode */ -int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int inputSize) +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) { LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; int result; - const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize; - const BYTE* smallest = dictEnd; - if (smallest > (const BYTE*) source) smallest = (const BYTE*) source; - LZ4_renormDictT(streamPtr, smallest); + LZ4_renormDictT(streamPtr, srcSize); - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } streamPtr->dictionary = (const BYTE*)source; - streamPtr->dictSize = (U32)inputSize; - streamPtr->currentOffset += (U32)inputSize; + streamPtr->dictSize = (U32)srcSize; return result; } @@ -1134,19 +1732,25 @@ int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* /*! LZ4_saveDict() : * If previously compressed data block is not guaranteed to remain available at its memory location, * save it into a safer place (char* safeBuffer). - * Note : you don't need to call LZ4_loadDict() afterwards, - * dictionary is immediately usable, you can therefore call LZ4_compress_fast_continue(). - * Return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + * Note : no need to call LZ4_loadDict() afterwards, dictionary is immediately usable, + * one can therefore call LZ4_compress_fast_continue() right after. + * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. */ int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) { LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; - const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; - if ((U32)dictSize > 64 KB) dictSize = 64 KB; /* useless to define a dictionary > 64 KB */ - if ((U32)dictSize > dict->dictSize) dictSize = dict->dictSize; + DEBUGLOG(5, "LZ4_saveDict : dictSize=%i, safeBuffer=%p", dictSize, safeBuffer); + + if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; } - memmove(safeBuffer, previousDictEnd - dictSize, dictSize); + if (safeBuffer == NULL) assert(dictSize == 0); + if (dictSize > 0) { + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + assert(dict->dictionary); + LZ4_memmove(safeBuffer, previousDictEnd - dictSize, (size_t)dictSize); + } dict->dictionary = (const BYTE*)safeBuffer; dict->dictSize = (U32)dictSize; @@ -1156,245 +1760,759 @@ int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) -/*-***************************** -* Decompression functions -*******************************/ +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + + +/* variant for decompress_unsafe() + * does not know end of input + * presumes input is well formed + * note : will consume at least one byte */ +size_t read_long_length_no_check(const BYTE** pp) +{ + size_t b, l = 0; + do { b = **pp; (*pp)++; l += b; } while (b==255); + DEBUGLOG(6, "read_long_length_no_check: +length=%zu using %zu input bytes", l, l/255 + 1) + return l; +} + +/* core decoder variant for LZ4_decompress_fast*() + * for legacy support only : these entry points are deprecated. + * - Presumes input is correctly formed (no defense vs malformed inputs) + * - Does not know input size (presume input buffer is "large enough") + * - Decompress a full block (only) + * @return : nb of bytes read from input. + * Note : this variant is not optimized for speed, just for maintenance. + * the goal is to remove support of decompress_fast*() variants by v2.0 +**/ +LZ4_FORCE_INLINE int +LZ4_decompress_unsafe_generic( + const BYTE* const istart, + BYTE* const ostart, + int decompressedSize, + + size_t prefixSize, + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note: =0 if dictStart==NULL */ + ) +{ + const BYTE* ip = istart; + BYTE* op = (BYTE*)ostart; + BYTE* const oend = ostart + decompressedSize; + const BYTE* const prefixStart = ostart - prefixSize; + + DEBUGLOG(5, "LZ4_decompress_unsafe_generic"); + if (dictStart == NULL) assert(dictSize == 0); + + while (1) { + /* start new sequence */ + unsigned token = *ip++; + + /* literals */ + { size_t ll = token >> ML_BITS; + if (ll==15) { + /* long literal length */ + ll += read_long_length_no_check(&ip); + } + if ((size_t)(oend-op) < ll) return -1; /* output buffer overflow */ + LZ4_memmove(op, ip, ll); /* support in-place decompression */ + op += ll; + ip += ll; + if ((size_t)(oend-op) < MFLIMIT) { + if (op==oend) break; /* end of block */ + DEBUGLOG(5, "invalid: literals end at distance %zi from end of block", oend-op); + /* incorrect end of block : + * last match must start at least MFLIMIT==12 bytes before end of output block */ + return -1; + } } + + /* match */ + { size_t ml = token & 15; + size_t const offset = LZ4_readLE16(ip); + ip+=2; + + if (ml==15) { + /* long literal length */ + ml += read_long_length_no_check(&ip); + } + ml += MINMATCH; + + if ((size_t)(oend-op) < ml) return -1; /* output buffer overflow */ + + { const BYTE* match = op - offset; + + /* out of range */ + if (offset > (size_t)(op - prefixStart) + dictSize) { + DEBUGLOG(6, "offset out of range"); + return -1; + } + + /* check special case : extDict */ + if (offset > (size_t)(op - prefixStart)) { + /* extDict scenario */ + const BYTE* const dictEnd = dictStart + dictSize; + const BYTE* extMatch = dictEnd - (offset - (size_t)(op-prefixStart)); + size_t const extml = (size_t)(dictEnd - extMatch); + if (extml > ml) { + /* match entirely within extDict */ + LZ4_memmove(op, extMatch, ml); + op += ml; + ml = 0; + } else { + /* match split between extDict & prefix */ + LZ4_memmove(op, extMatch, extml); + op += extml; + ml -= extml; + } + match = prefixStart; + } + + /* match copy - slow variant, supporting overlap copy */ + { size_t u; + for (u=0; u= ipmax before start of loop. Returns initial_error if so. + * @error (output) - error code. Must be set to 0 before call. +**/ +typedef size_t Rvl_t; +static const Rvl_t rvl_error = (Rvl_t)(-1); +LZ4_FORCE_INLINE Rvl_t +read_variable_length(const BYTE** ip, const BYTE* ilimit, + int initial_check) +{ + Rvl_t s, length = 0; + assert(ip != NULL); + assert(*ip != NULL); + assert(ilimit != NULL); + if (initial_check && unlikely((*ip) >= ilimit)) { /* read limit reached */ + return rvl_error; + } + do { + s = **ip; + (*ip)++; + length += s; + if (unlikely((*ip) > ilimit)) { /* read limit reached */ + return rvl_error; + } + /* accumulator overflow detection (32-bit mode only) */ + if ((sizeof(length)<8) && unlikely(length > ((Rvl_t)(-1)/2)) ) { + return rvl_error; + } + } while (s==255); + + return length; +} + /*! LZ4_decompress_generic() : * This generic decompression function covers all use cases. * It shall be instantiated several times, using different sets of directives. * Note that it is important for performance that this function really get inlined, * in order to remove useless branches during compilation optimization. */ -LZ4_FORCE_O2_GCC_PPC64LE -LZ4_FORCE_INLINE int LZ4_decompress_generic( +LZ4_FORCE_INLINE int +LZ4_decompress_generic( const char* const src, char* const dst, int srcSize, int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ - int endOnInput, /* endOnOutputSize, endOnInputSize */ - int partialDecoding, /* full, partial */ - int targetOutputSize, /* only used if partialDecoding==partial */ - int dict, /* noDict, withPrefix64k, usingExtDict */ + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ const BYTE* const dictStart, /* only if dict==usingExtDict */ const size_t dictSize /* note : = 0 if noDict */ ) { - const BYTE* ip = (const BYTE*) src; - const BYTE* const iend = ip + srcSize; + if ((src == NULL) || (outputSize < 0)) { return -1; } - BYTE* op = (BYTE*) dst; - BYTE* const oend = op + outputSize; - BYTE* cpy; - BYTE* oexit = op + targetOutputSize; + { const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; - const BYTE* const dictEnd = (const BYTE*)dictStart + dictSize; - const unsigned inc32table[8] = {0, 1, 2, 1, 0, 4, 4, 4}; - const int dec64table[8] = {0, 0, 0, -1, -4, 1, 2, 3}; + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; - const int safeDecode = (endOnInput==endOnInputSize); - const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; + const int checkOffset = (dictSize < (int)(64 KB)); - /* Special cases */ - if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => just decode everything */ - if ((endOnInput) && (unlikely(outputSize==0))) return ((srcSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ - if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1); - /* Main Loop : decode sequences */ - while (1) { - size_t length; + /* Set up the "end" pointers for the shortcut. */ + const BYTE* const shortiend = iend - 14 /*maxLL*/ - 2 /*offset*/; + const BYTE* const shortoend = oend - 14 /*maxLL*/ - 18 /*maxML*/; + const BYTE* match; size_t offset; + unsigned token; + size_t length; + + + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); + + /* Special cases */ + assert(lowPrefix <= op); + if (unlikely(outputSize==0)) { + /* Empty output buffer */ + if (partialDecoding) return 0; + return ((srcSize==1) && (*ip==0)) ? 0 : -1; + } + if (unlikely(srcSize==0)) { return -1; } + + /* LZ4_FAST_DEC_LOOP: + * designed for modern OoO performance cpus, + * where copying reliably 32-bytes is preferable to an unpredictable branch. + * note : fast loop may show a regression for some client arm chips. */ +#if LZ4_FAST_DEC_LOOP + if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(6, "skip fast decode loop"); + goto safe_decode; + } + + /* Fast loop : decode sequences as long as output < oend-FASTLOOP_SAFE_DISTANCE */ + while (1) { + /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ + assert(oend - op >= FASTLOOP_SAFE_DISTANCE); + assert(ip < iend); + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + /* decode literal length */ + if (length == RUN_MASK) { + size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + + /* copy literals */ + cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ((cpy>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } + LZ4_wildCopy32(op, ip, cpy); + ip += length; op = cpy; + } else { + cpy = op+length; + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + /* We don't need to check oend, since we check it once for each loop below */ + if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; } + /* Literals can only be <= 14, but hope compilers optimize better when copy by a register size */ + LZ4_memcpy(op, ip, 16); + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + assert(match <= op); /* overflow check */ + + /* get matchlength */ + length = token & ML_MASK; + + if (length == ML_MASK) { + size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); + if (addl == rvl_error) { goto _output_error; } + length += addl; + length += MINMATCH; + if (unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */ + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + } else { + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } - unsigned const token = *ip++; - - /* shortcut for common case : - * in most circumstances, we expect to decode small matches (<= 18 bytes) separated by few literals (<= 14 bytes). - * this shortcut was tested on x86 and x64, where it improves decoding speed. - * it has not yet been benchmarked on ARM, Power, mips, etc. */ - if (((ip + 14 /*maxLL*/ + 2 /*offset*/ <= iend) - & (op + 14 /*maxLL*/ + 18 /*maxML*/ <= oend)) - & ((token < (15<> ML_BITS; - size_t const off = LZ4_readLE16(ip+ll); - const BYTE* const matchPtr = op + ll - off; /* pointer underflow risk ? */ - if ((off >= 18) /* do not deal with overlapping matches */ & (matchPtr >= lowPrefix)) { - size_t const ml = (token & ML_MASK) + MINMATCH; - memcpy(op, ip, 16); op += ll; ip += ll + 2 /*offset*/; - memcpy(op, matchPtr, 18); op += ml; + /* Fastpath check: skip LZ4_wildCopy32 when true */ + if ((dict == withPrefix64k) || (match >= lowPrefix)) { + if (offset >= 8) { + assert(match >= lowPrefix); + assert(match <= op); + assert(op + 18 <= oend); + + LZ4_memcpy(op, match, 8); + LZ4_memcpy(op+8, match+8, 8); + LZ4_memcpy(op+16, match+16, 2); + op += length; + continue; + } } } + + if (checkOffset && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + assert(dictEnd != NULL); + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) { + DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd"); + length = MIN(length, (size_t)(oend-op)); + } else { + goto _output_error; /* end-of-block condition violated */ + } } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + LZ4_memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) { *op++ = *copyFrom++; } + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } continue; } - } - /* decode literal length */ - if ((length=(token>>ML_BITS)) == RUN_MASK) { - unsigned s; - do { - s = *ip++; - length += s; - } while ( likely(endOnInput ? ip(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) ) - || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) - { - if (partialDecoding) { - if (cpy > oend) goto _output_error; /* Error : write attempt beyond end of output buffer */ - if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ + assert((op <= oend) && (oend-op >= 32)); + if (unlikely(offset<16)) { + LZ4_memcpy_using_offset(op, match, cpy, offset); } else { - if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ - if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error; /* Error : input must be consumed */ + LZ4_wildCopy32(op, match, cpy); } - memcpy(op, ip, length); - ip += length; - op += length; - break; /* Necessarily EOF, due to parsing restrictions */ - } - LZ4_wildCopy(op, ip, cpy); - ip += length; op = cpy; - - /* get offset */ - offset = LZ4_readLE16(ip); ip+=2; - match = op - offset; - if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ - LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ - - /* get matchlength */ - length = token & ML_MASK; - if (length == ML_MASK) { - unsigned s; - do { - s = *ip++; - if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; - length += s; - } while (s==255); - if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + + op = cpy; /* wildcopy correction */ } - length += MINMATCH; + safe_decode: +#endif + + /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ + while (1) { + assert(ip < iend); + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + /* A two-stage shortcut for the most common case: + * 1) If the literal length is 0..14, and there is enough space, + * enter the shortcut and copy 16 bytes on behalf of the literals + * (in the fast mode, only 8 bytes can be safely copied this way). + * 2) Further if the match length is 4..18, copy 18 bytes in a similar + * manner; but we ensure that there's enough space in the output for + * those 18 bytes earlier, upon entering the shortcut (in other words, + * there is a combined check for both stages). + */ + if ( (length != RUN_MASK) + /* strictly "less than" on input, to re-enter the loop with at least one byte */ + && likely((ip < shortiend) & (op <= shortoend)) ) { + /* Copy the literals */ + LZ4_memcpy(op, ip, 16); + op += length; ip += length; + + /* The second stage: prepare for match copying, decode full info. + * If it doesn't work out, the info won't be wasted. */ + length = token & ML_MASK; /* match length */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); /* check overflow */ + + /* Do not deal with overlapping matches. */ + if ( (length != ML_MASK) + && (offset >= 8) + && (dict==withPrefix64k || match >= lowPrefix) ) { + /* Copy the match. */ + LZ4_memcpy(op + 0, match + 0, 8); + LZ4_memcpy(op + 8, match + 8, 8); + LZ4_memcpy(op +16, match +16, 2); + op += length + MINMATCH; + /* Both stages worked, load the next token. */ + continue; + } + + /* The second stage didn't work out, but the info is ready. + * Propel it right to the point of match copying. */ + goto _copy_match; + } - /* check external dictionary */ - if ((dict==usingExtDict) && (match < lowPrefix)) { - if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error; /* doesn't respect parsing restriction */ + /* decode literal length */ + if (length == RUN_MASK) { + size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + } - if (length <= (size_t)(lowPrefix-match)) { - /* match can be copied as a single segment from external dictionary */ - memmove(op, dictEnd - (lowPrefix-match), length); + /* copy literals */ + cpy = op+length; +#if LZ4_FAST_DEC_LOOP + safe_literal_copy: +#endif + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) { + /* We've either hit the input parsing restriction or the output parsing restriction. + * In the normal scenario, decoding a full block, it must be the last sequence, + * otherwise it's an error (invalid input or dimensions). + * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow. + */ + if (partialDecoding) { + /* Since we are partial decoding we may be in this block because of the output parsing + * restriction, which is not valid since the output buffer is allowed to be undersized. + */ + DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end") + DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length); + DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op)); + DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip)); + /* Finishing in the middle of a literals segment, + * due to lack of input. + */ + if (ip+length > iend) { + length = (size_t)(iend-ip); + cpy = op + length; + } + /* Finishing in the middle of a literals segment, + * due to lack of output space. + */ + if (cpy > oend) { + cpy = oend; + assert(op<=oend); + length = (size_t)(oend-op); + } + } else { + /* We must be on the last sequence (or invalid) because of the parsing limitations + * so check that we exactly consume the input and don't overrun the output buffer. + */ + if ((ip+length != iend) || (cpy > oend)) { + DEBUGLOG(6, "should have been last run of literals") + DEBUGLOG(6, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip+length, iend); + DEBUGLOG(6, "or cpy(%p) > oend(%p)", cpy, oend); + goto _output_error; + } + } + LZ4_memmove(op, ip, length); /* supports overlapping memory regions, for in-place decompression scenarios */ + ip += length; op += length; + /* Necessarily EOF when !partialDecoding. + * When partialDecoding, it is EOF if we've either + * filled the output buffer or + * can't proceed with reading an offset for following match. + */ + if (!partialDecoding || (cpy == oend) || (ip >= (iend-2))) { + break; + } } else { - /* match encompass external dictionary and current block */ - size_t const copySize = (size_t)(lowPrefix-match); - size_t const restSize = length - copySize; - memcpy(op, dictEnd - copySize, copySize); - op += copySize; - if (restSize > (size_t)(op-lowPrefix)) { /* overlap copy */ - BYTE* const endOfMatch = op + restSize; - const BYTE* copyFrom = lowPrefix; - while (op < endOfMatch) *op++ = *copyFrom++; + LZ4_wildCopy8(op, ip, cpy); /* can overwrite up to 8 bytes beyond cpy */ + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + _copy_match: + if (length == ML_MASK) { + size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + +#if LZ4_FAST_DEC_LOOP + safe_match_copy: +#endif + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + assert(dictEnd != NULL); + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + LZ4_memmove(op, dictEnd - (lowPrefix-match), length); + op += length; } else { - memcpy(op, lowPrefix, restSize); - op += restSize; - } } - continue; - } + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + assert(match >= lowPrefix); + + /* copy match within block */ + cpy = op + length; + + /* partialDecoding : may end anywhere within the block */ + assert(op<=oend); + if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + size_t const mlen = MIN(length, (size_t)(oend-op)); + const BYTE* const matchEnd = match + mlen; + BYTE* const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, mlen); + } + op = copyEnd; + if (op == oend) { break; } + continue; + } - /* copy match within block */ - cpy = op + length; - if (unlikely(offset<8)) { - op[0] = match[0]; - op[1] = match[1]; - op[2] = match[2]; - op[3] = match[3]; - match += inc32table[offset]; - memcpy(op+4, match, 4); - match -= dec64table[offset]; - } else { LZ4_copy8(op, match); match+=8; } - op += 8; - - if (unlikely(cpy>oend-12)) { - BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1); - if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ - if (op < oCopyLimit) { - LZ4_wildCopy(op, match, oCopyLimit); - match += oCopyLimit - op; - op = oCopyLimit; + if (unlikely(offset<8)) { + LZ4_write32(op, 0); /* silence msan warning when offset==0 */ + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + LZ4_memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { + LZ4_memcpy(op, match, 8); + match += 8; } - while (op16) LZ4_wildCopy(op+8, match+8, cpy); + op += 8; + + if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy8(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op < cpy) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, 8); + if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } + } + op = cpy; /* wildcopy correction */ } - op = cpy; /* correction */ - } - /* end of decoding */ - if (endOnInput) - return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ - else - return (int) (((const char*)ip)-src); /* Nb of input bytes read */ + /* end of decoding */ + DEBUGLOG(5, "decoded %i bytes", (int) (((char*)op)-dst)); + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ - /* Overflow error detected */ -_output_error: - return (int) (-(((const char*)ip)-src))-1; + /* Overflow error detected */ + _output_error: + return (int) (-(((const char*)ip)-src))-1; + } } -LZ4_FORCE_O2_GCC_PPC64LE +/*===== Instantiate the API decoding functions. =====*/ + +LZ4_FORCE_O2 int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) { - return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, full, 0, noDict, (BYTE*)dest, NULL, 0); + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, + decode_full_block, noDict, + (BYTE*)dest, NULL, 0); } -LZ4_FORCE_O2_GCC_PPC64LE -int LZ4_decompress_safe_partial(const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize) +LZ4_FORCE_O2 +int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) { - return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, partial, targetOutputSize, noDict, (BYTE*)dest, NULL, 0); + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, + partial_decode, + noDict, (BYTE*)dst, NULL, 0); } -LZ4_FORCE_O2_GCC_PPC64LE +LZ4_FORCE_O2 int LZ4_decompress_fast(const char* source, char* dest, int originalSize) { - return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)(dest - 64 KB), NULL, 64 KB); + DEBUGLOG(5, "LZ4_decompress_fast"); + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 0, NULL, 0); +} + +/*===== Instantiate a few more decoding cases, used more than once. =====*/ + +LZ4_FORCE_O2 /* Exported, an obsolete API function. */ +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_partial_withPrefix64k(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/* Another obsolete API function, paired with the previous one. */ +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 64 KB, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_partial_withSmallPrefix(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, + size_t prefixSize) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); } +LZ4_FORCE_O2 +int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest, + int compressedSize, int targetOutputSize, int dstCapacity, + const void* dictStart, size_t dictSize) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 0, (const BYTE*)dictStart, dictSize); +} + +/* The "double dictionary" mode, for use with e.g. ring buffers: the first part + * of the dictionary is passed as prefix, and the second via dictStart + dictSize. + * These routines are used only once, in LZ4_decompress_*_continue(). + */ +LZ4_FORCE_INLINE +int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} /*===== streaming decompression functions =====*/ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) LZ4_streamDecode_t* LZ4_createStreamDecode(void) { - LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOCATOR(1, sizeof(LZ4_streamDecode_t)); - return lz4s; + LZ4_STATIC_ASSERT(sizeof(LZ4_streamDecode_t) >= sizeof(LZ4_streamDecode_t_internal)); + return (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); } int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) { - if (!LZ4_stream) return 0; /* support free on NULL */ + if (LZ4_stream == NULL) { return 0; } /* support free on NULL */ FREEMEM(LZ4_stream); return 0; } +#endif -/*! - * LZ4_setStreamDecode() : - * Use this function to instruct where to find the dictionary. - * This function is not necessary if previous data is still available where it was decoded. - * Loading a size of 0 is allowed (same effect as no dictionary). - * Return : 1 if OK, 0 if error +/*! LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * @return : 1 if OK, 0 if error */ int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) { LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; - lz4sd->prefixSize = (size_t) dictSize; - lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + lz4sd->prefixSize = (size_t)dictSize; + if (dictSize) { + assert(dictionary != NULL); + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + } else { + lz4sd->prefixEnd = (const BYTE*) dictionary; + } lz4sd->externalDict = NULL; lz4sd->extDictSize = 0; return 1; } +/*! LZ4_decoderRingBufferSize() : + * when setting a ring buffer for streaming decompression (optional scenario), + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * Note : in a ring buffer scenario, + * blocks are presumed decompressed next to each other. + * When not enough space remains for next block (remainingSize < maxBlockSize), + * decoding resumes from beginning of ring buffer. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +int LZ4_decoderRingBufferSize(int maxBlockSize) +{ + if (maxBlockSize < 0) return 0; + if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0; + if (maxBlockSize < 16) maxBlockSize = 16; + return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize); +} + /* *_continue() : These decoding functions allow decompression of multiple blocks in "streaming" mode. @@ -1402,54 +2520,81 @@ int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dicti If it's not possible, save the relevant part of decoded data into a safe buffer, and indicate where it stands using LZ4_setStreamDecode() */ -LZ4_FORCE_O2_GCC_PPC64LE +LZ4_FORCE_O2 int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) { LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; int result; - if (lz4sd->prefixEnd == (BYTE*)dest) { - result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - endOnInputSize, full, 0, - usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (lz4sd->prefixSize == 0) { + /* The first call, no dictionary yet. */ + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); if (result <= 0) return result; - lz4sd->prefixSize += result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + /* They're rolling the current segment. */ + if (lz4sd->prefixSize >= 64 KB - 1) + result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + else if (lz4sd->extDictSize == 0) + result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize); + else + result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)result; lz4sd->prefixEnd += result; } else { + /* The buffer wraps around, or they're switching to another buffer. */ lz4sd->extDictSize = lz4sd->prefixSize; lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; - result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - endOnInputSize, full, 0, - usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize); + result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, + lz4sd->externalDict, lz4sd->extDictSize); if (result <= 0) return result; - lz4sd->prefixSize = result; + lz4sd->prefixSize = (size_t)result; lz4sd->prefixEnd = (BYTE*)dest + result; } return result; } -LZ4_FORCE_O2_GCC_PPC64LE -int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize) +LZ4_FORCE_O2 int +LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, + const char* source, char* dest, int originalSize) { - LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + LZ4_streamDecode_t_internal* const lz4sd = + (assert(LZ4_streamDecode!=NULL), &LZ4_streamDecode->internal_donotuse); int result; - if (lz4sd->prefixEnd == (BYTE*)dest) { - result = LZ4_decompress_generic(source, dest, 0, originalSize, - endOnOutputSize, full, 0, - usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + DEBUGLOG(5, "LZ4_decompress_fast_continue (toDecodeSize=%i)", originalSize); + assert(originalSize >= 0); + + if (lz4sd->prefixSize == 0) { + DEBUGLOG(5, "first invocation : no prefix nor extDict"); + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_fast(source, dest, originalSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + DEBUGLOG(5, "continue using existing prefix"); + result = LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + lz4sd->prefixSize, + lz4sd->externalDict, lz4sd->extDictSize); if (result <= 0) return result; - lz4sd->prefixSize += originalSize; + lz4sd->prefixSize += (size_t)originalSize; lz4sd->prefixEnd += originalSize; } else { + DEBUGLOG(5, "prefix becomes extDict"); lz4sd->extDictSize = lz4sd->prefixSize; lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; - result = LZ4_decompress_generic(source, dest, 0, originalSize, - endOnOutputSize, full, 0, - usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize); + result = LZ4_decompress_fast_extDict(source, dest, originalSize, + lz4sd->externalDict, lz4sd->extDictSize); if (result <= 0) return result; - lz4sd->prefixSize = originalSize; + lz4sd->prefixSize = (size_t)originalSize; lz4sd->prefixEnd = (BYTE*)dest + originalSize; } @@ -1464,36 +2609,44 @@ Advanced decoding functions : the dictionary must be explicitly provided within parameters */ -LZ4_FORCE_O2_GCC_PPC64LE -LZ4_FORCE_INLINE int LZ4_decompress_usingDict_generic(const char* source, char* dest, int compressedSize, int maxOutputSize, int safe, const char* dictStart, int dictSize) +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) { if (dictSize==0) - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest, NULL, 0); + return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); if (dictStart+dictSize == dest) { - if (dictSize >= (int)(64 KB - 1)) - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, withPrefix64k, (BYTE*)dest-64 KB, NULL, 0); - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest-dictSize, NULL, 0); + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize); } - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); + assert(dictSize >= 0); + return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize); } -LZ4_FORCE_O2_GCC_PPC64LE -int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +int LZ4_decompress_safe_partial_usingDict(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, const char* dictStart, int dictSize) { - return LZ4_decompress_usingDict_generic(source, dest, compressedSize, maxOutputSize, 1, dictStart, dictSize); + if (dictSize==0) + return LZ4_decompress_safe_partial(source, dest, compressedSize, targetOutputSize, dstCapacity); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_partial_withPrefix64k(source, dest, compressedSize, targetOutputSize, dstCapacity); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_partial_withSmallPrefix(source, dest, compressedSize, targetOutputSize, dstCapacity, (size_t)dictSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_partial_forceExtDict(source, dest, compressedSize, targetOutputSize, dstCapacity, dictStart, (size_t)dictSize); } -LZ4_FORCE_O2_GCC_PPC64LE int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) { - return LZ4_decompress_usingDict_generic(source, dest, 0, originalSize, 0, dictStart, dictSize); -} - -/* debug function */ -LZ4_FORCE_O2_GCC_PPC64LE -int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) -{ - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); + if (dictSize==0 || dictStart+dictSize == dest) + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + (size_t)dictSize, NULL, 0); + assert(dictSize >= 0); + return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize); } @@ -1501,64 +2654,69 @@ int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, int compres * Obsolete Functions ***************************************************/ /* obsolete compression functions */ -int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) { return LZ4_compress_default(source, dest, inputSize, maxOutputSize); } -int LZ4_compress(const char* source, char* dest, int inputSize) { return LZ4_compress_default(source, dest, inputSize, LZ4_compressBound(inputSize)); } -int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); } -int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); } -int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, maxDstSize, 1); } -int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) { return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); } +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_default(source, dest, inputSize, maxOutputSize); +} +int LZ4_compress(const char* src, char* dest, int srcSize) +{ + return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize)); +} +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); +} +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); +} +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity) +{ + return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1); +} +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) +{ + return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); +} /* -These function names are deprecated and should no longer be used. +These decompression functions are deprecated and should no longer be used. They are only provided here for compatibility with older user programs. - LZ4_uncompress is totally equivalent to LZ4_decompress_fast - LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe */ -int LZ4_uncompress (const char* source, char* dest, int outputSize) { return LZ4_decompress_fast(source, dest, outputSize); } -int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) { return LZ4_decompress_safe(source, dest, isize, maxOutputSize); } - +int LZ4_uncompress (const char* source, char* dest, int outputSize) +{ + return LZ4_decompress_fast(source, dest, outputSize); +} +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) +{ + return LZ4_decompress_safe(source, dest, isize, maxOutputSize); +} /* Obsolete Streaming functions */ -int LZ4_sizeofStreamState() { return LZ4_STREAMSIZE; } - -static void LZ4_init(LZ4_stream_t* lz4ds, BYTE* base) -{ - MEM_INIT(lz4ds, 0, sizeof(LZ4_stream_t)); - lz4ds->internal_donotuse.bufferStart = base; -} +int LZ4_sizeofStreamState(void) { return sizeof(LZ4_stream_t); } int LZ4_resetStreamState(void* state, char* inputBuffer) { - if ((((uptrval)state) & 3) != 0) return 1; /* Error : pointer is not aligned on 4-bytes boundary */ - LZ4_init((LZ4_stream_t*)state, (BYTE*)inputBuffer); + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); return 0; } +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) void* LZ4_create (char* inputBuffer) { - LZ4_stream_t* lz4ds = (LZ4_stream_t*)ALLOCATOR(8, sizeof(LZ4_stream_t)); - LZ4_init (lz4ds, (BYTE*)inputBuffer); - return lz4ds; -} - -char* LZ4_slideInputBuffer (void* LZ4_Data) -{ - LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)LZ4_Data)->internal_donotuse; - int dictSize = LZ4_saveDict((LZ4_stream_t*)LZ4_Data, (char*)ctx->bufferStart, 64 KB); - return (char*)(ctx->bufferStart + dictSize); -} - -/* Obsolete streaming decompression functions */ - -int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) -{ - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB); + (void)inputBuffer; + return LZ4_createStream(); } +#endif -int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +char* LZ4_slideInputBuffer (void* state) { - return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB); + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; } #endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/lz4libs/lz4.h b/lz4libs/lz4.h index a06b8a46..491c6087 100644 --- a/lz4libs/lz4.h +++ b/lz4libs/lz4.h @@ -1,7 +1,7 @@ /* * LZ4 - Fast LZ compression algorithm * Header File - * Copyright (C) 2011-2017, Yann Collet. + * Copyright (C) 2011-2020, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) @@ -46,24 +46,31 @@ extern "C" { /** Introduction - LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core, + LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, scalable with multi-cores CPU. It features an extremely fast decoder, with speed in multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. Compression can be done in: - a single step (described as Simple Functions) - a single step, reusing a context (described in Advanced Functions) - unbounded multiple steps (described as Streaming compression) - lz4.h provides block compression functions. It gives full buffer control to user. - Decompressing an lz4-compressed block also requires metadata (such as compressed size). - Each application is free to encode such metadata in whichever way it wants. + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing such a compressed block requires additional metadata. + Exact metadata depends on exact decompression function. + For the typical case of LZ4_decompress_safe(), + metadata includes block's compressed size, and maximum bound of decompressed size. + Each application is free to encode and pass such metadata in whichever way it wants. - An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md), - take care of encoding standard metadata alongside LZ4-compressed blocks. - If your application requires interoperability, it's recommended to use it. - A library is provided to take care of it, see lz4frame.h. + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + Embedding metadata is required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + The `lz4` CLI can only manage frames. */ /*^*************************************************************** @@ -90,63 +97,111 @@ extern "C" { # define LZ4LIB_API LZ4LIB_VISIBILITY #endif +/*! LZ4_FREESTANDING : + * When this macro is set to 1, it enables "freestanding mode" that is + * suitable for typical freestanding environment which doesn't support + * standard C library. + * + * - LZ4_FREESTANDING is a compile-time switch. + * - It requires the following macros to be defined: + * LZ4_memcpy, LZ4_memmove, LZ4_memset. + * - It only enables LZ4/HC functions which don't use heap. + * All LZ4F_* functions are not supported. + * - See tests/freestanding.c to check its basic setup. + */ +#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1) +# define LZ4_HEAPMODE 0 +# define LZ4HC_HEAPMODE 0 +# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1 +# if !defined(LZ4_memcpy) +# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'." +# endif +# if !defined(LZ4_memset) +# error "LZ4_FREESTANDING requires macro 'LZ4_memset'." +# endif +# if !defined(LZ4_memmove) +# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'." +# endif +#elif ! defined(LZ4_FREESTANDING) +# define LZ4_FREESTANDING 0 +#endif + + /*------ Version ------*/ #define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ -#define LZ4_VERSION_MINOR 8 /* for new (non-breaking) interface capabilities */ -#define LZ4_VERSION_RELEASE 1 /* for tweaks, bug-fixes, or development */ +#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 4 /* for tweaks, bug-fixes, or development */ #define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) #define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE #define LZ4_QUOTE(str) #str #define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) -#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */ -LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; to be used when checking dll version */ -LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; to be used when checking dll version */ +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */ /*-************************************ * Tuning parameter **************************************/ +#define LZ4_MEMORY_USAGE_MIN 10 +#define LZ4_MEMORY_USAGE_DEFAULT 14 +#define LZ4_MEMORY_USAGE_MAX 20 + /*! * LZ4_MEMORY_USAGE : - * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) - * Increasing memory usage improves compression ratio - * Reduced memory usage can improve speed, due to cache effect + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; ) + * Increasing memory usage improves compression ratio, at the cost of speed. + * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality. * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache */ #ifndef LZ4_MEMORY_USAGE -# define LZ4_MEMORY_USAGE 14 +# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT +#endif + +#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN) +# error "LZ4_MEMORY_USAGE is too small !" +#endif + +#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX) +# error "LZ4_MEMORY_USAGE is too large !" #endif /*-************************************ * Simple Functions **************************************/ /*! LZ4_compress_default() : - Compresses 'srcSize' bytes from buffer 'src' - into already allocated 'dst' buffer of size 'dstCapacity'. - Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). - It also runs faster, so it's a recommended setting. - If the function cannot compress 'src' into a limited 'dst' budget, - compression stops *immediately*, and the function result is zero. - As a consequence, 'dst' content is not valid. - This function never writes outside 'dst' buffer, nor read outside 'source' buffer. - srcSize : supported max value is LZ4_MAX_INPUT_VALUE - dstCapacity : full or partial size of buffer 'dst' (which must be already allocated) - return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) - or 0 if compression fails */ + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); /*! LZ4_decompress_safe() : - compressedSize : is the exact complete size of the compressed block. - dstCapacity : is the size of destination buffer, which must be already allocated. - return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) - If destination buffer is not large enough, decoding will stop and output an error code (negative value). - If the source stream is detected malformed, the function will stop decoding and return a negative result. - This function is protected against buffer overflow exploits, including malicious data packets. - It never writes outside output buffer, nor reads outside input buffer. -*/ + * compressedSize : is the exact complete size of the compressed block. + * dstCapacity : is the size of destination buffer (which must be already allocated), presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); @@ -156,322 +211,630 @@ LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSi #define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ #define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) -/*! -LZ4_compressBound() : +/*! LZ4_compressBound() : Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) This function is primarily useful for memory allocation purposes (destination buffer size). Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). - Note that LZ4_compress_default() compress faster when dest buffer size is >= LZ4_compressBound(srcSize) + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) inputSize : max supported value is LZ4_MAX_INPUT_SIZE return : maximum output size in a "worst case" scenario - or 0, if input size is too large ( > LZ4_MAX_INPUT_SIZE) + or 0, if input size is incorrect (too large or negative) */ LZ4LIB_API int LZ4_compressBound(int inputSize); -/*! -LZ4_compress_fast() : - Same as LZ4_compress_default(), but allows to select an "acceleration" factor. +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. The larger the acceleration value, the faster the algorithm, but also the lesser the compression. It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. An acceleration value of "1" is the same as regular LZ4_compress_default() - Values <= 0 will be replaced by ACCELERATION_DEFAULT (see lz4.c), which is 1. + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). */ LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); -/*! -LZ4_compress_fast_extState() : - Same compression function, just using an externally allocated memory space to store compression state. - Use LZ4_sizeofState() to know how much memory must be allocated, - and allocate it on 8-bytes boundaries (using malloc() typically). - Then, provide it as 'void* state' to compression function. -*/ +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ LZ4LIB_API int LZ4_sizeofState(void); LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); -/*! -LZ4_compress_destSize() : - Reverse the logic : compresses as much data as possible from 'src' buffer - into already allocated buffer 'dst' of size 'targetDestSize'. - This function either compresses the entire 'src' content into 'dst' if it's large enough, - or fill 'dst' buffer completely with as much data as possible from 'src'. - *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. - New value is necessarily <= old value. - return : Nb bytes written into 'dst' (necessarily <= targetDestSize) - or 0 if compression fails -*/ +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'targetDestSize'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed un v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); -/*! -LZ4_decompress_fast() : (unsafe!!) - originalSize : is the original uncompressed size - return : the number of bytes read from the source buffer (in other words, the compressed size) - If the source stream is detected malformed, the function will stop decoding and return a negative result. - Destination buffer must be already allocated. Its size must be >= 'originalSize' bytes. - note : This function respects memory boundaries for *properly formed* compressed data. - It is a bit faster than LZ4_decompress_safe(). - However, it does not provide any protection against intentionally modified data stream (malicious input). - Use this function in trusted environment only (data to decode comes from a trusted source). -*/ -LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); - -/*! -LZ4_decompress_safe_partial() : - This function decompress a compressed block of size 'srcSize' at position 'src' - into destination buffer 'dst' of size 'dstCapacity'. - The function will decompress a minimum of 'targetOutputSize' bytes, and stop after that. - However, it's not accurate, and may write more than 'targetOutputSize' (but <= dstCapacity). - @return : the number of bytes decoded in the destination buffer (necessarily <= dstCapacity) - Note : this number can be < 'targetOutputSize' should the compressed block contain less data. - Always control how many bytes were decoded. - If the source stream is detected malformed, the function will stop decoding and return a negative result. - This function never writes outside of output buffer, and never reads outside of input buffer. It is therefore protected against malicious data packets. -*/ +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); /*-********************************************* * Streaming Compression Functions ***********************************************/ -typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ -/*! LZ4_createStream() and LZ4_freeStream() : - * LZ4_createStream() will allocate and initialize an `LZ4_stream_t` structure. - * LZ4_freeStream() releases its memory. - */ +/** + Note about RC_INVOKED + + - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio). + https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros + + - Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars) + and reports warning "RC4011: identifier truncated". + + - To eliminate the warning, we surround long preprocessor symbol with + "#if !defined(RC_INVOKED) ... #endif" block that means + "skip this block when rc.exe is trying to read it". +*/ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif -/*! LZ4_resetStream() : - * An LZ4_stream_t structure can be allocated once and re-used multiple times. - * Use this function to start compressing a new stream. +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. */ -LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); /*! LZ4_loadDict() : - * Use this function to load a static dictionary into LZ4_stream_t. - * Any previous data will be forgotten, only 'dictionary' will remain in memory. + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 accept any input as dictionary, + * results are generally better when using Zstandard's Dictionary Builder. * Loading a size of 0 is allowed, and is the same as reset. - * @return : dictionary size, in bytes (necessarily <= 64 KB) + * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) */ LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); /*! LZ4_compress_fast_continue() : - * Compress content into 'src' using data from previously compressed blocks, improving compression ratio. - * 'dst' buffer must be already allocated. + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. * - * Important : Up to 64KB of previously compressed data is assumed to remain present and unmodified in memory ! - * Special 1 : If input buffer is a double-buffer, it can have any size, including < 64 KB. - * Special 2 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. - * * @return : size of compressed block - * or 0 if there is an error (typically, compressed data cannot fit into 'dst') - * After an error, the stream status is invalid, it can only be reset or freed. + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. */ LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); /*! LZ4_saveDict() : - * If previously compressed data block is not guaranteed to remain available at its current memory location, + * If last 64KB data cannot be guaranteed to remain available at its current memory location, * save it into a safer place (char* safeBuffer). - * Note : it's not necessary to call LZ4_loadDict() after LZ4_saveDict(), dictionary is immediately usable. - * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. */ -LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int dictSize); +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); /*-********************************************** * Streaming Decompression Functions * Bufferless synchronous API ************************************************/ -typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* incomplete type (defined later) */ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ /*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : - * creation / destruction of streaming decompression tracking structure. - * A tracking structure can be re-used multiple times sequentially. */ + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif /*! LZ4_setStreamDecode() : - * An LZ4_streamDecode_t structure can be allocated once and re-used multiple times. + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. * Use this function to start decompression of a new stream of blocks. - * A dictionary can optionnally be set. Use NULL or size 0 for a simple reset order. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. * @return : 1 if OK, 0 if error */ LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + /*! LZ4_decompress_*_continue() : * These decoding functions allow decompression of consecutive blocks in "streaming" mode. * A block is an unsplittable entity, it must be presented entirely to a decompression function. - * Decompression functions only accept one block at a time. - * Previously decoded blocks *must* remain available at the memory position where they were decoded (up to 64 KB). - * - * Special : if application sets a ring buffer for decompression, it must respect one of the following conditions : - * - Exactly same size as encoding buffer, with same update rule (block boundaries at same positions) - * In which case, the decoding & encoding ring buffer can have any size, including very small ones ( < 64 KB). - * - Larger than encoding buffer, by a minimum of maxBlockSize more bytes. - * maxBlockSize is implementation dependent. It's the maximum size of any single block. + * Decompression functions only accepts one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. * In which case, encoding and decoding buffers do not need to be synchronized, * and encoding ring buffer can have any size, including small ones ( < 64 KB). - * - _At least_ 64 KB + 8 bytes + maxBlockSize. - * In which case, encoding and decoding buffers do not need to be synchronized, - * and encoding ring buffer can have any size, including larger than decoding buffer. - * Whenever these conditions are not possible, save the last 64KB of decoded data into a safe buffer, - * and indicate where it is saved using LZ4_setStreamDecode() before decompressing next block. + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. */ -LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity); -LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4LIB_API int +LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, + const char* src, char* dst, + int srcSize, int dstCapacity); /*! LZ4_decompress_*_usingDict() : * These decoding functions work the same as * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. */ -LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize); -LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); +LZ4LIB_API int +LZ4_decompress_safe_usingDict(const char* src, char* dst, + int srcSize, int dstCapacity, + const char* dictStart, int dictSize); +LZ4LIB_API int +LZ4_decompress_safe_partial_usingDict(const char* src, char* dst, + int compressedSize, + int targetOutputSize, int maxOutputSize, + const char* dictStart, int dictSize); + +#endif /* LZ4_H_2983827168210 */ -/*^********************************************** + +/*^************************************* * !!!!!! STATIC LINKING ONLY !!!!!! - ***********************************************/ -/*-************************************ - * Private definitions - ************************************** - * Do not use these definitions. - * They are exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. - * Using these definitions will expose code to API and/or ABI break in future versions of the library. - **************************************/ -#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) -#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) -#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + ***************************************/ -#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -#include +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ -typedef struct { - uint32_t hashTable[LZ4_HASH_SIZE_U32]; - uint32_t currentOffset; - uint32_t initCheck; - const uint8_t* dictionary; - uint8_t* bufferStart; /* obsolete, used for slideInputBuffer */ - uint32_t dictSize; -} LZ4_stream_t_internal; +#ifdef LZ4_STATIC_LINKING_ONLY -typedef struct { - const uint8_t* externalDict; - size_t extDictSize; - const uint8_t* prefixEnd; - size_t prefixSize; -} LZ4_streamDecode_t_internal; +#ifndef LZ4_STATIC_3504398509 +#define LZ4_STATIC_3504398509 +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +#define LZ4LIB_STATIC_API LZ4LIB_API #else +#define LZ4LIB_STATIC_API +#endif -typedef struct { - unsigned int hashTable[LZ4_HASH_SIZE_U32]; - unsigned int currentOffset; - unsigned int initCheck; - const unsigned char* dictionary; - unsigned char* bufferStart; /* obsolete, used for slideInputBuffer */ - unsigned int dictSize; -} LZ4_stream_t_internal; -typedef struct { - const unsigned char* externalDict; - size_t extDictSize; - const unsigned char* prefixEnd; - size_t prefixSize; -} LZ4_streamDecode_t_internal; +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); -#endif +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows + * efficient use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_STATIC_API void +LZ4_attach_dictionary(LZ4_stream_t* workingStream, + const LZ4_stream_t* dictionaryStream); -/*! - * LZ4_stream_t : - * information structure to track an LZ4 stream. - * init this structure before first use. - * note : only use in association with static linking ! - * this definition is not API/ABI safe, - * it may change in a future version ! + +/*! In-place compression and decompression + * + * It's possible to have input and output sharing the same buffer, + * for highly constrained memory environments. + * In both cases, it requires input to lay at the end of the buffer, + * and decompression to start at beginning of the buffer. + * Buffer size must feature some margin, hence be larger than final size. + * + * |<------------------------buffer--------------------------------->| + * |<-----------compressed data--------->| + * |<-----------decompressed size------------------>| + * |<----margin---->| + * + * This technique is more useful for decompression, + * since decompressed size is typically larger, + * and margin is short. + * + * In-place decompression will work inside any buffer + * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). + * This presumes that decompressedSize > compressedSize. + * Otherwise, it means compression actually expanded data, + * and it would be more efficient to store such data with a flag indicating it's not compressed. + * This can happen when data is not compressible (already compressed, or encrypted). + * + * For in-place compression, margin is larger, as it must be able to cope with both + * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, + * and data expansion, which can happen when input is not compressible. + * As a consequence, buffer size requirements are much higher, + * and memory savings offered by in-place compression are more limited. + * + * There are ways to limit this cost for compression : + * - Reduce history size, by modifying LZ4_DISTANCE_MAX. + * Note that it is a compile-time constant, so all compressions will apply this limit. + * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, + * so it's a reasonable trick when inputs are known to be small. + * - Require the compressor to deliver a "maximum compressed size". + * This is the `dstCapacity` parameter in `LZ4_compress*()`. + * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, + * in which case, the return code will be 0 (zero). + * The caller must be ready for these cases to happen, + * and typically design a backup scheme to send data uncompressed. + * The combination of both techniques can significantly reduce + * the amount of margin required for in-place compression. + * + * In-place compression can work in any buffer + * which size is >= (maxCompressedSize) + * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. + * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, + * so it's possible to reduce memory requirements by playing with them. */ -#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4) -#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) + +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ + +#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ +# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ +#endif + +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ + +#endif /* LZ4_STATIC_3504398509 */ +#endif /* LZ4_STATIC_LINKING_ONLY */ + + + +#ifndef LZ4_H_98237428734687 +#define LZ4_H_98237428734687 + +/*-************************************************************ + * Private Definitions + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose user code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef int8_t LZ4_i8; + typedef uint8_t LZ4_byte; + typedef uint16_t LZ4_u16; + typedef uint32_t LZ4_u32; +#else + typedef signed char LZ4_i8; + typedef unsigned char LZ4_byte; + typedef unsigned short LZ4_u16; + typedef unsigned int LZ4_u32; +#endif + +/*! LZ4_stream_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_stream_t object. +**/ + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; + const LZ4_byte* dictionary; + const LZ4_stream_t_internal* dictCtx; + LZ4_u32 currentOffset; + LZ4_u32 tableType; + LZ4_u32 dictSize; + /* Implicit padding to ensure structure is aligned */ +}; + +#define LZ4_STREAM_MINSIZE ((1UL << LZ4_MEMORY_USAGE) + 32) /* static size, for inter-version compatibility */ union LZ4_stream_u { - unsigned long long table[LZ4_STREAMSIZE_U64]; + char minStateSize[LZ4_STREAM_MINSIZE]; LZ4_stream_t_internal internal_donotuse; -} ; /* previously typedef'd to LZ4_stream_t */ +}; /* previously typedef'd to LZ4_stream_t */ -/*! - * LZ4_streamDecode_t : - * information structure to track an LZ4 stream during decompression. - * init this structure using LZ4_setStreamDecode (or memset()) before first use - * note : only use in association with static linking ! - * this definition is not API/ABI safe, - * and may change in a future version ! - */ -#define LZ4_STREAMDECODESIZE_U64 4 -#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead +**/ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); + + +/*! LZ4_streamDecode_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_streamDecode_t object. +**/ +typedef struct { + const LZ4_byte* externalDict; + const LZ4_byte* prefixEnd; + size_t extDictSize; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#define LZ4_STREAMDECODE_MINSIZE 32 union LZ4_streamDecode_u { - unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + char minStateSize[LZ4_STREAMDECODE_MINSIZE]; LZ4_streamDecode_t_internal internal_donotuse; } ; /* previously typedef'd to LZ4_streamDecode_t */ + /*-************************************ * Obsolete Functions **************************************/ /*! Deprecation warnings - Should deprecation warnings be a problem, - it is generally possible to disable them, - typically with -Wno-deprecated-declarations for gcc - or _CRT_SECURE_NO_WARNINGS in Visual. - Otherwise, it's also possible to define LZ4_DISABLE_DEPRECATE_WARNINGS */ + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ #ifdef LZ4_DISABLE_DEPRECATE_WARNINGS # define LZ4_DEPRECATED(message) /* disable deprecation warnings */ #else -# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -# if defined(__clang__) /* clang doesn't handle mixed C++11 and CNU attributes */ -# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) -# elif defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ # define LZ4_DEPRECATED(message) [[deprecated(message)]] -# elif (LZ4_GCC_VERSION >= 405) -# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) -# elif (LZ4_GCC_VERSION >= 301) -# define LZ4_DEPRECATED(message) __attribute__((deprecated)) # elif defined(_MSC_VER) # define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45)) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) # else -# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") -# define LZ4_DEPRECATED(message) +# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler") +# define LZ4_DEPRECATED(message) /* disabled */ # endif #endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ -/* Obsolete compression functions */ -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_default() instead") int LZ4_compress (const char* source, char* dest, int sourceSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_default() instead") int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); +/*! Obsolete compression functions (since v1.7.3) */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); -/* Obsolete decompression functions */ -LZ4LIB_API LZ4_DEPRECATED("use LZ4_decompress_fast() instead") int LZ4_uncompress (const char* source, char* dest, int outputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_decompress_safe() instead") int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); +/*! Obsolete decompression functions (since v1.8.0) */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); -/* Obsolete streaming functions; use new streaming interface whenever possible */ -LZ4LIB_API LZ4_DEPRECATED("use LZ4_createStream() instead") void* LZ4_create (char* inputBuffer); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_createStream() instead") int LZ4_sizeofStreamState(void); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_resetStream() instead") int LZ4_resetStreamState(void* state, char* inputBuffer); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_saveDict() instead") char* LZ4_slideInputBuffer (void* state); +/* Obsolete streaming functions (since v1.7.0) + * degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/*! Obsolete streaming decoding functions (since v1.7.0) */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) : + * These functions used to be faster than LZ4_decompress_safe(), + * but this is no longer the case. They are now slower. + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously into the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality can be achieved in a more secure manner + * by employing LZ4_decompress_safe_partial(). + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); -/* Obsolete streaming decoding functions */ -LZ4LIB_API LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); -#endif /* LZ4_H_2983827168210 */ + +#endif /* LZ4_H_98237428734687 */ #if defined (__cplusplus) diff --git a/lz4libs/lz4frame.c b/lz4libs/lz4frame.c index 0b26f75a..174f9ae4 100644 --- a/lz4libs/lz4frame.c +++ b/lz4libs/lz4frame.c @@ -1,65 +1,73 @@ /* -LZ4 auto-framing library -Copyright (C) 2011-2016, Yann Collet. - -BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -You can contact the author at : -- LZ4 homepage : http://www.lz4.org -- LZ4 source repository : https://github.com/lz4/lz4 -*/ + * LZ4 auto-framing library + * Copyright (C) 2011-2016, Yann Collet. + * + * BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You can contact the author at : + * - LZ4 homepage : http://www.lz4.org + * - LZ4 source repository : https://github.com/lz4/lz4 + */ /* LZ4F is a stand-alone API to create LZ4-compressed Frames -* in full conformance with specification v1.5.0 -* All related operations, including memory management, are handled by the library. -* */ + * in full conformance with specification v1.6.1 . + * This library rely upon memory management capabilities (malloc, free) + * provided either by , + * or redirected towards another library of user's choice + * (see Memory Routines below). + */ /*-************************************ * Compiler Options **************************************/ #ifdef _MSC_VER /* Visual Studio */ -# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ #endif /*-************************************ -* Memory routines +* Tuning parameters **************************************/ -#include /* malloc, calloc, free */ -#define ALLOCATOR(s) calloc(1,s) -#define FREEMEM free -#include /* memset, memcpy, memmove */ -#define MEM_INIT memset +/* + * LZ4F_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4F_HEAPMODE +# define LZ4F_HEAPMODE 0 +#endif /*-************************************ -* Includes +* Library declarations **************************************/ -#include "lz4frame_static.h" +#define LZ4F_STATIC_LINKING_ONLY +#include "lz4frame.h" +#define LZ4_STATIC_LINKING_ONLY #include "lz4.h" #define LZ4_HC_STATIC_LINKING_ONLY #include "lz4hc.h" @@ -67,6 +75,66 @@ You can contact the author at : #include "xxhash.h" +/*-************************************ +* Memory routines +**************************************/ +/* + * User may redirect invocations of + * malloc(), calloc() and free() + * towards another library or solution of their choice + * by modifying below section. +**/ + +#include /* memset, memcpy, memmove */ +#ifndef LZ4_SRC_INCLUDED /* avoid redefinition when sources are coalesced */ +# define MEM_INIT(p,v,s) memset((p),(v),(s)) +#endif + +#ifndef LZ4_SRC_INCLUDED /* avoid redefinition when sources are coalesced */ +# include /* malloc, calloc, free */ +# define ALLOC(s) malloc(s) +# define ALLOC_AND_ZERO(s) calloc(1,(s)) +# define FREEMEM(p) free(p) +#endif + +static void* LZ4F_calloc(size_t s, LZ4F_CustomMem cmem) +{ + /* custom calloc defined : use it */ + if (cmem.customCalloc != NULL) { + return cmem.customCalloc(cmem.opaqueState, s); + } + /* nothing defined : use default 's calloc() */ + if (cmem.customAlloc == NULL) { + return ALLOC_AND_ZERO(s); + } + /* only custom alloc defined : use it, and combine it with memset() */ + { void* const p = cmem.customAlloc(cmem.opaqueState, s); + if (p != NULL) MEM_INIT(p, 0, s); + return p; +} } + +static void* LZ4F_malloc(size_t s, LZ4F_CustomMem cmem) +{ + /* custom malloc defined : use it */ + if (cmem.customAlloc != NULL) { + return cmem.customAlloc(cmem.opaqueState, s); + } + /* nothing defined : use default 's malloc() */ + return ALLOC(s); +} + +static void LZ4F_free(void* p, LZ4F_CustomMem cmem) +{ + /* custom malloc defined : use it */ + if (cmem.customFree != NULL) { + cmem.customFree(cmem.opaqueState, p); + return; + } + /* nothing defined : use default 's free() */ + FREEMEM(p); +} + + /*-************************************ * Debug **************************************/ @@ -80,6 +148,19 @@ You can contact the author at : #define LZ4F_STATIC_ASSERT(c) { enum { LZ4F_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) && !defined(DEBUGLOG) +# include +static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + /*-************************************ * Basic Types @@ -100,13 +181,13 @@ You can contact the author at : #endif -/* unoptimized version; solves endianess & alignment issues */ +/* unoptimized version; solves endianness & alignment issues */ static U32 LZ4F_readLE32 (const void* src) { const BYTE* const srcPtr = (const BYTE*)src; U32 value32 = srcPtr[0]; - value32 += (srcPtr[1]<<8); - value32 += (srcPtr[2]<<16); + value32 += ((U32)srcPtr[1])<< 8; + value32 += ((U32)srcPtr[2])<<16; value32 += ((U32)srcPtr[3])<<24; return value32; } @@ -151,9 +232,11 @@ static void LZ4F_writeLE64 (void* dst, U64 value64) /*-************************************ * Constants **************************************/ -#define KB *(1<<10) -#define MB *(1<<20) -#define GB *(1<<30) +#ifndef LZ4_SRC_INCLUDED /* avoid double definition */ +# define KB *(1<<10) +# define MB *(1<<20) +# define GB *(1<<30) +#endif #define _1BIT 0x01 #define _2BITS 0x03 @@ -161,34 +244,39 @@ static void LZ4F_writeLE64 (void* dst, U64 value64) #define _4BITS 0x0F #define _8BITS 0xFF -#define LZ4F_MAGIC_SKIPPABLE_START 0x184D2A50U -#define LZ4F_MAGICNUMBER 0x184D2204U #define LZ4F_BLOCKUNCOMPRESSED_FLAG 0x80000000U #define LZ4F_BLOCKSIZEID_DEFAULT LZ4F_max64KB -static const size_t minFHSize = 7; +static const size_t minFHSize = LZ4F_HEADER_SIZE_MIN; /* 7 */ static const size_t maxFHSize = LZ4F_HEADER_SIZE_MAX; /* 19 */ -static const size_t BHSize = 4; +static const size_t BHSize = LZ4F_BLOCK_HEADER_SIZE; /* block header : size, and compress flag */ +static const size_t BFSize = LZ4F_BLOCK_CHECKSUM_SIZE; /* block footer : checksum (optional) */ /*-************************************ * Structures and local types **************************************/ + +typedef enum { LZ4B_COMPRESSED, LZ4B_UNCOMPRESSED} LZ4F_blockCompression_t; + typedef struct LZ4F_cctx_s { + LZ4F_CustomMem cmem; LZ4F_preferences_t prefs; U32 version; U32 cStage; const LZ4F_CDict* cdict; size_t maxBlockSize; size_t maxBufferSize; - BYTE* tmpBuff; - BYTE* tmpIn; - size_t tmpInSize; + BYTE* tmpBuff; /* internal buffer, for streaming */ + BYTE* tmpIn; /* starting position of data compress within internal buffer (>= tmpBuff) */ + size_t tmpInSize; /* amount of data to compress after tmpIn */ U64 totalInSize; XXH32_state_t xxh; void* lz4CtxPtr; - U32 lz4CtxLevel; /* 0: unallocated; 1: LZ4_stream_t; 3: LZ4_streamHC_t */ + U16 lz4CtxAlloc; /* sized for: 0 = none, 1 = lz4 ctx, 2 = lz4hc ctx */ + U16 lz4CtxState; /* in use as: 0 = none, 1 = lz4 ctx, 2 = lz4hc ctx */ + LZ4F_blockCompression_t blockCompression; } LZ4F_cctx_t; @@ -217,33 +305,39 @@ LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult) return (LZ4F_errorCodes)(-(ptrdiff_t)functionResult); } -static LZ4F_errorCode_t err0r(LZ4F_errorCodes code) +static LZ4F_errorCode_t LZ4F_returnErrorCode(LZ4F_errorCodes code) { /* A compilation error here means sizeof(ptrdiff_t) is not large enough */ LZ4F_STATIC_ASSERT(sizeof(ptrdiff_t) >= sizeof(size_t)); return (LZ4F_errorCode_t)-(ptrdiff_t)code; } +#define RETURN_ERROR(e) return LZ4F_returnErrorCode(LZ4F_ERROR_ ## e) + +#define RETURN_ERROR_IF(c,e) if (c) RETURN_ERROR(e) + +#define FORWARD_IF_ERROR(r) if (LZ4F_isError(r)) return (r) + unsigned LZ4F_getVersion(void) { return LZ4F_VERSION; } int LZ4F_compressionLevel_max(void) { return LZ4HC_CLEVEL_MAX; } +size_t LZ4F_getBlockSize(LZ4F_blockSizeID_t blockSizeID) +{ + static const size_t blockSizes[4] = { 64 KB, 256 KB, 1 MB, 4 MB }; + + if (blockSizeID == 0) blockSizeID = LZ4F_BLOCKSIZEID_DEFAULT; + if (blockSizeID < LZ4F_max64KB || blockSizeID > LZ4F_max4MB) + RETURN_ERROR(maxBlockSize_invalid); + { int const blockSizeIdx = (int)blockSizeID - (int)LZ4F_max64KB; + return blockSizes[blockSizeIdx]; +} } /*-************************************ * Private functions **************************************/ #define MIN(a,b) ( (a) < (b) ? (a) : (b) ) -static size_t LZ4F_getBlockSize(unsigned blockSizeID) -{ - static const size_t blockSizes[4] = { 64 KB, 256 KB, 1 MB, 4 MB }; - - if (blockSizeID == 0) blockSizeID = LZ4F_BLOCKSIZEID_DEFAULT; - blockSizeID -= 4; - if (blockSizeID > 3) return err0r(LZ4F_ERROR_maxBlockSize_invalid); - return blockSizes[blockSizeID]; -} - static BYTE LZ4F_headerChecksum (const void* header, size_t length) { U32 const xxh = XXH32(header, length, 0); @@ -278,9 +372,9 @@ static size_t LZ4F_compressBound_internal(size_t srcSize, const LZ4F_preferences_t* preferencesPtr, size_t alreadyBuffered) { - LZ4F_preferences_t prefsNull; - memset(&prefsNull, 0, sizeof(prefsNull)); + LZ4F_preferences_t prefsNull = LZ4F_INIT_PREFERENCES; prefsNull.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled; /* worst case */ + prefsNull.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled; /* worst case */ { const LZ4F_preferences_t* const prefsPtr = (preferencesPtr==NULL) ? &prefsNull : preferencesPtr; U32 const flush = prefsPtr->autoFlush | (srcSize==0); LZ4F_blockSizeID_t const blockID = prefsPtr->frameInfo.blockSizeID; @@ -293,11 +387,10 @@ static size_t LZ4F_compressBound_internal(size_t srcSize, size_t const lastBlockSize = flush ? partialBlockSize : 0; unsigned const nbBlocks = nbFullBlocks + (lastBlockSize>0); - size_t const blockHeaderSize = 4; - size_t const blockCRCSize = 4 * prefsPtr->frameInfo.blockChecksumFlag; - size_t const frameEnd = 4 + (prefsPtr->frameInfo.contentChecksumFlag*4); + size_t const blockCRCSize = BFSize * prefsPtr->frameInfo.blockChecksumFlag; + size_t const frameEnd = BHSize + (prefsPtr->frameInfo.contentChecksumFlag*BFSize); - return ((blockHeaderSize + blockCRCSize) * nbBlocks) + + return ((BHSize + blockCRCSize) * nbBlocks) + (blockSize * nbFullBlocks) + lastBlockSize + frameEnd; } } @@ -308,7 +401,7 @@ size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* prefere size_t const headerSize = maxFHSize; /* max header size, including optional fields */ if (preferencesPtr!=NULL) prefs = *preferencesPtr; - else memset(&prefs, 0, sizeof(prefs)); + else MEM_INIT(&prefs, 0, sizeof(prefs)); prefs.autoFlush = 1; return headerSize + LZ4F_compressBound_internal(srcSize, &prefs, 0);; @@ -324,27 +417,22 @@ size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* prefere * @return : number of bytes written into dstBuffer, * or an error code if it fails (can be tested using LZ4F_isError()) */ -size_t LZ4F_compressFrame_usingCDict(void* dstBuffer, size_t dstCapacity, +size_t LZ4F_compressFrame_usingCDict(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, const void* srcBuffer, size_t srcSize, const LZ4F_CDict* cdict, const LZ4F_preferences_t* preferencesPtr) { - LZ4F_cctx_t cctxI; - LZ4_stream_t lz4ctx; /* pretty large on stack */ LZ4F_preferences_t prefs; LZ4F_compressOptions_t options; BYTE* const dstStart = (BYTE*) dstBuffer; BYTE* dstPtr = dstStart; BYTE* const dstEnd = dstStart + dstCapacity; - memset(&cctxI, 0, sizeof(cctxI)); - cctxI.version = LZ4F_VERSION; - cctxI.maxBufferSize = 5 MB; /* mess with real buffer size to prevent dynamic allocation; works only because autoflush==1 & stableSrc==1 */ - if (preferencesPtr!=NULL) prefs = *preferencesPtr; else - memset(&prefs, 0, sizeof(prefs)); + MEM_INIT(&prefs, 0, sizeof(prefs)); if (prefs.frameInfo.contentSize != 0) prefs.frameInfo.contentSize = (U64)srcSize; /* auto-correct content size if selected (!=0) */ @@ -353,33 +441,27 @@ size_t LZ4F_compressFrame_usingCDict(void* dstBuffer, size_t dstCapacity, if (srcSize <= LZ4F_getBlockSize(prefs.frameInfo.blockSizeID)) prefs.frameInfo.blockMode = LZ4F_blockIndependent; /* only one block => no need for inter-block link */ - if (prefs.compressionLevel < LZ4HC_CLEVEL_MIN) { - cctxI.lz4CtxPtr = &lz4ctx; - cctxI.lz4CtxLevel = 1; - } /* fast compression context pre-created on stack */ - - memset(&options, 0, sizeof(options)); + MEM_INIT(&options, 0, sizeof(options)); options.stableSrc = 1; - if (dstCapacity < LZ4F_compressFrameBound(srcSize, &prefs)) /* condition to guarantee success */ - return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + RETURN_ERROR_IF(dstCapacity < LZ4F_compressFrameBound(srcSize, &prefs), dstMaxSize_tooSmall); - { size_t const headerSize = LZ4F_compressBegin_usingCDict(&cctxI, dstBuffer, dstCapacity, cdict, &prefs); /* write header */ - if (LZ4F_isError(headerSize)) return headerSize; + { size_t const headerSize = LZ4F_compressBegin_usingCDict(cctx, dstBuffer, dstCapacity, cdict, &prefs); /* write header */ + FORWARD_IF_ERROR(headerSize); dstPtr += headerSize; /* header size */ } - { size_t const cSize = LZ4F_compressUpdate(&cctxI, dstPtr, dstEnd-dstPtr, srcBuffer, srcSize, &options); - if (LZ4F_isError(cSize)) return cSize; + assert(dstEnd >= dstPtr); + { size_t const cSize = LZ4F_compressUpdate(cctx, dstPtr, (size_t)(dstEnd-dstPtr), srcBuffer, srcSize, &options); + FORWARD_IF_ERROR(cSize); dstPtr += cSize; } - { size_t const tailSize = LZ4F_compressEnd(&cctxI, dstPtr, dstEnd-dstPtr, &options); /* flush last block, and generate suffix */ - if (LZ4F_isError(tailSize)) return tailSize; + assert(dstEnd >= dstPtr); + { size_t const tailSize = LZ4F_compressEnd(cctx, dstPtr, (size_t)(dstEnd-dstPtr), &options); /* flush last block, and generate suffix */ + FORWARD_IF_ERROR(tailSize); dstPtr += tailSize; } - if (prefs.compressionLevel >= LZ4HC_CLEVEL_MIN) /* Ctx allocation only for lz4hc */ - FREEMEM(cctxI.lz4CtxPtr); - - return (dstPtr - dstStart); + assert(dstEnd >= dstStart); + return (size_t)(dstPtr - dstStart); } @@ -394,9 +476,42 @@ size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, const void* srcBuffer, size_t srcSize, const LZ4F_preferences_t* preferencesPtr) { - return LZ4F_compressFrame_usingCDict(dstBuffer, dstCapacity, - srcBuffer, srcSize, - NULL, preferencesPtr); + size_t result; +#if (LZ4F_HEAPMODE) + LZ4F_cctx_t* cctxPtr; + result = LZ4F_createCompressionContext(&cctxPtr, LZ4F_VERSION); + FORWARD_IF_ERROR(result); +#else + LZ4F_cctx_t cctx; + LZ4_stream_t lz4ctx; + LZ4F_cctx_t* const cctxPtr = &cctx; + + MEM_INIT(&cctx, 0, sizeof(cctx)); + cctx.version = LZ4F_VERSION; + cctx.maxBufferSize = 5 MB; /* mess with real buffer size to prevent dynamic allocation; works only because autoflush==1 & stableSrc==1 */ + if ( preferencesPtr == NULL + || preferencesPtr->compressionLevel < LZ4HC_CLEVEL_MIN ) { + LZ4_initStream(&lz4ctx, sizeof(lz4ctx)); + cctxPtr->lz4CtxPtr = &lz4ctx; + cctxPtr->lz4CtxAlloc = 1; + cctxPtr->lz4CtxState = 1; + } +#endif + DEBUGLOG(4, "LZ4F_compressFrame"); + + result = LZ4F_compressFrame_usingCDict(cctxPtr, dstBuffer, dstCapacity, + srcBuffer, srcSize, + NULL, preferencesPtr); + +#if (LZ4F_HEAPMODE) + LZ4F_freeCompressionContext(cctxPtr); +#else + if ( preferencesPtr != NULL + && preferencesPtr->compressionLevel >= LZ4HC_CLEVEL_MIN ) { + LZ4F_free(cctxPtr->lz4CtxPtr, cctxPtr->cmem); + } +#endif + return result; } @@ -405,48 +520,61 @@ size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, *****************************************************/ struct LZ4F_CDict_s { + LZ4F_CustomMem cmem; void* dictContent; LZ4_stream_t* fastCtx; LZ4_streamHC_t* HCCtx; }; /* typedef'd to LZ4F_CDict within lz4frame_static.h */ -/*! LZ4F_createCDict() : - * When compressing multiple messages / blocks with the same dictionary, it's recommended to load it just once. - * LZ4F_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. - * LZ4F_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. - * `dictBuffer` can be released after LZ4F_CDict creation, since its content is copied within CDict - * @return : digested dictionary for compression, or NULL if failed */ -LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize) +LZ4F_CDict* +LZ4F_createCDict_advanced(LZ4F_CustomMem cmem, const void* dictBuffer, size_t dictSize) { const char* dictStart = (const char*)dictBuffer; - LZ4F_CDict* cdict = (LZ4F_CDict*) malloc(sizeof(*cdict)); + LZ4F_CDict* const cdict = (LZ4F_CDict*)LZ4F_malloc(sizeof(*cdict), cmem); + DEBUGLOG(4, "LZ4F_createCDict_advanced"); if (!cdict) return NULL; + cdict->cmem = cmem; if (dictSize > 64 KB) { dictStart += dictSize - 64 KB; dictSize = 64 KB; } - cdict->dictContent = ALLOCATOR(dictSize); - cdict->fastCtx = LZ4_createStream(); - cdict->HCCtx = LZ4_createStreamHC(); + cdict->dictContent = LZ4F_malloc(dictSize, cmem); + cdict->fastCtx = (LZ4_stream_t*)LZ4F_malloc(sizeof(LZ4_stream_t), cmem); + if (cdict->fastCtx) + LZ4_initStream(cdict->fastCtx, sizeof(LZ4_stream_t)); + cdict->HCCtx = (LZ4_streamHC_t*)LZ4F_malloc(sizeof(LZ4_streamHC_t), cmem); + if (cdict->HCCtx) + LZ4_initStream(cdict->HCCtx, sizeof(LZ4_streamHC_t)); if (!cdict->dictContent || !cdict->fastCtx || !cdict->HCCtx) { LZ4F_freeCDict(cdict); return NULL; } memcpy(cdict->dictContent, dictStart, dictSize); - LZ4_resetStream(cdict->fastCtx); LZ4_loadDict (cdict->fastCtx, (const char*)cdict->dictContent, (int)dictSize); - LZ4_resetStreamHC(cdict->HCCtx, LZ4HC_CLEVEL_DEFAULT); + LZ4_setCompressionLevel(cdict->HCCtx, LZ4HC_CLEVEL_DEFAULT); LZ4_loadDictHC(cdict->HCCtx, (const char*)cdict->dictContent, (int)dictSize); return cdict; } +/*! LZ4F_createCDict() : + * When compressing multiple messages / blocks with the same dictionary, it's recommended to load it just once. + * LZ4F_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. + * LZ4F_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * @dictBuffer can be released after LZ4F_CDict creation, since its content is copied within CDict + * @return : digested dictionary for compression, or NULL if failed */ +LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize) +{ + DEBUGLOG(4, "LZ4F_createCDict"); + return LZ4F_createCDict_advanced(LZ4F_defaultCMem, dictBuffer, dictSize); +} + void LZ4F_freeCDict(LZ4F_CDict* cdict) { if (cdict==NULL) return; /* support free on NULL */ - FREEMEM(cdict->dictContent); - LZ4_freeStream(cdict->fastCtx); - LZ4_freeStreamHC(cdict->HCCtx); - FREEMEM(cdict); + LZ4F_free(cdict->dictContent, cdict->cmem); + LZ4F_free(cdict->fastCtx, cdict->cmem); + LZ4F_free(cdict->HCCtx, cdict->cmem); + LZ4F_free(cdict, cdict->cmem); } @@ -454,6 +582,20 @@ void LZ4F_freeCDict(LZ4F_CDict* cdict) * Advanced compression functions ***********************************/ +LZ4F_cctx* +LZ4F_createCompressionContext_advanced(LZ4F_CustomMem customMem, unsigned version) +{ + LZ4F_cctx* const cctxPtr = + (LZ4F_cctx*)LZ4F_calloc(sizeof(LZ4F_cctx), customMem); + if (cctxPtr==NULL) return NULL; + + cctxPtr->cmem = customMem; + cctxPtr->version = version; + cctxPtr->cStage = 0; /* Uninitialized. Next stage : init cctx */ + + return cctxPtr; +} + /*! LZ4F_createCompressionContext() : * The first thing to do is to create a compressionContext object, which will be used in all compression operations. * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version and an LZ4F_preferences_t structure. @@ -461,39 +603,75 @@ void LZ4F_freeCDict(LZ4F_CDict* cdict) * The function will provide a pointer to an allocated LZ4F_compressionContext_t object. * If the result LZ4F_errorCode_t is not OK_NoError, there was an error during context creation. * Object can release its memory using LZ4F_freeCompressionContext(); - */ -LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_compressionContext_t* LZ4F_compressionContextPtr, unsigned version) +**/ +LZ4F_errorCode_t +LZ4F_createCompressionContext(LZ4F_cctx** LZ4F_compressionContextPtr, unsigned version) { - LZ4F_cctx_t* const cctxPtr = (LZ4F_cctx_t*)ALLOCATOR(sizeof(LZ4F_cctx_t)); - if (cctxPtr==NULL) return err0r(LZ4F_ERROR_allocation_failed); - - cctxPtr->version = version; - cctxPtr->cStage = 0; /* Next stage : init stream */ - - *LZ4F_compressionContextPtr = (LZ4F_compressionContext_t)cctxPtr; + assert(LZ4F_compressionContextPtr != NULL); /* considered a violation of narrow contract */ + /* in case it nonetheless happen in production */ + RETURN_ERROR_IF(LZ4F_compressionContextPtr == NULL, parameter_null); + *LZ4F_compressionContextPtr = LZ4F_createCompressionContext_advanced(LZ4F_defaultCMem, version); + RETURN_ERROR_IF(*LZ4F_compressionContextPtr==NULL, allocation_failed); return LZ4F_OK_NoError; } -LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_compressionContext_t LZ4F_compressionContext) +LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_cctx* cctxPtr) { - LZ4F_cctx_t* const cctxPtr = (LZ4F_cctx_t*)LZ4F_compressionContext; - if (cctxPtr != NULL) { /* support free on NULL */ - FREEMEM(cctxPtr->lz4CtxPtr); /* works because LZ4_streamHC_t and LZ4_stream_t are simple POD types */ - FREEMEM(cctxPtr->tmpBuff); - FREEMEM(LZ4F_compressionContext); + LZ4F_free(cctxPtr->lz4CtxPtr, cctxPtr->cmem); /* note: LZ4_streamHC_t and LZ4_stream_t are simple POD types */ + LZ4F_free(cctxPtr->tmpBuff, cctxPtr->cmem); + LZ4F_free(cctxPtr, cctxPtr->cmem); } - return LZ4F_OK_NoError; } +/** + * This function prepares the internal LZ4(HC) stream for a new compression, + * resetting the context and attaching the dictionary, if there is one. + * + * It needs to be called at the beginning of each independent compression + * stream (i.e., at the beginning of a frame in blockLinked mode, or at the + * beginning of each block in blockIndependent mode). + */ +static void LZ4F_initStream(void* ctx, + const LZ4F_CDict* cdict, + int level, + LZ4F_blockMode_t blockMode) { + if (level < LZ4HC_CLEVEL_MIN) { + if (cdict != NULL || blockMode == LZ4F_blockLinked) { + /* In these cases, we will call LZ4_compress_fast_continue(), + * which needs an already reset context. Otherwise, we'll call a + * one-shot API. The non-continued APIs internally perform their own + * resets at the beginning of their calls, where they know what + * tableType they need the context to be in. So in that case this + * would be misguided / wasted work. */ + LZ4_resetStream_fast((LZ4_stream_t*)ctx); + } + LZ4_attach_dictionary((LZ4_stream_t *)ctx, cdict ? cdict->fastCtx : NULL); + } else { + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)ctx, level); + LZ4_attach_HC_dictionary((LZ4_streamHC_t *)ctx, cdict ? cdict->HCCtx : NULL); + } +} + +static int ctxTypeID_to_size(int ctxTypeID) { + switch(ctxTypeID) { + case 1: + return LZ4_sizeofState(); + case 2: + return LZ4_sizeofStateHC(); + default: + return 0; + } +} + /*! LZ4F_compressBegin_usingCDict() : - * init streaming compression and writes frame header into dstBuffer. - * dstBuffer must be >= LZ4F_HEADER_SIZE_MAX bytes. - * @return : number of bytes written into dstBuffer for the header + * init streaming compression AND writes frame header into @dstBuffer. + * @dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @return : number of bytes written into @dstBuffer for the header * or an error code (can be tested using LZ4F_isError()) */ size_t LZ4F_compressBegin_usingCDict(LZ4F_cctx* cctxPtr, @@ -501,26 +679,45 @@ size_t LZ4F_compressBegin_usingCDict(LZ4F_cctx* cctxPtr, const LZ4F_CDict* cdict, const LZ4F_preferences_t* preferencesPtr) { - LZ4F_preferences_t prefNull; + LZ4F_preferences_t const prefNull = LZ4F_INIT_PREFERENCES; BYTE* const dstStart = (BYTE*)dstBuffer; BYTE* dstPtr = dstStart; - BYTE* headerStart; - if (dstCapacity < maxFHSize) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); - memset(&prefNull, 0, sizeof(prefNull)); + RETURN_ERROR_IF(dstCapacity < maxFHSize, dstMaxSize_tooSmall); if (preferencesPtr == NULL) preferencesPtr = &prefNull; cctxPtr->prefs = *preferencesPtr; - /* Ctx Management */ - { U32 const ctxTypeID = (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) ? 1 : 2; /* 0:nothing ; 1:LZ4 table ; 2:HC tables */ - if (cctxPtr->lz4CtxLevel < ctxTypeID) { - FREEMEM(cctxPtr->lz4CtxPtr); - if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) - cctxPtr->lz4CtxPtr = (void*)LZ4_createStream(); - else - cctxPtr->lz4CtxPtr = (void*)LZ4_createStreamHC(); - if (cctxPtr->lz4CtxPtr == NULL) return err0r(LZ4F_ERROR_allocation_failed); - cctxPtr->lz4CtxLevel = ctxTypeID; + /* cctx Management */ + { U16 const ctxTypeID = (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) ? 1 : 2; + int requiredSize = ctxTypeID_to_size(ctxTypeID); + int allocatedSize = ctxTypeID_to_size(cctxPtr->lz4CtxAlloc); + if (allocatedSize < requiredSize) { + /* not enough space allocated */ + LZ4F_free(cctxPtr->lz4CtxPtr, cctxPtr->cmem); + if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) { + /* must take ownership of memory allocation, + * in order to respect custom allocator contract */ + cctxPtr->lz4CtxPtr = LZ4F_malloc(sizeof(LZ4_stream_t), cctxPtr->cmem); + if (cctxPtr->lz4CtxPtr) + LZ4_initStream(cctxPtr->lz4CtxPtr, sizeof(LZ4_stream_t)); + } else { + cctxPtr->lz4CtxPtr = LZ4F_malloc(sizeof(LZ4_streamHC_t), cctxPtr->cmem); + if (cctxPtr->lz4CtxPtr) + LZ4_initStreamHC(cctxPtr->lz4CtxPtr, sizeof(LZ4_streamHC_t)); + } + RETURN_ERROR_IF(cctxPtr->lz4CtxPtr == NULL, allocation_failed); + cctxPtr->lz4CtxAlloc = ctxTypeID; + cctxPtr->lz4CtxState = ctxTypeID; + } else if (cctxPtr->lz4CtxState != ctxTypeID) { + /* otherwise, a sufficient buffer is already allocated, + * but we need to reset it to the correct context type */ + if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) { + LZ4_initStream((LZ4_stream_t*)cctxPtr->lz4CtxPtr, sizeof(LZ4_stream_t)); + } else { + LZ4_initStreamHC((LZ4_streamHC_t*)cctxPtr->lz4CtxPtr, sizeof(LZ4_streamHC_t)); + LZ4_setCompressionLevel((LZ4_streamHC_t*)cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel); + } + cctxPtr->lz4CtxState = ctxTypeID; } } /* Buffer Management */ @@ -529,79 +726,71 @@ size_t LZ4F_compressBegin_usingCDict(LZ4F_cctx* cctxPtr, cctxPtr->maxBlockSize = LZ4F_getBlockSize(cctxPtr->prefs.frameInfo.blockSizeID); { size_t const requiredBuffSize = preferencesPtr->autoFlush ? - (cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) * 64 KB : /* only needs windows size */ - cctxPtr->maxBlockSize + ((cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) * 128 KB); + ((cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) ? 64 KB : 0) : /* only needs past data up to window size */ + cctxPtr->maxBlockSize + ((cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) ? 128 KB : 0); if (cctxPtr->maxBufferSize < requiredBuffSize) { cctxPtr->maxBufferSize = 0; - FREEMEM(cctxPtr->tmpBuff); - cctxPtr->tmpBuff = (BYTE*)ALLOCATOR(requiredBuffSize); - if (cctxPtr->tmpBuff == NULL) return err0r(LZ4F_ERROR_allocation_failed); + LZ4F_free(cctxPtr->tmpBuff, cctxPtr->cmem); + cctxPtr->tmpBuff = (BYTE*)LZ4F_calloc(requiredBuffSize, cctxPtr->cmem); + RETURN_ERROR_IF(cctxPtr->tmpBuff == NULL, allocation_failed); cctxPtr->maxBufferSize = requiredBuffSize; } } cctxPtr->tmpIn = cctxPtr->tmpBuff; cctxPtr->tmpInSize = 0; - XXH32_reset(&(cctxPtr->xxh), 0); + (void)XXH32_reset(&(cctxPtr->xxh), 0); /* context init */ cctxPtr->cdict = cdict; if (cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) { /* frame init only for blockLinked : blockIndependent will be init at each block */ - if (cdict) { - if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) { - memcpy(cctxPtr->lz4CtxPtr, cdict->fastCtx, sizeof(*cdict->fastCtx)); - } else { - memcpy(cctxPtr->lz4CtxPtr, cdict->HCCtx, sizeof(*cdict->HCCtx)); - LZ4_setCompressionLevel((LZ4_streamHC_t*)cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel); - } - } else { - if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) - LZ4_resetStream((LZ4_stream_t*)(cctxPtr->lz4CtxPtr)); - else - LZ4_resetStreamHC((LZ4_streamHC_t*)(cctxPtr->lz4CtxPtr), cctxPtr->prefs.compressionLevel); - } + LZ4F_initStream(cctxPtr->lz4CtxPtr, cdict, cctxPtr->prefs.compressionLevel, LZ4F_blockLinked); + } + if (preferencesPtr->compressionLevel >= LZ4HC_CLEVEL_MIN) { + LZ4_favorDecompressionSpeed((LZ4_streamHC_t*)cctxPtr->lz4CtxPtr, (int)preferencesPtr->favorDecSpeed); } /* Magic Number */ LZ4F_writeLE32(dstPtr, LZ4F_MAGICNUMBER); dstPtr += 4; - headerStart = dstPtr; - - /* FLG Byte */ - *dstPtr++ = (BYTE)(((1 & _2BITS) << 6) /* Version('01') */ - + ((cctxPtr->prefs.frameInfo.blockMode & _1BIT ) << 5) - + ((cctxPtr->prefs.frameInfo.blockChecksumFlag & _1BIT ) << 4) - + ((cctxPtr->prefs.frameInfo.contentSize > 0) << 3) - + ((cctxPtr->prefs.frameInfo.contentChecksumFlag & _1BIT ) << 2) - + (cctxPtr->prefs.frameInfo.dictID > 0) ); - /* BD Byte */ - *dstPtr++ = (BYTE)((cctxPtr->prefs.frameInfo.blockSizeID & _3BITS) << 4); - /* Optional Frame content size field */ - if (cctxPtr->prefs.frameInfo.contentSize) { - LZ4F_writeLE64(dstPtr, cctxPtr->prefs.frameInfo.contentSize); - dstPtr += 8; - cctxPtr->totalInSize = 0; - } - /* Optional dictionary ID field */ - if (cctxPtr->prefs.frameInfo.dictID) { - LZ4F_writeLE32(dstPtr, cctxPtr->prefs.frameInfo.dictID); - dstPtr += 4; + { BYTE* const headerStart = dstPtr; + + /* FLG Byte */ + *dstPtr++ = (BYTE)(((1 & _2BITS) << 6) /* Version('01') */ + + ((cctxPtr->prefs.frameInfo.blockMode & _1BIT ) << 5) + + ((cctxPtr->prefs.frameInfo.blockChecksumFlag & _1BIT ) << 4) + + ((unsigned)(cctxPtr->prefs.frameInfo.contentSize > 0) << 3) + + ((cctxPtr->prefs.frameInfo.contentChecksumFlag & _1BIT ) << 2) + + (cctxPtr->prefs.frameInfo.dictID > 0) ); + /* BD Byte */ + *dstPtr++ = (BYTE)((cctxPtr->prefs.frameInfo.blockSizeID & _3BITS) << 4); + /* Optional Frame content size field */ + if (cctxPtr->prefs.frameInfo.contentSize) { + LZ4F_writeLE64(dstPtr, cctxPtr->prefs.frameInfo.contentSize); + dstPtr += 8; + cctxPtr->totalInSize = 0; + } + /* Optional dictionary ID field */ + if (cctxPtr->prefs.frameInfo.dictID) { + LZ4F_writeLE32(dstPtr, cctxPtr->prefs.frameInfo.dictID); + dstPtr += 4; + } + /* Header CRC Byte */ + *dstPtr = LZ4F_headerChecksum(headerStart, (size_t)(dstPtr - headerStart)); + dstPtr++; } - /* Header CRC Byte */ - *dstPtr = LZ4F_headerChecksum(headerStart, dstPtr - headerStart); - dstPtr++; cctxPtr->cStage = 1; /* header written, now request input data block */ - return (dstPtr - dstStart); + return (size_t)(dstPtr - dstStart); } /*! LZ4F_compressBegin() : - * init streaming compression and writes frame header into dstBuffer. - * dstBuffer must be >= LZ4F_HEADER_SIZE_MAX bytes. - * preferencesPtr can be NULL, in which case default parameters are selected. + * init streaming compression AND writes frame header into @dstBuffer. + * @dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @preferencesPtr can be NULL, in which case default parameters are selected. * @return : number of bytes written into dstBuffer for the header - * or an error code (can be tested using LZ4F_isError()) + * or an error code (can be tested using LZ4F_isError()) */ size_t LZ4F_compressBegin(LZ4F_cctx* cctxPtr, void* dstBuffer, size_t dstCapacity, @@ -619,6 +808,9 @@ size_t LZ4F_compressBegin(LZ4F_cctx* cctxPtr, */ size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr) { + if (preferencesPtr && preferencesPtr->autoFlush) { + return LZ4F_compressBound_internal(srcSize, preferencesPtr, 0); + } return LZ4F_compressBound_internal(srcSize, preferencesPtr, (size_t)-1); } @@ -627,55 +819,64 @@ typedef int (*compressFunc_t)(void* ctx, const char* src, char* dst, int srcSize /*! LZ4F_makeBlock(): - * compress a single block, add header and checksum - * assumption : dst buffer capacity is >= srcSize */ -static size_t LZ4F_makeBlock(void* dst, const void* src, size_t srcSize, + * compress a single block, add header and optional checksum. + * assumption : dst buffer capacity is >= BHSize + srcSize + crcSize + */ +static size_t LZ4F_makeBlock(void* dst, + const void* src, size_t srcSize, compressFunc_t compress, void* lz4ctx, int level, - const LZ4F_CDict* cdict, LZ4F_blockChecksum_t crcFlag) + const LZ4F_CDict* cdict, + LZ4F_blockChecksum_t crcFlag) { BYTE* const cSizePtr = (BYTE*)dst; - U32 cSize = (U32)compress(lz4ctx, (const char*)src, (char*)(cSizePtr+4), - (int)(srcSize), (int)(srcSize-1), - level, cdict); - LZ4F_writeLE32(cSizePtr, cSize); - if (cSize == 0) { /* compression failed */ + U32 cSize; + assert(compress != NULL); + cSize = (U32)compress(lz4ctx, (const char*)src, (char*)(cSizePtr+BHSize), + (int)(srcSize), (int)(srcSize-1), + level, cdict); + + if (cSize == 0 || cSize >= srcSize) { cSize = (U32)srcSize; LZ4F_writeLE32(cSizePtr, cSize | LZ4F_BLOCKUNCOMPRESSED_FLAG); - memcpy(cSizePtr+4, src, srcSize); + memcpy(cSizePtr+BHSize, src, srcSize); + } else { + LZ4F_writeLE32(cSizePtr, cSize); } if (crcFlag) { - U32 const crc32 = XXH32(cSizePtr+4, cSize, 0); /* checksum of compressed data */ - LZ4F_writeLE32(cSizePtr+4+cSize, crc32); + U32 const crc32 = XXH32(cSizePtr+BHSize, cSize, 0); /* checksum of compressed data */ + LZ4F_writeLE32(cSizePtr+BHSize+cSize, crc32); } - return 4 + cSize + ((U32)crcFlag)*4; + return BHSize + cSize + ((U32)crcFlag)*BFSize; } static int LZ4F_compressBlock(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) { - int const acceleration = (level < -1) ? -level : 1; + int const acceleration = (level < 0) ? -level + 1 : 1; + DEBUGLOG(5, "LZ4F_compressBlock (srcSize=%i)", srcSize); + LZ4F_initStream(ctx, cdict, level, LZ4F_blockIndependent); if (cdict) { - memcpy(ctx, cdict->fastCtx, sizeof(*cdict->fastCtx)); return LZ4_compress_fast_continue((LZ4_stream_t*)ctx, src, dst, srcSize, dstCapacity, acceleration); + } else { + return LZ4_compress_fast_extState_fastReset(ctx, src, dst, srcSize, dstCapacity, acceleration); } - return LZ4_compress_fast_extState(ctx, src, dst, srcSize, dstCapacity, acceleration); } static int LZ4F_compressBlock_continue(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) { - int const acceleration = (level < -1) ? -level : 1; + int const acceleration = (level < 0) ? -level + 1 : 1; (void)cdict; /* init once at beginning of frame */ + DEBUGLOG(5, "LZ4F_compressBlock_continue (srcSize=%i)", srcSize); return LZ4_compress_fast_continue((LZ4_stream_t*)ctx, src, dst, srcSize, dstCapacity, acceleration); } static int LZ4F_compressBlockHC(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) { + LZ4F_initStream(ctx, cdict, level, LZ4F_blockIndependent); if (cdict) { - memcpy(ctx, cdict->HCCtx, sizeof(*cdict->HCCtx)); - LZ4_setCompressionLevel((LZ4_streamHC_t*)ctx, level); return LZ4_compress_HC_continue((LZ4_streamHC_t*)ctx, src, dst, srcSize, dstCapacity); } - return LZ4_compress_HC_extStateHC(ctx, src, dst, srcSize, dstCapacity, level); + return LZ4_compress_HC_extStateHC_fastReset(ctx, src, dst, srcSize, dstCapacity, level); } static int LZ4F_compressBlockHC_continue(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) @@ -684,8 +885,15 @@ static int LZ4F_compressBlockHC_continue(void* ctx, const char* src, char* dst, return LZ4_compress_HC_continue((LZ4_streamHC_t*)ctx, src, dst, srcSize, dstCapacity); } -static compressFunc_t LZ4F_selectCompression(LZ4F_blockMode_t blockMode, int level) +static int LZ4F_doNotCompressBlock(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) { + (void)ctx; (void)src; (void)dst; (void)srcSize; (void)dstCapacity; (void)level; (void)cdict; + return 0; +} + +static compressFunc_t LZ4F_selectCompression(LZ4F_blockMode_t blockMode, int level, LZ4F_blockCompression_t compressMode) +{ + if (compressMode == LZ4B_UNCOMPRESSED) return LZ4F_doNotCompressBlock; if (level < LZ4HC_CLEVEL_MIN) { if (blockMode == LZ4F_blockIndependent) return LZ4F_compressBlock; return LZ4F_compressBlock_continue; @@ -694,6 +902,7 @@ static compressFunc_t LZ4F_selectCompression(LZ4F_blockMode_t blockMode, int lev return LZ4F_compressBlockHC_continue; } +/* Save history (up to 64KB) into @tmpBuff */ static int LZ4F_localSaveDict(LZ4F_cctx_t* cctxPtr) { if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) @@ -703,36 +912,57 @@ static int LZ4F_localSaveDict(LZ4F_cctx_t* cctxPtr) typedef enum { notDone, fromTmpBuffer, fromSrcBuffer } LZ4F_lastBlockStatus; -/*! LZ4F_compressUpdate() : +static const LZ4F_compressOptions_t k_cOptionsNull = { 0, { 0, 0, 0 } }; + + + /*! LZ4F_compressUpdateImpl() : * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. - * dstBuffer MUST be >= LZ4F_compressBound(srcSize, preferencesPtr). - * LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * When successful, the function always entirely consumes @srcBuffer. + * src data is either buffered or compressed into @dstBuffer. + * If the block compression does not match the compression of the previous block, the old data is flushed + * and operations continue with the new compression mode. + * @dstCapacity MUST be >= LZ4F_compressBound(srcSize, preferencesPtr) when block compression is turned on. + * @compressOptionsPtr is optional : provide NULL to mean "default". * @return : the number of bytes written into dstBuffer. It can be zero, meaning input data was just buffered. * or an error code if it fails (which can be tested using LZ4F_isError()) + * After an error, the state is left in a UB state, and must be re-initialized. */ -size_t LZ4F_compressUpdate(LZ4F_cctx* cctxPtr, - void* dstBuffer, size_t dstCapacity, +static size_t LZ4F_compressUpdateImpl(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, const void* srcBuffer, size_t srcSize, - const LZ4F_compressOptions_t* compressOptionsPtr) -{ - LZ4F_compressOptions_t cOptionsNull; + const LZ4F_compressOptions_t* compressOptionsPtr, + LZ4F_blockCompression_t blockCompression) + { size_t const blockSize = cctxPtr->maxBlockSize; const BYTE* srcPtr = (const BYTE*)srcBuffer; const BYTE* const srcEnd = srcPtr + srcSize; BYTE* const dstStart = (BYTE*)dstBuffer; BYTE* dstPtr = dstStart; LZ4F_lastBlockStatus lastBlockCompressed = notDone; - compressFunc_t const compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel); - + compressFunc_t const compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel, blockCompression); + size_t bytesWritten; + DEBUGLOG(4, "LZ4F_compressUpdate (srcSize=%zu)", srcSize); + + RETURN_ERROR_IF(cctxPtr->cStage != 1, compressionState_uninitialized); /* state must be initialized and waiting for next block */ + if (dstCapacity < LZ4F_compressBound_internal(srcSize, &(cctxPtr->prefs), cctxPtr->tmpInSize)) + RETURN_ERROR(dstMaxSize_tooSmall); + + if (blockCompression == LZ4B_UNCOMPRESSED && dstCapacity < srcSize) + RETURN_ERROR(dstMaxSize_tooSmall); + + /* flush currently written block, to continue with new block compression */ + if (cctxPtr->blockCompression != blockCompression) { + bytesWritten = LZ4F_flush(cctxPtr, dstBuffer, dstCapacity, compressOptionsPtr); + dstPtr += bytesWritten; + cctxPtr->blockCompression = blockCompression; + } - if (cctxPtr->cStage != 1) return err0r(LZ4F_ERROR_GENERIC); - if (dstCapacity < LZ4F_compressBound_internal(srcSize, &(cctxPtr->prefs), cctxPtr->tmpInSize)) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); - memset(&cOptionsNull, 0, sizeof(cOptionsNull)); - if (compressOptionsPtr == NULL) compressOptionsPtr = &cOptionsNull; + if (compressOptionsPtr == NULL) compressOptionsPtr = &k_cOptionsNull; /* complete tmp buffer */ if (cctxPtr->tmpInSize > 0) { /* some data already within tmp buffer */ size_t const sizeToCopy = blockSize - cctxPtr->tmpInSize; + assert(blockSize > cctxPtr->tmpInSize); if (sizeToCopy > srcSize) { /* add src to tmpIn buffer */ memcpy(cctxPtr->tmpIn + cctxPtr->tmpInSize, srcBuffer, srcSize); @@ -745,130 +975,202 @@ size_t LZ4F_compressUpdate(LZ4F_cctx* cctxPtr, memcpy(cctxPtr->tmpIn + cctxPtr->tmpInSize, srcBuffer, sizeToCopy); srcPtr += sizeToCopy; - dstPtr += LZ4F_makeBlock(dstPtr, cctxPtr->tmpIn, blockSize, + dstPtr += LZ4F_makeBlock(dstPtr, + cctxPtr->tmpIn, blockSize, compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, - cctxPtr->cdict, cctxPtr->prefs.frameInfo.blockChecksumFlag); - + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); if (cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) cctxPtr->tmpIn += blockSize; cctxPtr->tmpInSize = 0; - } - } + } } while ((size_t)(srcEnd - srcPtr) >= blockSize) { /* compress full blocks */ lastBlockCompressed = fromSrcBuffer; - dstPtr += LZ4F_makeBlock(dstPtr, srcPtr, blockSize, + dstPtr += LZ4F_makeBlock(dstPtr, + srcPtr, blockSize, compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, - cctxPtr->cdict, cctxPtr->prefs.frameInfo.blockChecksumFlag); + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); srcPtr += blockSize; } if ((cctxPtr->prefs.autoFlush) && (srcPtr < srcEnd)) { - /* compress remaining input < blockSize */ + /* autoFlush : remaining input (< blockSize) is compressed */ lastBlockCompressed = fromSrcBuffer; - dstPtr += LZ4F_makeBlock(dstPtr, srcPtr, srcEnd - srcPtr, + dstPtr += LZ4F_makeBlock(dstPtr, + srcPtr, (size_t)(srcEnd - srcPtr), compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, - cctxPtr->cdict, cctxPtr->prefs.frameInfo.blockChecksumFlag); - srcPtr = srcEnd; + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + srcPtr = srcEnd; } - /* preserve dictionary if necessary */ + /* preserve dictionary within @tmpBuff whenever necessary */ if ((cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) && (lastBlockCompressed==fromSrcBuffer)) { + /* linked blocks are only supported in compressed mode, see LZ4F_uncompressedUpdate */ + assert(blockCompression == LZ4B_COMPRESSED); if (compressOptionsPtr->stableSrc) { - cctxPtr->tmpIn = cctxPtr->tmpBuff; + cctxPtr->tmpIn = cctxPtr->tmpBuff; /* src is stable : dictionary remains in src across invocations */ } else { int const realDictSize = LZ4F_localSaveDict(cctxPtr); - if (realDictSize==0) return err0r(LZ4F_ERROR_GENERIC); + assert(0 <= realDictSize && realDictSize <= 64 KB); cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; } } /* keep tmpIn within limits */ - if ((cctxPtr->tmpIn + blockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize) /* necessarily LZ4F_blockLinked && lastBlockCompressed==fromTmpBuffer */ - && !(cctxPtr->prefs.autoFlush)) + if (!(cctxPtr->prefs.autoFlush) /* no autoflush : there may be some data left within internal buffer */ + && (cctxPtr->tmpIn + blockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize) ) /* not enough room to store next block */ { + /* only preserve 64KB within internal buffer. Ensures there is enough room for next block. + * note: this situation necessarily implies lastBlockCompressed==fromTmpBuffer */ int const realDictSize = LZ4F_localSaveDict(cctxPtr); cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + assert((cctxPtr->tmpIn + blockSize) <= (cctxPtr->tmpBuff + cctxPtr->maxBufferSize)); } /* some input data left, necessarily < blockSize */ if (srcPtr < srcEnd) { /* fill tmp buffer */ - size_t const sizeToCopy = srcEnd - srcPtr; + size_t const sizeToCopy = (size_t)(srcEnd - srcPtr); memcpy(cctxPtr->tmpIn, srcPtr, sizeToCopy); cctxPtr->tmpInSize = sizeToCopy; } if (cctxPtr->prefs.frameInfo.contentChecksumFlag == LZ4F_contentChecksumEnabled) - XXH32_update(&(cctxPtr->xxh), srcBuffer, srcSize); + (void)XXH32_update(&(cctxPtr->xxh), srcBuffer, srcSize); cctxPtr->totalInSize += srcSize; - return dstPtr - dstStart; + return (size_t)(dstPtr - dstStart); +} + +/*! LZ4F_compressUpdate() : + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * When successful, the function always entirely consumes @srcBuffer. + * src data is either buffered or compressed into @dstBuffer. + * If previously an uncompressed block was written, buffered data is flushed + * before appending compressed data is continued. + * @dstCapacity MUST be >= LZ4F_compressBound(srcSize, preferencesPtr). + * @compressOptionsPtr is optional : provide NULL to mean "default". + * @return : the number of bytes written into dstBuffer. It can be zero, meaning input data was just buffered. + * or an error code if it fails (which can be tested using LZ4F_isError()) + * After an error, the state is left in a UB state, and must be re-initialized. + */ +size_t LZ4F_compressUpdate(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* compressOptionsPtr) +{ + return LZ4F_compressUpdateImpl(cctxPtr, + dstBuffer, dstCapacity, + srcBuffer, srcSize, + compressOptionsPtr, LZ4B_COMPRESSED); +} + +/*! LZ4F_compressUpdate() : + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * When successful, the function always entirely consumes @srcBuffer. + * src data is either buffered or compressed into @dstBuffer. + * If previously an uncompressed block was written, buffered data is flushed + * before appending compressed data is continued. + * This is only supported when LZ4F_blockIndependent is used + * @dstCapacity MUST be >= LZ4F_compressBound(srcSize, preferencesPtr). + * @compressOptionsPtr is optional : provide NULL to mean "default". + * @return : the number of bytes written into dstBuffer. It can be zero, meaning input data was just buffered. + * or an error code if it fails (which can be tested using LZ4F_isError()) + * After an error, the state is left in a UB state, and must be re-initialized. + */ +size_t LZ4F_uncompressedUpdate(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* compressOptionsPtr) { + RETURN_ERROR_IF(cctxPtr->prefs.frameInfo.blockMode != LZ4F_blockIndependent, blockMode_invalid); + return LZ4F_compressUpdateImpl(cctxPtr, + dstBuffer, dstCapacity, + srcBuffer, srcSize, + compressOptionsPtr, LZ4B_UNCOMPRESSED); } /*! LZ4F_flush() : - * Should you need to create compressed data immediately, without waiting for a block to be filled, - * you can call LZ4_flush(), which will immediately compress any remaining data stored within compressionContext. - * The result of the function is the number of bytes written into dstBuffer - * (it can be zero, this means there was no data left within compressionContext) + * When compressed data must be sent immediately, without waiting for a block to be filled, + * invoke LZ4_flush(), which will immediately compress any remaining data stored within LZ4F_cctx. + * The result of the function is the number of bytes written into dstBuffer. + * It can be zero, this means there was no data left within LZ4F_cctx. * The function outputs an error code if it fails (can be tested using LZ4F_isError()) - * The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * LZ4F_compressOptions_t* is optional. NULL is a valid argument. */ -size_t LZ4F_flush(LZ4F_cctx* cctxPtr, void* dstBuffer, size_t dstCapacity, const LZ4F_compressOptions_t* compressOptionsPtr) +size_t LZ4F_flush(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* compressOptionsPtr) { BYTE* const dstStart = (BYTE*)dstBuffer; BYTE* dstPtr = dstStart; compressFunc_t compress; if (cctxPtr->tmpInSize == 0) return 0; /* nothing to flush */ - if (cctxPtr->cStage != 1) return err0r(LZ4F_ERROR_GENERIC); - if (dstCapacity < (cctxPtr->tmpInSize + 4)) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); /* +4 : block header(4) */ - (void)compressOptionsPtr; /* not yet useful */ + RETURN_ERROR_IF(cctxPtr->cStage != 1, compressionState_uninitialized); + RETURN_ERROR_IF(dstCapacity < (cctxPtr->tmpInSize + BHSize + BFSize), dstMaxSize_tooSmall); + (void)compressOptionsPtr; /* not useful (yet) */ /* select compression function */ - compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel); + compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel, cctxPtr->blockCompression); /* compress tmp buffer */ - dstPtr += LZ4F_makeBlock(dstPtr, cctxPtr->tmpIn, cctxPtr->tmpInSize, + dstPtr += LZ4F_makeBlock(dstPtr, + cctxPtr->tmpIn, cctxPtr->tmpInSize, compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, - cctxPtr->cdict, cctxPtr->prefs.frameInfo.blockChecksumFlag); - if (cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) cctxPtr->tmpIn += cctxPtr->tmpInSize; + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + assert(((void)"flush overflows dstBuffer!", (size_t)(dstPtr - dstStart) <= dstCapacity)); + + if (cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) + cctxPtr->tmpIn += cctxPtr->tmpInSize; cctxPtr->tmpInSize = 0; /* keep tmpIn within limits */ if ((cctxPtr->tmpIn + cctxPtr->maxBlockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize)) { /* necessarily LZ4F_blockLinked */ - int realDictSize = LZ4F_localSaveDict(cctxPtr); + int const realDictSize = LZ4F_localSaveDict(cctxPtr); cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; } - return dstPtr - dstStart; + return (size_t)(dstPtr - dstStart); } /*! LZ4F_compressEnd() : - * When you want to properly finish the compressed frame, just call LZ4F_compressEnd(). - * It will flush whatever data remained within compressionContext (like LZ4_flush()) - * but also properly finalize the frame, with an endMark and a checksum. - * The result of the function is the number of bytes written into dstBuffer (necessarily >= 4 (endMark size)) - * The function outputs an error code if it fails (can be tested using LZ4F_isError()) - * The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. - * compressionContext can then be used again, starting with LZ4F_compressBegin(). The preferences will remain the same. + * When you want to properly finish the compressed frame, just call LZ4F_compressEnd(). + * It will flush whatever data remained within compressionContext (like LZ4_flush()) + * but also properly finalize the frame, with an endMark and an (optional) checksum. + * LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * @return: the number of bytes written into dstBuffer (necessarily >= 4 (endMark size)) + * or an error code if it fails (can be tested using LZ4F_isError()) + * The context can then be used again to compress a new frame, starting with LZ4F_compressBegin(). */ -size_t LZ4F_compressEnd(LZ4F_cctx* cctxPtr, void* dstBuffer, size_t dstMaxSize, const LZ4F_compressOptions_t* compressOptionsPtr) +size_t LZ4F_compressEnd(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* compressOptionsPtr) { BYTE* const dstStart = (BYTE*)dstBuffer; BYTE* dstPtr = dstStart; - size_t const flushSize = LZ4F_flush(cctxPtr, dstBuffer, dstMaxSize, compressOptionsPtr); - if (LZ4F_isError(flushSize)) return flushSize; + size_t const flushSize = LZ4F_flush(cctxPtr, dstBuffer, dstCapacity, compressOptionsPtr); + DEBUGLOG(5,"LZ4F_compressEnd: dstCapacity=%u", (unsigned)dstCapacity); + FORWARD_IF_ERROR(flushSize); dstPtr += flushSize; + assert(flushSize <= dstCapacity); + dstCapacity -= flushSize; + + RETURN_ERROR_IF(dstCapacity < 4, dstMaxSize_tooSmall); LZ4F_writeLE32(dstPtr, 0); - dstPtr+=4; /* endMark */ + dstPtr += 4; /* endMark */ if (cctxPtr->prefs.frameInfo.contentChecksumFlag == LZ4F_contentChecksumEnabled) { U32 const xxh = XXH32_digest(&(cctxPtr->xxh)); + RETURN_ERROR_IF(dstCapacity < 8, dstMaxSize_tooSmall); + DEBUGLOG(5,"Writing 32-bit content checksum"); LZ4F_writeLE32(dstPtr, xxh); dstPtr+=4; /* content Checksum */ } @@ -878,10 +1180,10 @@ size_t LZ4F_compressEnd(LZ4F_cctx* cctxPtr, void* dstBuffer, size_t dstMaxSize, if (cctxPtr->prefs.frameInfo.contentSize) { if (cctxPtr->prefs.frameInfo.contentSize != cctxPtr->totalInSize) - return err0r(LZ4F_ERROR_frameSize_wrong); + RETURN_ERROR(frameSize_wrong); } - return dstPtr - dstStart; + return (size_t)(dstPtr - dstStart); } @@ -902,6 +1204,7 @@ typedef enum { } dStage_t; struct LZ4F_dctx_s { + LZ4F_CustomMem cmem; LZ4F_frameInfo_t frameInfo; U32 version; dStage_t dStage; @@ -919,23 +1222,37 @@ struct LZ4F_dctx_s { size_t tmpOutStart; XXH32_state_t xxh; XXH32_state_t blockChecksum; + int skipChecksum; BYTE header[LZ4F_HEADER_SIZE_MAX]; }; /* typedef'd to LZ4F_dctx in lz4frame.h */ +LZ4F_dctx* LZ4F_createDecompressionContext_advanced(LZ4F_CustomMem customMem, unsigned version) +{ + LZ4F_dctx* const dctx = (LZ4F_dctx*)LZ4F_calloc(sizeof(LZ4F_dctx), customMem); + if (dctx == NULL) return NULL; + + dctx->cmem = customMem; + dctx->version = version; + return dctx; +} + /*! LZ4F_createDecompressionContext() : * Create a decompressionContext object, which will track all decompression operations. * Provides a pointer to a fully allocated and initialized LZ4F_decompressionContext object. * Object can later be released using LZ4F_freeDecompressionContext(). * @return : if != 0, there was an error during context creation. */ -LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_dctx** LZ4F_decompressionContextPtr, unsigned versionNumber) +LZ4F_errorCode_t +LZ4F_createDecompressionContext(LZ4F_dctx** LZ4F_decompressionContextPtr, unsigned versionNumber) { - LZ4F_dctx* const dctx = (LZ4F_dctx*)ALLOCATOR(sizeof(LZ4F_dctx)); - if (dctx==NULL) return err0r(LZ4F_ERROR_GENERIC); + assert(LZ4F_decompressionContextPtr != NULL); /* violation of narrow contract */ + RETURN_ERROR_IF(LZ4F_decompressionContextPtr == NULL, parameter_null); /* in case it nonetheless happen in production */ - dctx->version = versionNumber; - *LZ4F_decompressionContextPtr = dctx; + *LZ4F_decompressionContextPtr = LZ4F_createDecompressionContext_advanced(LZ4F_defaultCMem, versionNumber); + if (*LZ4F_decompressionContextPtr == NULL) { /* failed allocation */ + RETURN_ERROR(allocation_failed); + } return LZ4F_OK_NoError; } @@ -944,9 +1261,9 @@ LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_dctx* dctx) LZ4F_errorCode_t result = LZ4F_OK_NoError; if (dctx != NULL) { /* can accept NULL input, like free() */ result = (LZ4F_errorCode_t)dctx->dStage; - FREEMEM(dctx->tmpIn); - FREEMEM(dctx->tmpOutBuffer); - FREEMEM(dctx); + LZ4F_free(dctx->tmpIn, dctx->cmem); + LZ4F_free(dctx->tmpOutBuffer, dctx->cmem); + LZ4F_free(dctx, dctx->cmem); } return result; } @@ -959,31 +1276,7 @@ void LZ4F_resetDecompressionContext(LZ4F_dctx* dctx) dctx->dStage = dstage_getFrameHeader; dctx->dict = NULL; dctx->dictSize = 0; -} - - -/*! LZ4F_headerSize() : - * @return : size of frame header - * or an error code, which can be tested using LZ4F_isError() - */ -static size_t LZ4F_headerSize(const void* src, size_t srcSize) -{ - /* minimal srcSize to determine header size */ - if (srcSize < 5) return err0r(LZ4F_ERROR_frameHeader_incomplete); - - /* special case : skippable frames */ - if ((LZ4F_readLE32(src) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) return 8; - - /* control magic number */ - if (LZ4F_readLE32(src) != LZ4F_MAGICNUMBER) - return err0r(LZ4F_ERROR_frameType_unknown); - - /* Frame Header Size */ - { BYTE const FLG = ((const BYTE*)src)[4]; - U32 const contentSizeFlag = (FLG>>3) & _1BIT; - U32 const dictIDFlag = FLG & _1BIT; - return minFHSize + (contentSizeFlag*8) + (dictIDFlag*4); - } + dctx->skipChecksum = 0; } @@ -1001,9 +1294,10 @@ static size_t LZ4F_decodeHeader(LZ4F_dctx* dctx, const void* src, size_t srcSize size_t frameHeaderSize; const BYTE* srcPtr = (const BYTE*)src; + DEBUGLOG(5, "LZ4F_decodeHeader"); /* need to decode header to get frameInfo */ - if (srcSize < minFHSize) return err0r(LZ4F_ERROR_frameHeader_incomplete); /* minimal frame header size */ - memset(&(dctx->frameInfo), 0, sizeof(dctx->frameInfo)); + RETURN_ERROR_IF(srcSize < minFHSize, frameHeader_incomplete); /* minimal frame header size */ + MEM_INIT(&(dctx->frameInfo), 0, sizeof(dctx->frameInfo)); /* special case : skippable frames */ if ((LZ4F_readLE32(srcPtr) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) { @@ -1016,12 +1310,15 @@ static size_t LZ4F_decodeHeader(LZ4F_dctx* dctx, const void* src, size_t srcSize } else { dctx->dStage = dstage_getSFrameSize; return 4; - } - } + } } /* control magic number */ - if (LZ4F_readLE32(srcPtr) != LZ4F_MAGICNUMBER) - return err0r(LZ4F_ERROR_frameType_unknown); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (LZ4F_readLE32(srcPtr) != LZ4F_MAGICNUMBER) { + DEBUGLOG(4, "frame header error : unknown magic number"); + RETURN_ERROR(frameType_unknown); + } +#endif dctx->frameInfo.frameType = LZ4F_frame; /* Flags */ @@ -1033,12 +1330,12 @@ static size_t LZ4F_decodeHeader(LZ4F_dctx* dctx, const void* src, size_t srcSize contentChecksumFlag = (FLG>>2) & _1BIT; dictIDFlag = FLG & _1BIT; /* validate */ - if (((FLG>>1)&_1BIT) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bit */ - if (version != 1) return err0r(LZ4F_ERROR_headerVersion_wrong); /* Version Number, only supported value */ + if (((FLG>>1)&_1BIT) != 0) RETURN_ERROR(reservedFlag_set); /* Reserved bit */ + if (version != 1) RETURN_ERROR(headerVersion_wrong); /* Version Number, only supported value */ } /* Frame Header Size */ - frameHeaderSize = minFHSize + (contentSizeFlag*8) + (dictIDFlag*4); + frameHeaderSize = minFHSize + (contentSizeFlag?8:0) + (dictIDFlag?4:0); if (srcSize < frameHeaderSize) { /* not enough input to fully decode frame header */ @@ -1053,26 +1350,27 @@ static size_t LZ4F_decodeHeader(LZ4F_dctx* dctx, const void* src, size_t srcSize { U32 const BD = srcPtr[5]; blockSizeID = (BD>>4) & _3BITS; /* validate */ - if (((BD>>7)&_1BIT) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bit */ - if (blockSizeID < 4) return err0r(LZ4F_ERROR_maxBlockSize_invalid); /* 4-7 only supported values for the time being */ - if (((BD>>0)&_4BITS) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bits */ + if (((BD>>7)&_1BIT) != 0) RETURN_ERROR(reservedFlag_set); /* Reserved bit */ + if (blockSizeID < 4) RETURN_ERROR(maxBlockSize_invalid); /* 4-7 only supported values for the time being */ + if (((BD>>0)&_4BITS) != 0) RETURN_ERROR(reservedFlag_set); /* Reserved bits */ } /* check header */ + assert(frameHeaderSize > 5); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION { BYTE const HC = LZ4F_headerChecksum(srcPtr+4, frameHeaderSize-5); - if (HC != srcPtr[frameHeaderSize-1]) - return err0r(LZ4F_ERROR_headerChecksum_invalid); + RETURN_ERROR_IF(HC != srcPtr[frameHeaderSize-1], headerChecksum_invalid); } +#endif /* save */ dctx->frameInfo.blockMode = (LZ4F_blockMode_t)blockMode; dctx->frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)blockChecksumFlag; dctx->frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)contentChecksumFlag; dctx->frameInfo.blockSizeID = (LZ4F_blockSizeID_t)blockSizeID; - dctx->maxBlockSize = LZ4F_getBlockSize(blockSizeID); + dctx->maxBlockSize = LZ4F_getBlockSize((LZ4F_blockSizeID_t)blockSizeID); if (contentSizeFlag) - dctx->frameRemainingSize = - dctx->frameInfo.contentSize = LZ4F_readLE64(srcPtr+6); + dctx->frameRemainingSize = dctx->frameInfo.contentSize = LZ4F_readLE64(srcPtr+6); if (dictIDFlag) dctx->frameInfo.dictID = LZ4F_readLE32(srcPtr + frameHeaderSize - 5); @@ -1082,6 +1380,36 @@ static size_t LZ4F_decodeHeader(LZ4F_dctx* dctx, const void* src, size_t srcSize } +/*! LZ4F_headerSize() : + * @return : size of frame header + * or an error code, which can be tested using LZ4F_isError() + */ +size_t LZ4F_headerSize(const void* src, size_t srcSize) +{ + RETURN_ERROR_IF(src == NULL, srcPtr_wrong); + + /* minimal srcSize to determine header size */ + if (srcSize < LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH) + RETURN_ERROR(frameHeader_incomplete); + + /* special case : skippable frames */ + if ((LZ4F_readLE32(src) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) + return 8; + + /* control magic number */ +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (LZ4F_readLE32(src) != LZ4F_MAGICNUMBER) + RETURN_ERROR(frameType_unknown); +#endif + + /* Frame Header Size */ + { BYTE const FLG = ((const BYTE*)src)[4]; + U32 const contentSizeFlag = (FLG>>3) & _1BIT; + U32 const dictIDFlag = FLG & _1BIT; + return minFHSize + (contentSizeFlag?8:0) + (dictIDFlag?4:0); + } +} + /*! LZ4F_getFrameInfo() : * This function extracts frame parameters (max blockSize, frame checksum, etc.). * Usage is optional. Objective is to provide relevant information for allocation purposes. @@ -1097,10 +1425,12 @@ static size_t LZ4F_decodeHeader(LZ4F_dctx* dctx, const void* src, size_t srcSize * note 1 : in case of error, dctx is not modified. Decoding operations can resume from where they stopped. * note 2 : frame parameters are *copied into* an already allocated LZ4F_frameInfo_t structure. */ -LZ4F_errorCode_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, LZ4F_frameInfo_t* frameInfoPtr, - const void* srcBuffer, size_t* srcSizePtr) +LZ4F_errorCode_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr) { - if (dctx->dStage > dstage_storeFrameHeader) { /* assumption : dstage_* header enum at beginning of range */ + LZ4F_STATIC_ASSERT(dstage_getFrameHeader < dstage_storeFrameHeader); + if (dctx->dStage > dstage_storeFrameHeader) { /* frameInfo already decoded */ size_t o=0, i=0; *srcSizePtr = 0; @@ -1111,55 +1441,66 @@ LZ4F_errorCode_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, LZ4F_frameInfo_t* frameInfoP if (dctx->dStage == dstage_storeFrameHeader) { /* frame decoding already started, in the middle of header => automatic fail */ *srcSizePtr = 0; - return err0r(LZ4F_ERROR_frameDecoding_alreadyStarted); + RETURN_ERROR(frameDecoding_alreadyStarted); } else { - size_t decodeResult; size_t const hSize = LZ4F_headerSize(srcBuffer, *srcSizePtr); if (LZ4F_isError(hSize)) { *srcSizePtr=0; return hSize; } if (*srcSizePtr < hSize) { *srcSizePtr=0; - return err0r(LZ4F_ERROR_frameHeader_incomplete); + RETURN_ERROR(frameHeader_incomplete); } - decodeResult = LZ4F_decodeHeader(dctx, srcBuffer, hSize); - if (LZ4F_isError(decodeResult)) { - *srcSizePtr = 0; - } else { - *srcSizePtr = decodeResult; - decodeResult = BHSize; /* block header size */ - } - *frameInfoPtr = dctx->frameInfo; - return decodeResult; - } } + { size_t decodeResult = LZ4F_decodeHeader(dctx, srcBuffer, hSize); + if (LZ4F_isError(decodeResult)) { + *srcSizePtr = 0; + } else { + *srcSizePtr = decodeResult; + decodeResult = BHSize; /* block header size */ + } + *frameInfoPtr = dctx->frameInfo; + return decodeResult; + } } } } /* LZ4F_updateDict() : - * only used for LZ4F_blockLinked mode */ -static void LZ4F_updateDict(LZ4F_dctx* dctx, const BYTE* dstPtr, size_t dstSize, const BYTE* dstPtr0, unsigned withinTmp) + * only used for LZ4F_blockLinked mode + * Condition : @dstPtr != NULL + */ +static void LZ4F_updateDict(LZ4F_dctx* dctx, + const BYTE* dstPtr, size_t dstSize, const BYTE* dstBufferStart, + unsigned withinTmp) { - if (dctx->dictSize==0) - dctx->dict = (const BYTE*)dstPtr; /* priority to dictionary continuity */ + assert(dstPtr != NULL); + if (dctx->dictSize==0) dctx->dict = (const BYTE*)dstPtr; /* will lead to prefix mode */ + assert(dctx->dict != NULL); - if (dctx->dict + dctx->dictSize == dstPtr) { /* dictionary continuity */ + if (dctx->dict + dctx->dictSize == dstPtr) { /* prefix mode, everything within dstBuffer */ dctx->dictSize += dstSize; return; } - if (dstPtr - dstPtr0 + dstSize >= 64 KB) { /* dstBuffer large enough to become dictionary */ - dctx->dict = (const BYTE*)dstPtr0; - dctx->dictSize = dstPtr - dstPtr0 + dstSize; + assert(dstPtr >= dstBufferStart); + if ((size_t)(dstPtr - dstBufferStart) + dstSize >= 64 KB) { /* history in dstBuffer becomes large enough to become dictionary */ + dctx->dict = (const BYTE*)dstBufferStart; + dctx->dictSize = (size_t)(dstPtr - dstBufferStart) + dstSize; return; } - if ((withinTmp) && (dctx->dict == dctx->tmpOutBuffer)) { - /* assumption : dctx->dict + dctx->dictSize == dctx->tmpOut + dctx->tmpOutStart */ + assert(dstSize < 64 KB); /* if dstSize >= 64 KB, dictionary would be set into dstBuffer directly */ + + /* dstBuffer does not contain whole useful history (64 KB), so it must be saved within tmpOutBuffer */ + assert(dctx->tmpOutBuffer != NULL); + + if (withinTmp && (dctx->dict == dctx->tmpOutBuffer)) { /* continue history within tmpOutBuffer */ + /* withinTmp expectation : content of [dstPtr,dstSize] is same as [dict+dictSize,dstSize], so we just extend it */ + assert(dctx->dict + dctx->dictSize == dctx->tmpOut + dctx->tmpOutStart); dctx->dictSize += dstSize; return; } if (withinTmp) { /* copy relevant dict portion in front of tmpOut within tmpOutBuffer */ - size_t const preserveSize = dctx->tmpOut - dctx->tmpOutBuffer; + size_t const preserveSize = (size_t)(dctx->tmpOut - dctx->tmpOutBuffer); size_t copySize = 64 KB - dctx->tmpOutSize; const BYTE* const oldDictEnd = dctx->dict + dctx->dictSize - dctx->tmpOutStart; if (dctx->tmpOutSize > 64 KB) copySize = 0; @@ -1174,7 +1515,7 @@ static void LZ4F_updateDict(LZ4F_dctx* dctx, const BYTE* dstPtr, size_t dstSize, if (dctx->dict == dctx->tmpOutBuffer) { /* copy dst into tmp to complete dict */ if (dctx->dictSize + dstSize > dctx->maxBufferSize) { /* tmp buffer not large enough */ - size_t const preserveSize = 64 KB - dstSize; /* note : dstSize < 64 KB */ + size_t const preserveSize = 64 KB - dstSize; memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - preserveSize, preserveSize); dctx->dictSize = preserveSize; } @@ -1184,7 +1525,7 @@ static void LZ4F_updateDict(LZ4F_dctx* dctx, const BYTE* dstPtr, size_t dstSize, } /* join dict & dest into tmp */ - { size_t preserveSize = 64 KB - dstSize; /* note : dstSize < 64 KB */ + { size_t preserveSize = 64 KB - dstSize; if (preserveSize > dctx->dictSize) preserveSize = dctx->dictSize; memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - preserveSize, preserveSize); memcpy(dctx->tmpOutBuffer + preserveSize, dstPtr, dstSize); @@ -1194,7 +1535,6 @@ static void LZ4F_updateDict(LZ4F_dctx* dctx, const BYTE* dstPtr, size_t dstSize, } - /*! LZ4F_decompress() : * Call this function repetitively to regenerate compressed data in srcBuffer. * The function will attempt to decode up to *srcSizePtr bytes from srcBuffer @@ -1223,17 +1563,22 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, const BYTE* const srcEnd = srcStart + *srcSizePtr; const BYTE* srcPtr = srcStart; BYTE* const dstStart = (BYTE*)dstBuffer; - BYTE* const dstEnd = dstStart + *dstSizePtr; + BYTE* const dstEnd = dstStart ? dstStart + *dstSizePtr : NULL; BYTE* dstPtr = dstStart; const BYTE* selectedIn = NULL; unsigned doAnotherStage = 1; size_t nextSrcSizeHint = 1; - memset(&optionsNull, 0, sizeof(optionsNull)); + DEBUGLOG(5, "LZ4F_decompress : %p,%u => %p,%u", + srcBuffer, (unsigned)*srcSizePtr, dstBuffer, (unsigned)*dstSizePtr); + if (dstBuffer == NULL) assert(*dstSizePtr == 0); + MEM_INIT(&optionsNull, 0, sizeof(optionsNull)); if (decompressOptionsPtr==NULL) decompressOptionsPtr = &optionsNull; *srcSizePtr = 0; *dstSizePtr = 0; + assert(dctx != NULL); + dctx->skipChecksum |= (decompressOptionsPtr->skipChecksums != 0); /* once set, disable for the remainder of the frame */ /* behaves as a state machine */ @@ -1243,19 +1588,21 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, { case dstage_getFrameHeader: + DEBUGLOG(6, "dstage_getFrameHeader"); if ((size_t)(srcEnd-srcPtr) >= maxFHSize) { /* enough to decode - shortcut */ - size_t const hSize = LZ4F_decodeHeader(dctx, srcPtr, srcEnd-srcPtr); /* will update dStage appropriately */ - if (LZ4F_isError(hSize)) return hSize; + size_t const hSize = LZ4F_decodeHeader(dctx, srcPtr, (size_t)(srcEnd-srcPtr)); /* will update dStage appropriately */ + FORWARD_IF_ERROR(hSize); srcPtr += hSize; break; } dctx->tmpInSize = 0; if (srcEnd-srcPtr == 0) return minFHSize; /* 0-size input */ - dctx->tmpInTarget = minFHSize; /* minimum to attempt decode */ + dctx->tmpInTarget = minFHSize; /* minimum size to decode header */ dctx->dStage = dstage_storeFrameHeader; /* fall-through */ case dstage_storeFrameHeader: + DEBUGLOG(6, "dstage_storeFrameHeader"); { size_t const sizeToCopy = MIN(dctx->tmpInTarget - dctx->tmpInSize, (size_t)(srcEnd - srcPtr)); memcpy(dctx->header + dctx->tmpInSize, srcPtr, sizeToCopy); dctx->tmpInSize += sizeToCopy; @@ -1266,26 +1613,23 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, doAnotherStage = 0; /* not enough src data, ask for some more */ break; } - { size_t const hSize = LZ4F_decodeHeader(dctx, dctx->header, dctx->tmpInTarget); /* will update dStage appropriately */ - if (LZ4F_isError(hSize)) return hSize; - } + FORWARD_IF_ERROR( LZ4F_decodeHeader(dctx, dctx->header, dctx->tmpInTarget) ); /* will update dStage appropriately */ break; case dstage_init: - if (dctx->frameInfo.contentChecksumFlag) XXH32_reset(&(dctx->xxh), 0); + DEBUGLOG(6, "dstage_init"); + if (dctx->frameInfo.contentChecksumFlag) (void)XXH32_reset(&(dctx->xxh), 0); /* internal buffers allocation */ { size_t const bufferNeeded = dctx->maxBlockSize - + ((dctx->frameInfo.blockMode==LZ4F_blockLinked) * 128 KB); + + ((dctx->frameInfo.blockMode==LZ4F_blockLinked) ? 128 KB : 0); if (bufferNeeded > dctx->maxBufferSize) { /* tmp buffers too small */ dctx->maxBufferSize = 0; /* ensure allocation will be re-attempted on next entry*/ - FREEMEM(dctx->tmpIn); - dctx->tmpIn = (BYTE*)ALLOCATOR(dctx->maxBlockSize + 4 /* block checksum */); - if (dctx->tmpIn == NULL) - return err0r(LZ4F_ERROR_allocation_failed); - FREEMEM(dctx->tmpOutBuffer); - dctx->tmpOutBuffer= (BYTE*)ALLOCATOR(bufferNeeded); - if (dctx->tmpOutBuffer== NULL) - return err0r(LZ4F_ERROR_allocation_failed); + LZ4F_free(dctx->tmpIn, dctx->cmem); + dctx->tmpIn = (BYTE*)LZ4F_malloc(dctx->maxBlockSize + BFSize /* block checksum */, dctx->cmem); + RETURN_ERROR_IF(dctx->tmpIn == NULL, allocation_failed); + LZ4F_free(dctx->tmpOutBuffer, dctx->cmem); + dctx->tmpOutBuffer= (BYTE*)LZ4F_malloc(bufferNeeded, dctx->cmem); + RETURN_ERROR_IF(dctx->tmpOutBuffer== NULL, allocation_failed); dctx->maxBufferSize = bufferNeeded; } } dctx->tmpInSize = 0; @@ -1325,19 +1669,23 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, } /* if (dctx->dStage == dstage_storeBlockHeader) */ /* decode block header */ - { size_t const nextCBlockSize = LZ4F_readLE32(selectedIn) & 0x7FFFFFFFU; - size_t const crcSize = dctx->frameInfo.blockChecksumFlag * 4; - if (nextCBlockSize==0) { /* frameEnd signal, no more block */ + { U32 const blockHeader = LZ4F_readLE32(selectedIn); + size_t const nextCBlockSize = blockHeader & 0x7FFFFFFFU; + size_t const crcSize = dctx->frameInfo.blockChecksumFlag * BFSize; + if (blockHeader==0) { /* frameEnd signal, no more block */ + DEBUGLOG(5, "end of frame"); dctx->dStage = dstage_getSuffix; break; } - if (nextCBlockSize > dctx->maxBlockSize) - return err0r(LZ4F_ERROR_maxBlockSize_invalid); - if (LZ4F_readLE32(selectedIn) & LZ4F_BLOCKUNCOMPRESSED_FLAG) { + if (nextCBlockSize > dctx->maxBlockSize) { + RETURN_ERROR(maxBlockSize_invalid); + } + if (blockHeader & LZ4F_BLOCKUNCOMPRESSED_FLAG) { /* next block is uncompressed */ dctx->tmpInTarget = nextCBlockSize; + DEBUGLOG(5, "next block is uncompressed (size %u)", (U32)nextCBlockSize); if (dctx->frameInfo.blockChecksumFlag) { - XXH32_reset(&dctx->blockChecksum, 0); + (void)XXH32_reset(&dctx->blockChecksum, 0); } dctx->dStage = dstage_copyDirect; break; @@ -1345,28 +1693,36 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, /* next block is a compressed block */ dctx->tmpInTarget = nextCBlockSize + crcSize; dctx->dStage = dstage_getCBlock; - if (dstPtr==dstEnd) { - nextSrcSizeHint = nextCBlockSize + crcSize + BHSize; + if (dstPtr==dstEnd || srcPtr==srcEnd) { + nextSrcSizeHint = BHSize + nextCBlockSize + crcSize; doAnotherStage = 0; } break; } case dstage_copyDirect: /* uncompressed block */ - { size_t const minBuffSize = MIN((size_t)(srcEnd-srcPtr), (size_t)(dstEnd-dstPtr)); - size_t const sizeToCopy = MIN(dctx->tmpInTarget, minBuffSize); - memcpy(dstPtr, srcPtr, sizeToCopy); - if (dctx->frameInfo.blockChecksumFlag) { - XXH32_update(&dctx->blockChecksum, srcPtr, sizeToCopy); - } - if (dctx->frameInfo.contentChecksumFlag) - XXH32_update(&dctx->xxh, srcPtr, sizeToCopy); - if (dctx->frameInfo.contentSize) - dctx->frameRemainingSize -= sizeToCopy; + DEBUGLOG(6, "dstage_copyDirect"); + { size_t sizeToCopy; + if (dstPtr == NULL) { + sizeToCopy = 0; + } else { + size_t const minBuffSize = MIN((size_t)(srcEnd-srcPtr), (size_t)(dstEnd-dstPtr)); + sizeToCopy = MIN(dctx->tmpInTarget, minBuffSize); + memcpy(dstPtr, srcPtr, sizeToCopy); + if (!dctx->skipChecksum) { + if (dctx->frameInfo.blockChecksumFlag) { + (void)XXH32_update(&dctx->blockChecksum, srcPtr, sizeToCopy); + } + if (dctx->frameInfo.contentChecksumFlag) + (void)XXH32_update(&dctx->xxh, srcPtr, sizeToCopy); + } + if (dctx->frameInfo.contentSize) + dctx->frameRemainingSize -= sizeToCopy; - /* history management (linked blocks only)*/ - if (dctx->frameInfo.blockMode == LZ4F_blockLinked) - LZ4F_updateDict(dctx, dstPtr, sizeToCopy, dstStart, 0); + /* history management (linked blocks only)*/ + if (dctx->frameInfo.blockMode == LZ4F_blockLinked) { + LZ4F_updateDict(dctx, dstPtr, sizeToCopy, dstStart, 0); + } } srcPtr += sizeToCopy; dstPtr += sizeToCopy; @@ -1379,15 +1735,16 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, break; } dctx->tmpInTarget -= sizeToCopy; /* need to copy more */ - nextSrcSizeHint = dctx->tmpInTarget + - + dctx->frameInfo.contentChecksumFlag * 4 /* block checksum */ - + BHSize /* next header size */; - doAnotherStage = 0; - break; } + nextSrcSizeHint = dctx->tmpInTarget + + +(dctx->frameInfo.blockChecksumFlag ? BFSize : 0) + + BHSize /* next header size */; + doAnotherStage = 0; + break; /* check block checksum for recently transferred uncompressed block */ case dstage_getBlockChecksum: + DEBUGLOG(6, "dstage_getBlockChecksum"); { const void* crcSrc; if ((srcEnd-srcPtr >= 4) && (dctx->tmpInSize==0)) { crcSrc = srcPtr; @@ -1404,16 +1761,26 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, } crcSrc = dctx->header; } - { U32 const readCRC = LZ4F_readLE32(crcSrc); + if (!dctx->skipChecksum) { + U32 const readCRC = LZ4F_readLE32(crcSrc); U32 const calcCRC = XXH32_digest(&dctx->blockChecksum); - if (readCRC != calcCRC) - return err0r(LZ4F_ERROR_blockChecksum_invalid); - } - } +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + DEBUGLOG(6, "compare block checksum"); + if (readCRC != calcCRC) { + DEBUGLOG(4, "incorrect block checksum: %08X != %08X", + readCRC, calcCRC); + RETURN_ERROR(blockChecksum_invalid); + } +#else + (void)readCRC; + (void)calcCRC; +#endif + } } dctx->dStage = dstage_getBlockHeader; /* new block */ break; case dstage_getCBlock: + DEBUGLOG(6, "dstage_getCBlock"); if ((size_t)(srcEnd-srcPtr) < dctx->tmpInTarget) { dctx->tmpInSize = 0; dctx->dStage = dstage_storeCBlock; @@ -1423,7 +1790,7 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, selectedIn = srcPtr; srcPtr += dctx->tmpInTarget; - if (0) /* jump over next block */ + if (0) /* always jump over next block */ case dstage_storeCBlock: { size_t const wantedData = dctx->tmpInTarget - dctx->tmpInSize; size_t const inputLeft = (size_t)(srcEnd-srcPtr); @@ -1432,99 +1799,132 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, dctx->tmpInSize += sizeToCopy; srcPtr += sizeToCopy; if (dctx->tmpInSize < dctx->tmpInTarget) { /* need more input */ - nextSrcSizeHint = (dctx->tmpInTarget - dctx->tmpInSize) + BHSize; - doAnotherStage=0; + nextSrcSizeHint = (dctx->tmpInTarget - dctx->tmpInSize) + + (dctx->frameInfo.blockChecksumFlag ? BFSize : 0) + + BHSize /* next header size */; + doAnotherStage = 0; break; } selectedIn = dctx->tmpIn; } /* At this stage, input is large enough to decode a block */ + + /* First, decode and control block checksum if it exists */ if (dctx->frameInfo.blockChecksumFlag) { + assert(dctx->tmpInTarget >= 4); dctx->tmpInTarget -= 4; assert(selectedIn != NULL); /* selectedIn is defined at this stage (either srcPtr, or dctx->tmpIn) */ { U32 const readBlockCrc = LZ4F_readLE32(selectedIn + dctx->tmpInTarget); U32 const calcBlockCrc = XXH32(selectedIn, dctx->tmpInTarget, 0); - if (readBlockCrc != calcBlockCrc) - return err0r(LZ4F_ERROR_blockChecksum_invalid); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + RETURN_ERROR_IF(readBlockCrc != calcBlockCrc, blockChecksum_invalid); +#else + (void)readBlockCrc; + (void)calcBlockCrc; +#endif } } - if ((size_t)(dstEnd-dstPtr) >= dctx->maxBlockSize) { - /* enough capacity in `dst` to decompress directly there */ - int const decodedSize = LZ4_decompress_safe_usingDict( + /* decode directly into destination buffer if there is enough room */ + if ( ((size_t)(dstEnd-dstPtr) >= dctx->maxBlockSize) + /* unless the dictionary is stored in tmpOut: + * in which case it's faster to decode within tmpOut + * to benefit from prefix speedup */ + && !(dctx->dict!= NULL && (const BYTE*)dctx->dict + dctx->dictSize == dctx->tmpOut) ) + { + const char* dict = (const char*)dctx->dict; + size_t dictSize = dctx->dictSize; + int decodedSize; + assert(dstPtr != NULL); + if (dict && dictSize > 1 GB) { + /* overflow control : dctx->dictSize is an int, avoid truncation / sign issues */ + dict += dictSize - 64 KB; + dictSize = 64 KB; + } + decodedSize = LZ4_decompress_safe_usingDict( (const char*)selectedIn, (char*)dstPtr, (int)dctx->tmpInTarget, (int)dctx->maxBlockSize, - (const char*)dctx->dict, (int)dctx->dictSize); - if (decodedSize < 0) return err0r(LZ4F_ERROR_GENERIC); /* decompression failed */ - if (dctx->frameInfo.contentChecksumFlag) - XXH32_update(&(dctx->xxh), dstPtr, decodedSize); + dict, (int)dictSize); + RETURN_ERROR_IF(decodedSize < 0, decompressionFailed); + if ((dctx->frameInfo.contentChecksumFlag) && (!dctx->skipChecksum)) + XXH32_update(&(dctx->xxh), dstPtr, (size_t)decodedSize); if (dctx->frameInfo.contentSize) - dctx->frameRemainingSize -= decodedSize; + dctx->frameRemainingSize -= (size_t)decodedSize; /* dictionary management */ - if (dctx->frameInfo.blockMode==LZ4F_blockLinked) - LZ4F_updateDict(dctx, dstPtr, decodedSize, dstStart, 0); + if (dctx->frameInfo.blockMode==LZ4F_blockLinked) { + LZ4F_updateDict(dctx, dstPtr, (size_t)decodedSize, dstStart, 0); + } dstPtr += decodedSize; - dctx->dStage = dstage_getBlockHeader; + dctx->dStage = dstage_getBlockHeader; /* end of block, let's get another one */ break; } /* not enough place into dst : decode into tmpOut */ - /* ensure enough place for tmpOut */ + + /* manage dictionary */ if (dctx->frameInfo.blockMode == LZ4F_blockLinked) { if (dctx->dict == dctx->tmpOutBuffer) { + /* truncate dictionary to 64 KB if too big */ if (dctx->dictSize > 128 KB) { memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - 64 KB, 64 KB); dctx->dictSize = 64 KB; } dctx->tmpOut = dctx->tmpOutBuffer + dctx->dictSize; - } else { /* dict not within tmp */ + } else { /* dict not within tmpOut */ size_t const reservedDictSpace = MIN(dctx->dictSize, 64 KB); dctx->tmpOut = dctx->tmpOutBuffer + reservedDictSpace; - } - } + } } - /* Decode block */ - { int const decodedSize = LZ4_decompress_safe_usingDict( + /* Decode block into tmpOut */ + { const char* dict = (const char*)dctx->dict; + size_t dictSize = dctx->dictSize; + int decodedSize; + if (dict && dictSize > 1 GB) { + /* the dictSize param is an int, avoid truncation / sign issues */ + dict += dictSize - 64 KB; + dictSize = 64 KB; + } + decodedSize = LZ4_decompress_safe_usingDict( (const char*)selectedIn, (char*)dctx->tmpOut, (int)dctx->tmpInTarget, (int)dctx->maxBlockSize, - (const char*)dctx->dict, (int)dctx->dictSize); - if (decodedSize < 0) /* decompression failed */ - return err0r(LZ4F_ERROR_decompressionFailed); - if (dctx->frameInfo.contentChecksumFlag) - XXH32_update(&(dctx->xxh), dctx->tmpOut, decodedSize); + dict, (int)dictSize); + RETURN_ERROR_IF(decodedSize < 0, decompressionFailed); + if (dctx->frameInfo.contentChecksumFlag && !dctx->skipChecksum) + XXH32_update(&(dctx->xxh), dctx->tmpOut, (size_t)decodedSize); if (dctx->frameInfo.contentSize) - dctx->frameRemainingSize -= decodedSize; - dctx->tmpOutSize = decodedSize; + dctx->frameRemainingSize -= (size_t)decodedSize; + dctx->tmpOutSize = (size_t)decodedSize; dctx->tmpOutStart = 0; dctx->dStage = dstage_flushOut; } /* fall-through */ case dstage_flushOut: /* flush decoded data from tmpOut to dstBuffer */ - { size_t const sizeToCopy = MIN(dctx->tmpOutSize - dctx->tmpOutStart, (size_t)(dstEnd-dstPtr)); + DEBUGLOG(6, "dstage_flushOut"); + if (dstPtr != NULL) { + size_t const sizeToCopy = MIN(dctx->tmpOutSize - dctx->tmpOutStart, (size_t)(dstEnd-dstPtr)); memcpy(dstPtr, dctx->tmpOut + dctx->tmpOutStart, sizeToCopy); /* dictionary management */ - if (dctx->frameInfo.blockMode==LZ4F_blockLinked) - LZ4F_updateDict(dctx, dstPtr, sizeToCopy, dstStart, 1); + if (dctx->frameInfo.blockMode == LZ4F_blockLinked) + LZ4F_updateDict(dctx, dstPtr, sizeToCopy, dstStart, 1 /*withinTmp*/); dctx->tmpOutStart += sizeToCopy; dstPtr += sizeToCopy; - - if (dctx->tmpOutStart == dctx->tmpOutSize) { /* all flushed */ - dctx->dStage = dstage_getBlockHeader; /* get next block */ - break; - } - nextSrcSizeHint = BHSize; - doAnotherStage = 0; /* still some data to flush */ + } + if (dctx->tmpOutStart == dctx->tmpOutSize) { /* all flushed */ + dctx->dStage = dstage_getBlockHeader; /* get next block */ break; } + /* could not flush everything : stop there, just request a block header */ + doAnotherStage = 0; + nextSrcSizeHint = BHSize; + break; case dstage_getSuffix: - if (dctx->frameRemainingSize) - return err0r(LZ4F_ERROR_frameSize_wrong); /* incorrect frame size decoded */ + RETURN_ERROR_IF(dctx->frameRemainingSize, frameSize_wrong); /* incorrect frame size decoded */ if (!dctx->frameInfo.contentChecksumFlag) { /* no checksum, frame is completed */ nextSrcSizeHint = 0; LZ4F_resetDecompressionContext(dctx); @@ -1555,16 +1955,21 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, selectedIn = dctx->tmpIn; } /* if (dctx->dStage == dstage_storeSuffix) */ - /* case dstage_checkSuffix: */ /* no direct call, avoid scan-build warning */ - { U32 const readCRC = LZ4F_readLE32(selectedIn); + /* case dstage_checkSuffix: */ /* no direct entry, avoid initialization risks */ + if (!dctx->skipChecksum) { + U32 const readCRC = LZ4F_readLE32(selectedIn); U32 const resultCRC = XXH32_digest(&(dctx->xxh)); - if (readCRC != resultCRC) - return err0r(LZ4F_ERROR_contentChecksum_invalid); - nextSrcSizeHint = 0; - LZ4F_resetDecompressionContext(dctx); - doAnotherStage = 0; - break; +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + RETURN_ERROR_IF(readCRC != resultCRC, contentChecksum_invalid); +#else + (void)readCRC; + (void)resultCRC; +#endif } + nextSrcSizeHint = 0; + LZ4F_resetDecompressionContext(dctx); + doAnotherStage = 0; + break; case dstage_getSFrameSize: if ((srcEnd - srcPtr) >= 4) { @@ -1579,8 +1984,7 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, if (dctx->dStage == dstage_storeSFrameSize) case dstage_storeSFrameSize: - { - size_t const sizeToCopy = MIN(dctx->tmpInTarget - dctx->tmpInSize, + { size_t const sizeToCopy = MIN(dctx->tmpInTarget - dctx->tmpInSize, (size_t)(srcEnd - srcPtr) ); memcpy(dctx->header + dctx->tmpInSize, srcPtr, sizeToCopy); srcPtr += sizeToCopy; @@ -1594,7 +1998,7 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, selectedIn = dctx->header + 4; } /* if (dctx->dStage == dstage_storeSFrameSize) */ - /* case dstage_decodeSFrameSize: */ /* no direct access */ + /* case dstage_decodeSFrameSize: */ /* no direct entry */ { size_t const SFrameSize = LZ4F_readLE32(selectedIn); dctx->frameInfo.contentSize = SFrameSize; dctx->tmpInTarget = SFrameSize; @@ -1613,25 +2017,26 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, LZ4F_resetDecompressionContext(dctx); break; } - } + } /* switch (dctx->dStage) */ } /* while (doAnotherStage) */ - /* preserve history within tmp whenever necessary */ + /* preserve history within tmpOut whenever necessary */ LZ4F_STATIC_ASSERT((unsigned)dstage_init == 2); if ( (dctx->frameInfo.blockMode==LZ4F_blockLinked) /* next block will use up to 64KB from previous ones */ && (dctx->dict != dctx->tmpOutBuffer) /* dictionary is not already within tmp */ + && (dctx->dict != NULL) /* dictionary exists */ && (!decompressOptionsPtr->stableDst) /* cannot rely on dst data to remain there for next call */ && ((unsigned)(dctx->dStage)-2 < (unsigned)(dstage_getSuffix)-2) ) /* valid stages : [init ... getSuffix[ */ { if (dctx->dStage == dstage_flushOut) { - size_t const preserveSize = dctx->tmpOut - dctx->tmpOutBuffer; + size_t const preserveSize = (size_t)(dctx->tmpOut - dctx->tmpOutBuffer); size_t copySize = 64 KB - dctx->tmpOutSize; const BYTE* oldDictEnd = dctx->dict + dctx->dictSize - dctx->tmpOutStart; if (dctx->tmpOutSize > 64 KB) copySize = 0; if (copySize > preserveSize) copySize = preserveSize; + assert(dctx->tmpOutBuffer != NULL); - if (copySize > 0) - memcpy(dctx->tmpOutBuffer + preserveSize - copySize, oldDictEnd - copySize, copySize); + memcpy(dctx->tmpOutBuffer + preserveSize - copySize, oldDictEnd - copySize, copySize); dctx->dict = dctx->tmpOutBuffer; dctx->dictSize = preserveSize + dctx->tmpOutStart; @@ -1639,8 +2044,7 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, const BYTE* const oldDictEnd = dctx->dict + dctx->dictSize; size_t const newDictSize = MIN(dctx->dictSize, 64 KB); - if (newDictSize > 0) - memcpy(dctx->tmpOutBuffer, oldDictEnd - newDictSize, newDictSize); + memcpy(dctx->tmpOutBuffer, oldDictEnd - newDictSize, newDictSize); dctx->dict = dctx->tmpOutBuffer; dctx->dictSize = newDictSize; @@ -1648,8 +2052,8 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx, } } - *srcSizePtr = (srcPtr - srcStart); - *dstSizePtr = (dstPtr - dstStart); + *srcSizePtr = (size_t)(srcPtr - srcStart); + *dstSizePtr = (size_t)(dstPtr - dstStart); return nextSrcSizeHint; } diff --git a/lz4libs/lz4frame.h b/lz4libs/lz4frame.h index eb55e45e..1bdf6c4f 100644 --- a/lz4libs/lz4frame.h +++ b/lz4libs/lz4frame.h @@ -1,7 +1,7 @@ /* - LZ4 auto-framing library + LZ4F - LZ4-Frame library Header File - Copyright (C) 2011-2017, Yann Collet. + Copyright (C) 2011-2020, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) Redistribution and use in source and binary forms, with or without @@ -32,10 +32,14 @@ - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c */ -/* LZ4F is a stand-alone API to create LZ4-compressed frames - * conformant with specification v1.5.1. - * It also offers streaming capabilities. - * lz4.h is not required when using lz4frame.h. +/* LZ4F is a stand-alone API able to create and decode LZ4 frames + * conformant with specification v1.6.1 in doc/lz4_Frame_format.md . + * Generated frames are compatible with `lz4` CLI. + * + * LZ4F also offers streaming capabilities. + * + * lz4.h is not required when using lz4frame.h, + * except to extract common constants such as LZ4_VERSION_NUMBER. * */ #ifndef LZ4F_H_09782039843 @@ -50,29 +54,34 @@ extern "C" { /** - Introduction - - lz4frame.h implements LZ4 frame specification (doc/lz4_Frame_format.md). - lz4frame.h provides frame compression functions that take care - of encoding standard metadata alongside LZ4-compressed blocks. -*/ + * Introduction + * + * lz4frame.h implements LZ4 frame specification: see doc/lz4_Frame_format.md . + * LZ4 Frames are compatible with `lz4` CLI, + * and designed to be interoperable with any system. +**/ /*-*************************************************************** * Compiler specifics *****************************************************************/ /* LZ4_DLL_EXPORT : * Enable exporting of functions when building a Windows DLL - * LZ4FLIB_API : + * LZ4FLIB_VISIBILITY : * Control library symbols visibility. */ +#ifndef LZ4FLIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4FLIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4FLIB_VISIBILITY +# endif +#endif #if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) -# define LZ4FLIB_API __declspec(dllexport) +# define LZ4FLIB_API __declspec(dllexport) LZ4FLIB_VISIBILITY #elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) -# define LZ4FLIB_API __declspec(dllimport) -#elif defined(__GNUC__) && (__GNUC__ >= 4) -# define LZ4FLIB_API __attribute__ ((__visibility__ ("default"))) +# define LZ4FLIB_API __declspec(dllimport) LZ4FLIB_VISIBILITY #else -# define LZ4FLIB_API +# define LZ4FLIB_API LZ4FLIB_VISIBILITY #endif #ifdef LZ4F_DISABLE_DEPRECATE_WARNINGS @@ -93,13 +102,13 @@ extern "C" { **************************************/ typedef size_t LZ4F_errorCode_t; -LZ4FLIB_API unsigned LZ4F_isError(LZ4F_errorCode_t code); /**< tells if a `LZ4F_errorCode_t` function result is an error code */ -LZ4FLIB_API const char* LZ4F_getErrorName(LZ4F_errorCode_t code); /**< return error code string; useful for debugging */ +LZ4FLIB_API unsigned LZ4F_isError(LZ4F_errorCode_t code); /**< tells when a function result is an error code */ +LZ4FLIB_API const char* LZ4F_getErrorName(LZ4F_errorCode_t code); /**< return error code string; for debugging */ /*-************************************ * Frame compression types - **************************************/ + ************************************* */ /* #define LZ4F_ENABLE_OBSOLETE_ENUMS // uncomment to enable obsolete enums */ #ifdef LZ4F_ENABLE_OBSOLETE_ENUMS # define LZ4F_OBSOLETE_ENUM(x) , LZ4F_DEPRECATE(x) = LZ4F_##x @@ -109,7 +118,8 @@ LZ4FLIB_API const char* LZ4F_getErrorName(LZ4F_errorCode_t code); /**< return /* The larger the block size, the (slightly) better the compression ratio, * though there are diminishing returns. - * Larger blocks also increase memory usage on both compression and decompression sides. */ + * Larger blocks also increase memory usage on both compression and decompression sides. + */ typedef enum { LZ4F_default=0, LZ4F_max64KB=4, @@ -159,38 +169,48 @@ typedef LZ4F_contentChecksum_t contentChecksum_t; /*! LZ4F_frameInfo_t : * makes it possible to set or read frame parameters. - * It's not required to set all fields, as long as the structure was initially memset() to zero. - * For all fields, 0 sets it to default value */ + * Structure must be first init to 0, using memset() or LZ4F_INIT_FRAMEINFO, + * setting all parameters to default. + * It's then possible to update selectively some parameters */ typedef struct { - LZ4F_blockSizeID_t blockSizeID; /* max64KB, max256KB, max1MB, max4MB ; 0 == default */ - LZ4F_blockMode_t blockMode; /* LZ4F_blockLinked, LZ4F_blockIndependent ; 0 == default */ - LZ4F_contentChecksum_t contentChecksumFlag; /* if enabled, frame is terminated with a 32-bits checksum of decompressed data ; 0 == disabled (default) */ - LZ4F_frameType_t frameType; /* read-only field : LZ4F_frame or LZ4F_skippableFrame */ - unsigned long long contentSize; /* Size of uncompressed content ; 0 == unknown */ - unsigned dictID; /* Dictionary ID, sent by the compressor to help decoder select the correct dictionary; 0 == no dictID provided */ - LZ4F_blockChecksum_t blockChecksumFlag; /* if enabled, each block is followed by a checksum of block's compressed data ; 0 == disabled (default) */ + LZ4F_blockSizeID_t blockSizeID; /* max64KB, max256KB, max1MB, max4MB; 0 == default */ + LZ4F_blockMode_t blockMode; /* LZ4F_blockLinked, LZ4F_blockIndependent; 0 == default */ + LZ4F_contentChecksum_t contentChecksumFlag; /* 1: frame terminated with 32-bit checksum of decompressed data; 0: disabled (default) */ + LZ4F_frameType_t frameType; /* read-only field : LZ4F_frame or LZ4F_skippableFrame */ + unsigned long long contentSize; /* Size of uncompressed content ; 0 == unknown */ + unsigned dictID; /* Dictionary ID, sent by compressor to help decoder select correct dictionary; 0 == no dictID provided */ + LZ4F_blockChecksum_t blockChecksumFlag; /* 1: each block followed by a checksum of block's compressed data; 0: disabled (default) */ } LZ4F_frameInfo_t; +#define LZ4F_INIT_FRAMEINFO { LZ4F_default, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame, 0ULL, 0U, LZ4F_noBlockChecksum } /* v1.8.3+ */ + /*! LZ4F_preferences_t : - * makes it possible to supply detailed compression parameters to the stream interface. - * It's not required to set all fields, as long as the structure was initially memset() to zero. + * makes it possible to supply advanced compression instructions to streaming interface. + * Structure must be first init to 0, using memset() or LZ4F_INIT_PREFERENCES, + * setting all parameters to default. * All reserved fields must be set to zero. */ typedef struct { LZ4F_frameInfo_t frameInfo; - int compressionLevel; /* 0 == default (fast mode); values above LZ4HC_CLEVEL_MAX count as LZ4HC_CLEVEL_MAX; values below 0 trigger "fast acceleration", proportional to value */ - unsigned autoFlush; /* 1 == always flush, to reduce usage of internal buffers */ - unsigned reserved[4]; /* must be zero for forward compatibility */ + int compressionLevel; /* 0: default (fast mode); values > LZ4HC_CLEVEL_MAX count as LZ4HC_CLEVEL_MAX; values < 0 trigger "fast acceleration" */ + unsigned autoFlush; /* 1: always flush; reduces usage of internal buffers */ + unsigned favorDecSpeed; /* 1: parser favors decompression speed vs compression ratio. Only works for high compression modes (>= LZ4HC_CLEVEL_OPT_MIN) */ /* v1.8.2+ */ + unsigned reserved[3]; /* must be zero for forward compatibility */ } LZ4F_preferences_t; -LZ4FLIB_API int LZ4F_compressionLevel_max(void); +#define LZ4F_INIT_PREFERENCES { LZ4F_INIT_FRAMEINFO, 0, 0u, 0u, { 0u, 0u, 0u } } /* v1.8.3+ */ /*-********************************* * Simple compression function ***********************************/ + +LZ4FLIB_API int LZ4F_compressionLevel_max(void); /* v1.8.0+ */ + /*! LZ4F_compressFrameBound() : - * Returns the maximum possible size of a frame compressed with LZ4F_compressFrame() given srcSize content and preferences. - * Note : this result is only usable with LZ4F_compressFrame(), not with multi-segments compression. + * Returns the maximum possible compressed size with LZ4F_compressFrame() given srcSize and preferences. + * `preferencesPtr` is optional. It can be replaced by NULL, in which case, the function will assume default preferences. + * Note : this result is only usable with LZ4F_compressFrame(). + * It may also be relevant to LZ4F_compressUpdate() _only if_ no flush() operation is ever performed. */ LZ4FLIB_API size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr); @@ -210,7 +230,7 @@ LZ4FLIB_API size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, * Advanced compression functions *************************************/ typedef struct LZ4F_cctx_s LZ4F_cctx; /* incomplete type */ -typedef LZ4F_cctx* LZ4F_compressionContext_t; /* for compatibility with previous API version */ +typedef LZ4F_cctx* LZ4F_compressionContext_t; /* for compatibility with older APIs, prefer using LZ4F_cctx */ typedef struct { unsigned stableSrc; /* 1 == src content will remain present on future calls to LZ4F_compress(); skip copying src content within tmp buffer */ @@ -219,23 +239,42 @@ typedef struct { /*--- Resource Management ---*/ -#define LZ4F_VERSION 100 +#define LZ4F_VERSION 100 /* This number can be used to check for an incompatible API breaking change */ LZ4FLIB_API unsigned LZ4F_getVersion(void); + /*! LZ4F_createCompressionContext() : - * The first thing to do is to create a compressionContext object, which will be used in all compression operations. - * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version. - * The version provided MUST be LZ4F_VERSION. It is intended to track potential version mismatch, notably when using DLL. - * The function will provide a pointer to a fully allocated LZ4F_cctx object. - * If @return != zero, there was an error during context creation. - * Object can release its memory using LZ4F_freeCompressionContext(); - */ + * The first thing to do is to create a compressionContext object, + * which will keep track of operation state during streaming compression. + * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version, + * and a pointer to LZ4F_cctx*, to write the resulting pointer into. + * @version provided MUST be LZ4F_VERSION. It is intended to track potential version mismatch, notably when using DLL. + * The function provides a pointer to a fully allocated LZ4F_cctx object. + * @cctxPtr MUST be != NULL. + * If @return != zero, context creation failed. + * A created compression context can be employed multiple times for consecutive streaming operations. + * Once all streaming compression jobs are completed, + * the state object can be released using LZ4F_freeCompressionContext(). + * Note1 : LZ4F_freeCompressionContext() is always successful. Its return value can be ignored. + * Note2 : LZ4F_freeCompressionContext() works fine with NULL input pointers (do nothing). +**/ LZ4FLIB_API LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_cctx** cctxPtr, unsigned version); LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_cctx* cctx); /*---- Compression ----*/ +#define LZ4F_HEADER_SIZE_MIN 7 /* LZ4 Frame header size can vary, depending on selected parameters */ #define LZ4F_HEADER_SIZE_MAX 19 + +/* Size in bytes of a block header in little-endian format. Highest bit indicates if block data is uncompressed */ +#define LZ4F_BLOCK_HEADER_SIZE 4 + +/* Size in bytes of a block checksum footer in little-endian format. */ +#define LZ4F_BLOCK_CHECKSUM_SIZE 4 + +/* Size in bytes of the content checksum. */ +#define LZ4F_CONTENT_CHECKSUM_SIZE 4 + /*! LZ4F_compressBegin() : * will write the frame header into dstBuffer. * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. @@ -248,45 +287,65 @@ LZ4FLIB_API size_t LZ4F_compressBegin(LZ4F_cctx* cctx, const LZ4F_preferences_t* prefsPtr); /*! LZ4F_compressBound() : - * Provides minimum dstCapacity for a given srcSize to guarantee operation success in worst case situations. + * Provides minimum dstCapacity required to guarantee success of + * LZ4F_compressUpdate(), given a srcSize and preferences, for a worst case scenario. + * When srcSize==0, LZ4F_compressBound() provides an upper bound for LZ4F_flush() and LZ4F_compressEnd() instead. + * Note that the result is only valid for a single invocation of LZ4F_compressUpdate(). + * When invoking LZ4F_compressUpdate() multiple times, + * if the output buffer is gradually filled up instead of emptied and re-used from its start, + * one must check if there is enough remaining capacity before each invocation, using LZ4F_compressBound(). + * @return is always the same for a srcSize and prefsPtr. * prefsPtr is optional : when NULL is provided, preferences will be set to cover worst case scenario. - * Result is always the same for a srcSize and prefsPtr, so it can be trusted to size reusable buffers. - * When srcSize==0, LZ4F_compressBound() provides an upper bound for LZ4F_flush() and LZ4F_compressEnd() operations. + * tech details : + * @return if automatic flushing is not enabled, includes the possibility that internal buffer might already be filled by up to (blockSize-1) bytes. + * It also includes frame footer (ending + checksum), since it might be generated by LZ4F_compressEnd(). + * @return doesn't include frame header, as it was already generated by LZ4F_compressBegin(). */ LZ4FLIB_API size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* prefsPtr); /*! LZ4F_compressUpdate() : * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. - * An important rule is that dstCapacity MUST be large enough to ensure operation success even in worst case situations. + * Important rule: dstCapacity MUST be large enough to ensure operation success even in worst case situations. * This value is provided by LZ4F_compressBound(). * If this condition is not respected, LZ4F_compress() will fail (result is an errorCode). - * LZ4F_compressUpdate() doesn't guarantee error recovery. When an error occurs, compression context must be freed or resized. + * After an error, the state is left in a UB state, and must be re-initialized or freed. + * If previously an uncompressed block was written, buffered data is flushed + * before appending compressed data is continued. * `cOptPtr` is optional : NULL can be provided, in which case all options are set to default. * @return : number of bytes written into `dstBuffer` (it can be zero, meaning input data was just buffered). * or an error code if it fails (which can be tested using LZ4F_isError()) */ -LZ4FLIB_API size_t LZ4F_compressUpdate(LZ4F_cctx* cctx, void* dstBuffer, size_t dstCapacity, const void* srcBuffer, size_t srcSize, const LZ4F_compressOptions_t* cOptPtr); +LZ4FLIB_API size_t LZ4F_compressUpdate(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* cOptPtr); /*! LZ4F_flush() : * When data must be generated and sent immediately, without waiting for a block to be completely filled, * it's possible to call LZ4_flush(). It will immediately compress any data buffered within cctx. * `dstCapacity` must be large enough to ensure the operation will be successful. * `cOptPtr` is optional : it's possible to provide NULL, all options will be set to default. - * @return : number of bytes written into dstBuffer (it can be zero, which means there was no data stored within cctx) + * @return : nb of bytes written into dstBuffer (can be zero, when there is no data stored within cctx) * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_flush() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). */ -LZ4FLIB_API size_t LZ4F_flush(LZ4F_cctx* cctx, void* dstBuffer, size_t dstCapacity, const LZ4F_compressOptions_t* cOptPtr); +LZ4FLIB_API size_t LZ4F_flush(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); /*! LZ4F_compressEnd() : * To properly finish an LZ4 frame, invoke LZ4F_compressEnd(). * It will flush whatever data remained within `cctx` (like LZ4_flush()) * and properly finalize the frame, with an endMark and a checksum. * `cOptPtr` is optional : NULL can be provided, in which case all options will be set to default. - * @return : number of bytes written into dstBuffer (necessarily >= 4 (endMark), or 8 if optional frame checksum is enabled) + * @return : nb of bytes written into dstBuffer, necessarily >= 4 (endMark), * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_compressEnd() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). * A successful call to LZ4F_compressEnd() makes `cctx` available again for another compression task. */ -LZ4FLIB_API size_t LZ4F_compressEnd(LZ4F_cctx* cctx, void* dstBuffer, size_t dstCapacity, const LZ4F_compressOptions_t* cOptPtr); +LZ4FLIB_API size_t LZ4F_compressEnd(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); /*-********************************* @@ -296,8 +355,12 @@ typedef struct LZ4F_dctx_s LZ4F_dctx; /* incomplete type */ typedef LZ4F_dctx* LZ4F_decompressionContext_t; /* compatibility with previous API versions */ typedef struct { - unsigned stableDst; /* pledge that at least 64KB+64Bytes of previously decompressed data remain unmodifed where it was decoded. This optimization skips storage operations in tmp buffers */ - unsigned reserved[3]; /* must be set to zero for forward compatibility */ + unsigned stableDst; /* pledges that last 64KB decompressed data will remain available unmodified between invocations. + * This optimization skips storage operations in tmp buffers. */ + unsigned skipChecksums; /* disable checksum calculation and verification, even when one is present in frame, to save CPU time. + * Setting this option to 1 once disables all checksums for the rest of the frame. */ + unsigned reserved1; /* must be set to zero for forward compatibility */ + unsigned reserved0; /* idem */ } LZ4F_decompressOptions_t; @@ -305,11 +368,12 @@ typedef struct { /*! LZ4F_createDecompressionContext() : * Create an LZ4F_dctx object, to track all decompression operations. - * The version provided MUST be LZ4F_VERSION. - * The function provides a pointer to an allocated and initialized LZ4F_dctx object. - * The result is an errorCode, which can be tested using LZ4F_isError(). + * @version provided MUST be LZ4F_VERSION. + * @dctxPtr MUST be valid. + * The function fills @dctxPtr with the value of a pointer to an allocated and initialized LZ4F_dctx object. + * The @return is an errorCode, which can be tested using LZ4F_isError(). * dctx memory can be released using LZ4F_freeDecompressionContext(); - * The result of LZ4F_freeDecompressionContext() is indicative of the current state of decompressionContext when being released. + * Result of LZ4F_freeDecompressionContext() indicates current state of decompressionContext when being released. * That is, it should be == 0 if decompression has been completed fully and correctly. */ LZ4FLIB_API LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_dctx** dctxPtr, unsigned version); @@ -320,38 +384,78 @@ LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_dctx* dctx); * Streaming decompression functions *************************************/ +#define LZ4F_MAGICNUMBER 0x184D2204U +#define LZ4F_MAGIC_SKIPPABLE_START 0x184D2A50U +#define LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH 5 + +/*! LZ4F_headerSize() : v1.9.0+ + * Provide the header size of a frame starting at `src`. + * `srcSize` must be >= LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH, + * which is enough to decode the header length. + * @return : size of frame header + * or an error code, which can be tested using LZ4F_isError() + * note : Frame header size is variable, but is guaranteed to be + * >= LZ4F_HEADER_SIZE_MIN bytes, and <= LZ4F_HEADER_SIZE_MAX bytes. + */ +LZ4FLIB_API size_t LZ4F_headerSize(const void* src, size_t srcSize); + /*! LZ4F_getFrameInfo() : * This function extracts frame parameters (max blockSize, dictID, etc.). - * Its usage is optional. - * Extracted information is typically useful for allocation and dictionary. - * This function works in 2 situations : - * - At the beginning of a new frame, in which case - * it will decode information from `srcBuffer`, starting the decoding process. - * Input size must be large enough to successfully decode the entire frame header. - * Frame header size is variable, but is guaranteed to be <= LZ4F_HEADER_SIZE_MAX bytes. - * It's allowed to provide more input data than this minimum. - * - After decoding has been started. - * In which case, no input is read, frame parameters are extracted from dctx. - * - If decoding has barely started, but not yet extracted information from header, + * Its usage is optional: user can also invoke LZ4F_decompress() directly. + * + * Extracted information will fill an existing LZ4F_frameInfo_t structure. + * This can be useful for allocation and dictionary identification purposes. + * + * LZ4F_getFrameInfo() can work in the following situations : + * + * 1) At the beginning of a new frame, before any invocation of LZ4F_decompress(). + * It will decode header from `srcBuffer`, + * consuming the header and starting the decoding process. + * + * Input size must be large enough to contain the full frame header. + * Frame header size can be known beforehand by LZ4F_headerSize(). + * Frame header size is variable, but is guaranteed to be >= LZ4F_HEADER_SIZE_MIN bytes, + * and not more than <= LZ4F_HEADER_SIZE_MAX bytes. + * Hence, blindly providing LZ4F_HEADER_SIZE_MAX bytes or more will always work. + * It's allowed to provide more input data than the header size, + * LZ4F_getFrameInfo() will only consume the header. + * + * If input size is not large enough, + * aka if it's smaller than header size, + * function will fail and return an error code. + * + * 2) After decoding has been started, + * it's possible to invoke LZ4F_getFrameInfo() anytime + * to extract already decoded frame parameters stored within dctx. + * + * Note that, if decoding has barely started, + * and not yet read enough information to decode the header, * LZ4F_getFrameInfo() will fail. - * The number of bytes consumed from srcBuffer will be updated within *srcSizePtr (necessarily <= original value). - * Decompression must resume from (srcBuffer + *srcSizePtr). - * @return : an hint about how many srcSize bytes LZ4F_decompress() expects for next call, + * + * The number of bytes consumed from srcBuffer will be updated in *srcSizePtr (necessarily <= original value). + * LZ4F_getFrameInfo() only consumes bytes when decoding has not yet started, + * and when decoding the header has been successful. + * Decompression must then resume from (srcBuffer + *srcSizePtr). + * + * @return : a hint about how many srcSize bytes LZ4F_decompress() expects for next call, * or an error code which can be tested using LZ4F_isError(). * note 1 : in case of error, dctx is not modified. Decoding operation can resume from beginning safely. * note 2 : frame parameters are *copied into* an already allocated LZ4F_frameInfo_t structure. */ -LZ4FLIB_API size_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, - LZ4F_frameInfo_t* frameInfoPtr, - const void* srcBuffer, size_t* srcSizePtr); +LZ4FLIB_API size_t +LZ4F_getFrameInfo(LZ4F_dctx* dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr); /*! LZ4F_decompress() : - * Call this function repetitively to regenerate compressed data from `srcBuffer`. - * The function will read up to *srcSizePtr bytes from srcBuffer, + * Call this function repetitively to regenerate data compressed in `srcBuffer`. + * + * The function requires a valid dctx state. + * It will read up to *srcSizePtr bytes from srcBuffer, * and decompress data into dstBuffer, of capacity *dstSizePtr. * - * The number of bytes consumed from srcBuffer will be written into *srcSizePtr (necessarily <= original value). - * The number of bytes decompressed into dstBuffer will be written into *dstSizePtr (necessarily <= original value). + * The nb of bytes consumed from srcBuffer will be written into *srcSizePtr (necessarily <= original value). + * The nb of bytes decompressed into dstBuffer will be written into *dstSizePtr (necessarily <= original value). * * The function does not necessarily read all input bytes, so always check value in *srcSizePtr. * Unconsumed source data must be presented again in subsequent invocations. @@ -374,10 +478,11 @@ LZ4FLIB_API size_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, * * After a frame is fully decoded, dctx can be used again to decompress another frame. */ -LZ4FLIB_API size_t LZ4F_decompress(LZ4F_dctx* dctx, - void* dstBuffer, size_t* dstSizePtr, - const void* srcBuffer, size_t* srcSizePtr, - const LZ4F_decompressOptions_t* dOptPtr); +LZ4FLIB_API size_t +LZ4F_decompress(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* dOptPtr); /*! LZ4F_resetDecompressionContext() : added in v1.8.0 @@ -394,3 +499,194 @@ LZ4FLIB_API void LZ4F_resetDecompressionContext(LZ4F_dctx* dctx); /* always su #endif #endif /* LZ4F_H_09782039843 */ + +#if defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) +#define LZ4F_H_STATIC_09782039843 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* These declarations are not stable and may change in the future. + * They are therefore only safe to depend on + * when the caller is statically linked against the library. + * To access their declarations, define LZ4F_STATIC_LINKING_ONLY. + * + * By default, these symbols aren't published into shared/dynamic libraries. + * You can override this behavior and force them to be published + * by defining LZ4F_PUBLISH_STATIC_FUNCTIONS. + * Use at your own risk. + */ +#ifdef LZ4F_PUBLISH_STATIC_FUNCTIONS +# define LZ4FLIB_STATIC_API LZ4FLIB_API +#else +# define LZ4FLIB_STATIC_API +#endif + + +/* --- Error List --- */ +#define LZ4F_LIST_ERRORS(ITEM) \ + ITEM(OK_NoError) \ + ITEM(ERROR_GENERIC) \ + ITEM(ERROR_maxBlockSize_invalid) \ + ITEM(ERROR_blockMode_invalid) \ + ITEM(ERROR_contentChecksumFlag_invalid) \ + ITEM(ERROR_compressionLevel_invalid) \ + ITEM(ERROR_headerVersion_wrong) \ + ITEM(ERROR_blockChecksum_invalid) \ + ITEM(ERROR_reservedFlag_set) \ + ITEM(ERROR_allocation_failed) \ + ITEM(ERROR_srcSize_tooLarge) \ + ITEM(ERROR_dstMaxSize_tooSmall) \ + ITEM(ERROR_frameHeader_incomplete) \ + ITEM(ERROR_frameType_unknown) \ + ITEM(ERROR_frameSize_wrong) \ + ITEM(ERROR_srcPtr_wrong) \ + ITEM(ERROR_decompressionFailed) \ + ITEM(ERROR_headerChecksum_invalid) \ + ITEM(ERROR_contentChecksum_invalid) \ + ITEM(ERROR_frameDecoding_alreadyStarted) \ + ITEM(ERROR_compressionState_uninitialized) \ + ITEM(ERROR_parameter_null) \ + ITEM(ERROR_maxCode) + +#define LZ4F_GENERATE_ENUM(ENUM) LZ4F_##ENUM, + +/* enum list is exposed, to handle specific errors */ +typedef enum { LZ4F_LIST_ERRORS(LZ4F_GENERATE_ENUM) + _LZ4F_dummy_error_enum_for_c89_never_used } LZ4F_errorCodes; + +LZ4FLIB_STATIC_API LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult); + + +/*! LZ4F_getBlockSize() : + * Return, in scalar format (size_t), + * the maximum block size associated with blockSizeID. +**/ +LZ4FLIB_STATIC_API size_t LZ4F_getBlockSize(LZ4F_blockSizeID_t blockSizeID); + +/*! LZ4F_uncompressedUpdate() : + * LZ4F_uncompressedUpdate() can be called repetitively to add as much data uncompressed data as necessary. + * Important rule: dstCapacity MUST be large enough to store the entire source buffer as + * no compression is done for this operation + * If this condition is not respected, LZ4F_uncompressedUpdate() will fail (result is an errorCode). + * After an error, the state is left in a UB state, and must be re-initialized or freed. + * If previously a compressed block was written, buffered data is flushed + * before appending uncompressed data is continued. + * This is only supported when LZ4F_blockIndependent is used + * `cOptPtr` is optional : NULL can be provided, in which case all options are set to default. + * @return : number of bytes written into `dstBuffer` (it can be zero, meaning input data was just buffered). + * or an error code if it fails (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_STATIC_API size_t +LZ4F_uncompressedUpdate(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* cOptPtr); + +/********************************** + * Bulk processing dictionary API + *********************************/ + +/* A Dictionary is useful for the compression of small messages (KB range). + * It dramatically improves compression efficiency. + * + * LZ4 can ingest any input as dictionary, though only the last 64 KB are useful. + * Best results are generally achieved by using Zstandard's Dictionary Builder + * to generate a high-quality dictionary from a set of samples. + * + * Loading a dictionary has a cost, since it involves construction of tables. + * The Bulk processing dictionary API makes it possible to share this cost + * over an arbitrary number of compression jobs, even concurrently, + * markedly improving compression latency for these cases. + * + * The same dictionary will have to be used on the decompression side + * for decoding to be successful. + * To help identify the correct dictionary at decoding stage, + * the frame header allows optional embedding of a dictID field. + */ +typedef struct LZ4F_CDict_s LZ4F_CDict; + +/*! LZ4_createCDict() : + * When compressing multiple messages / blocks using the same dictionary, it's recommended to load it just once. + * LZ4_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. + * LZ4_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * `dictBuffer` can be released after LZ4_CDict creation, since its content is copied within CDict */ +LZ4FLIB_STATIC_API LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize); +LZ4FLIB_STATIC_API void LZ4F_freeCDict(LZ4F_CDict* CDict); + + +/*! LZ4_compressFrame_usingCDict() : + * Compress an entire srcBuffer into a valid LZ4 frame using a digested Dictionary. + * cctx must point to a context created by LZ4F_createCompressionContext(). + * If cdict==NULL, compress without a dictionary. + * dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * If this condition is not respected, function will fail (@return an errorCode). + * The LZ4F_preferences_t structure is optional : you may provide NULL as argument, + * but it's not recommended, as it's the only way to provide dictID in the frame header. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) */ +LZ4FLIB_STATIC_API size_t +LZ4F_compressFrame_usingCDict(LZ4F_cctx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr); + + +/*! LZ4F_compressBegin_usingCDict() : + * Inits streaming dictionary compression, and writes the frame header into dstBuffer. + * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * `prefsPtr` is optional : you may provide NULL as argument, + * however, it's the only way to provide dictID in the frame header. + * @return : number of bytes written into dstBuffer for the header, + * or an error code (which can be tested using LZ4F_isError()) */ +LZ4FLIB_STATIC_API size_t +LZ4F_compressBegin_usingCDict(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* prefsPtr); + + +/*! LZ4F_decompress_usingDict() : + * Same as LZ4F_decompress(), using a predefined dictionary. + * Dictionary is used "in place", without any preprocessing. +** It must remain accessible throughout the entire frame decoding. */ +LZ4FLIB_STATIC_API size_t +LZ4F_decompress_usingDict(LZ4F_dctx* dctxPtr, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const void* dict, size_t dictSize, + const LZ4F_decompressOptions_t* decompressOptionsPtr); + + +/*! Custom memory allocation : + * These prototypes make it possible to pass custom allocation/free functions. + * LZ4F_customMem is provided at state creation time, using LZ4F_create*_advanced() listed below. + * All allocation/free operations will be completed using these custom variants instead of regular ones. + */ +typedef void* (*LZ4F_AllocFunction) (void* opaqueState, size_t size); +typedef void* (*LZ4F_CallocFunction) (void* opaqueState, size_t size); +typedef void (*LZ4F_FreeFunction) (void* opaqueState, void* address); +typedef struct { + LZ4F_AllocFunction customAlloc; + LZ4F_CallocFunction customCalloc; /* optional; when not defined, uses customAlloc + memset */ + LZ4F_FreeFunction customFree; + void* opaqueState; +} LZ4F_CustomMem; +static +#ifdef __GNUC__ +__attribute__((__unused__)) +#endif +LZ4F_CustomMem const LZ4F_defaultCMem = { NULL, NULL, NULL, NULL }; /**< this constant defers to stdlib's functions */ + +LZ4FLIB_STATIC_API LZ4F_cctx* LZ4F_createCompressionContext_advanced(LZ4F_CustomMem customMem, unsigned version); +LZ4FLIB_STATIC_API LZ4F_dctx* LZ4F_createDecompressionContext_advanced(LZ4F_CustomMem customMem, unsigned version); +LZ4FLIB_STATIC_API LZ4F_CDict* LZ4F_createCDict_advanced(LZ4F_CustomMem customMem, const void* dictBuffer, size_t dictSize); + + +#if defined (__cplusplus) +} +#endif + +#endif /* defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) */ diff --git a/lz4libs/lz4frame_static.h b/lz4libs/lz4frame_static.h index a59b94b5..2b44a631 100644 --- a/lz4libs/lz4frame_static.h +++ b/lz4libs/lz4frame_static.h @@ -1,7 +1,7 @@ /* LZ4 auto-framing library Header File for static linking only - Copyright (C) 2011-2016, Yann Collet. + Copyright (C) 2011-2020, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) @@ -36,119 +36,12 @@ #ifndef LZ4FRAME_STATIC_H_0398209384 #define LZ4FRAME_STATIC_H_0398209384 -#if defined (__cplusplus) -extern "C" { -#endif - -/* lz4frame_static.h should be used solely in the context of static linking. - * It contains definitions which are not stable and may change in the future. - * Never use it in the context of DLL linking. - * - * Defining LZ4F_PUBLISH_STATIC_FUNCTIONS allows one to override this. Use at - * your own risk. +/* The declarations that formerly were made here have been merged into + * lz4frame.h, protected by the LZ4F_STATIC_LINKING_ONLY macro. Going forward, + * it is recommended to simply include that header directly. */ -#ifdef LZ4F_PUBLISH_STATIC_FUNCTIONS -#define LZ4FLIB_STATIC_API LZ4FLIB_API -#else -#define LZ4FLIB_STATIC_API -#endif - -/* --- Dependency --- */ +#define LZ4F_STATIC_LINKING_ONLY #include "lz4frame.h" - -/* --- Error List --- */ -#define LZ4F_LIST_ERRORS(ITEM) \ - ITEM(OK_NoError) \ - ITEM(ERROR_GENERIC) \ - ITEM(ERROR_maxBlockSize_invalid) \ - ITEM(ERROR_blockMode_invalid) \ - ITEM(ERROR_contentChecksumFlag_invalid) \ - ITEM(ERROR_compressionLevel_invalid) \ - ITEM(ERROR_headerVersion_wrong) \ - ITEM(ERROR_blockChecksum_invalid) \ - ITEM(ERROR_reservedFlag_set) \ - ITEM(ERROR_allocation_failed) \ - ITEM(ERROR_srcSize_tooLarge) \ - ITEM(ERROR_dstMaxSize_tooSmall) \ - ITEM(ERROR_frameHeader_incomplete) \ - ITEM(ERROR_frameType_unknown) \ - ITEM(ERROR_frameSize_wrong) \ - ITEM(ERROR_srcPtr_wrong) \ - ITEM(ERROR_decompressionFailed) \ - ITEM(ERROR_headerChecksum_invalid) \ - ITEM(ERROR_contentChecksum_invalid) \ - ITEM(ERROR_frameDecoding_alreadyStarted) \ - ITEM(ERROR_maxCode) - -#define LZ4F_GENERATE_ENUM(ENUM) LZ4F_##ENUM, - -/* enum list is exposed, to handle specific errors */ -typedef enum { LZ4F_LIST_ERRORS(LZ4F_GENERATE_ENUM) } LZ4F_errorCodes; - -LZ4FLIB_STATIC_API LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult); - - - -/********************************** - * Bulk processing dictionary API - *********************************/ -typedef struct LZ4F_CDict_s LZ4F_CDict; - -/*! LZ4_createCDict() : - * When compressing multiple messages / blocks with the same dictionary, it's recommended to load it just once. - * LZ4_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. - * LZ4_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. - * `dictBuffer` can be released after LZ4_CDict creation, since its content is copied within CDict */ -LZ4FLIB_STATIC_API LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize); -LZ4FLIB_STATIC_API void LZ4F_freeCDict(LZ4F_CDict* CDict); - - -/*! LZ4_compressFrame_usingCDict() : - * Compress an entire srcBuffer into a valid LZ4 frame using a digested Dictionary. - * If cdict==NULL, compress without a dictionary. - * dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). - * If this condition is not respected, function will fail (@return an errorCode). - * The LZ4F_preferences_t structure is optional : you may provide NULL as argument, - * but it's not recommended, as it's the only way to provide dictID in the frame header. - * @return : number of bytes written into dstBuffer. - * or an error code if it fails (can be tested using LZ4F_isError()) */ -LZ4FLIB_STATIC_API size_t LZ4F_compressFrame_usingCDict( - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - const LZ4F_CDict* cdict, - const LZ4F_preferences_t* preferencesPtr); - - -/*! LZ4F_compressBegin_usingCDict() : - * Inits streaming dictionary compression, and writes the frame header into dstBuffer. - * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. - * `prefsPtr` is optional : you may provide NULL as argument, - * however, it's the only way to provide dictID in the frame header. - * @return : number of bytes written into dstBuffer for the header, - * or an error code (which can be tested using LZ4F_isError()) */ -LZ4FLIB_STATIC_API size_t LZ4F_compressBegin_usingCDict( - LZ4F_cctx* cctx, - void* dstBuffer, size_t dstCapacity, - const LZ4F_CDict* cdict, - const LZ4F_preferences_t* prefsPtr); - - -/*! LZ4F_decompress_usingDict() : - * Same as LZ4F_decompress(), using a predefined dictionary. - * Dictionary is used "in place", without any preprocessing. - * It must remain accessible throughout the entire frame decoding. */ -LZ4FLIB_STATIC_API size_t LZ4F_decompress_usingDict( - LZ4F_dctx* dctxPtr, - void* dstBuffer, size_t* dstSizePtr, - const void* srcBuffer, size_t* srcSizePtr, - const void* dict, size_t dictSize, - const LZ4F_decompressOptions_t* decompressOptionsPtr); - - -#if defined (__cplusplus) -} -#endif - #endif /* LZ4FRAME_STATIC_H_0398209384 */ diff --git a/lz4libs/lz4hc.c b/lz4libs/lz4hc.c index f2c2566f..b21ad6bb 100644 --- a/lz4libs/lz4hc.c +++ b/lz4libs/lz4hc.c @@ -1,6 +1,6 @@ /* LZ4 HC - High Compression Mode of LZ4 - Copyright (C) 2011-2017, Yann Collet. + Copyright (C) 2011-2020, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) @@ -42,7 +42,7 @@ * Select how default compression function will allocate workplace memory, * in stack (0:fastest), or in heap (1:requires malloc()). * Since workplace is rather large, heap mode is recommended. - */ +**/ #ifndef LZ4HC_HEAPMODE # define LZ4HC_HEAPMODE 1 #endif @@ -53,7 +53,7 @@ #include "lz4hc.h" -/*=== Common LZ4 definitions ===*/ +/*=== Common definitions ===*/ #if defined(__GNUC__) # pragma GCC diagnostic ignored "-Wunused-function" #endif @@ -62,11 +62,18 @@ #endif #define LZ4_COMMONDEFS_ONLY +#ifndef LZ4_SRC_INCLUDED #include "lz4.c" /* LZ4_count, constants, mem */ +#endif + + +/*=== Enums ===*/ +typedef enum { noDictCtx, usingDictCtxHc } dictCtx_directive; /*=== Constants ===*/ #define OPTIMAL_ML (int)((ML_MASK-1)+MINMATCH) +#define LZ4_OPT_NUM (1<<12) /*=== Macros ===*/ @@ -75,24 +82,37 @@ #define HASH_FUNCTION(i) (((i) * 2654435761U) >> ((MINMATCH*8)-LZ4HC_HASH_LOG)) #define DELTANEXTMAXD(p) chainTable[(p) & LZ4HC_MAXD_MASK] /* flexible, LZ4HC_MAXD dependent */ #define DELTANEXTU16(table, pos) table[(U16)(pos)] /* faster */ +/* Make fields passed to, and updated by LZ4HC_encodeSequence explicit */ +#define UPDATABLE(ip, op, anchor) &ip, &op, &anchor static U32 LZ4HC_hashPtr(const void* ptr) { return HASH_FUNCTION(LZ4_read32(ptr)); } - /************************************** * HC Compression **************************************/ -static void LZ4HC_init (LZ4HC_CCtx_internal* hc4, const BYTE* start) +static void LZ4HC_clearTables (LZ4HC_CCtx_internal* hc4) { - MEM_INIT((void*)hc4->hashTable, 0, sizeof(hc4->hashTable)); + MEM_INIT(hc4->hashTable, 0, sizeof(hc4->hashTable)); MEM_INIT(hc4->chainTable, 0xFF, sizeof(hc4->chainTable)); - hc4->nextToUpdate = 64 KB; - hc4->base = start - 64 KB; +} + +static void LZ4HC_init_internal (LZ4HC_CCtx_internal* hc4, const BYTE* start) +{ + size_t const bufferSize = (size_t)(hc4->end - hc4->prefixStart); + size_t newStartingOffset = bufferSize + hc4->dictLimit; + assert(newStartingOffset >= bufferSize); /* check overflow */ + if (newStartingOffset > 1 GB) { + LZ4HC_clearTables(hc4); + newStartingOffset = 0; + } + newStartingOffset += 64 KB; + hc4->nextToUpdate = (U32)newStartingOffset; + hc4->prefixStart = start; hc4->end = start; - hc4->dictBase = start - 64 KB; - hc4->dictLimit = 64 KB; - hc4->lowLimit = 64 KB; + hc4->dictStart = start; + hc4->dictLimit = (U32)newStartingOffset; + hc4->lowLimit = (U32)newStartingOffset; } @@ -101,14 +121,17 @@ LZ4_FORCE_INLINE void LZ4HC_Insert (LZ4HC_CCtx_internal* hc4, const BYTE* ip) { U16* const chainTable = hc4->chainTable; U32* const hashTable = hc4->hashTable; - const BYTE* const base = hc4->base; - U32 const target = (U32)(ip - base); + const BYTE* const prefixPtr = hc4->prefixStart; + U32 const prefixIdx = hc4->dictLimit; + U32 const target = (U32)(ip - prefixPtr) + prefixIdx; U32 idx = hc4->nextToUpdate; + assert(ip >= prefixPtr); + assert(target >= prefixIdx); while (idx < target) { - U32 const h = LZ4HC_hashPtr(base+idx); + U32 const h = LZ4HC_hashPtr(prefixPtr+idx-prefixIdx); size_t delta = idx - hashTable[h]; - if (delta>MAX_DISTANCE) delta = MAX_DISTANCE; + if (delta>LZ4_DISTANCE_MAX) delta = LZ4_DISTANCE_MAX; DELTANEXTU16(chainTable, idx) = (U16)delta; hashTable[h] = idx; idx++; @@ -123,20 +146,39 @@ LZ4_FORCE_INLINE int LZ4HC_countBack(const BYTE* const ip, const BYTE* const match, const BYTE* const iMin, const BYTE* const mMin) { - int back=0; - while ( (ip+back > iMin) - && (match+back > mMin) - && (ip[back-1] == match[back-1])) + int back = 0; + int const min = (int)MAX(iMin - ip, mMin - match); + assert(min <= 0); + assert(ip >= iMin); assert((size_t)(ip-iMin) < (1U<<31)); + assert(match >= mMin); assert((size_t)(match - mMin) < (1U<<31)); + while ( (back > min) + && (ip[back-1] == match[back-1]) ) back--; return back; } +#if defined(_MSC_VER) +# define LZ4HC_rotl32(x,r) _rotl(x,r) +#else +# define LZ4HC_rotl32(x,r) ((x << r) | (x >> (32 - r))) +#endif + + +static U32 LZ4HC_rotatePattern(size_t const rotate, U32 const pattern) +{ + size_t const bitsToRotate = (rotate & (sizeof(pattern) - 1)) << 3; + if (bitsToRotate == 0) return pattern; + return LZ4HC_rotl32(pattern, (int)bitsToRotate); +} + /* LZ4HC_countPattern() : * pattern32 must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) */ -static unsigned LZ4HC_countPattern(const BYTE* ip, const BYTE* const iEnd, U32 const pattern32) +static unsigned +LZ4HC_countPattern(const BYTE* ip, const BYTE* const iEnd, U32 const pattern32) { const BYTE* const iStart = ip; - reg_t const pattern = (sizeof(pattern)==8) ? (reg_t)pattern32 + (((reg_t)pattern32) << 32) : pattern32; + reg_t const pattern = (sizeof(pattern)==8) ? + (reg_t)pattern32 + (((reg_t)pattern32) << (sizeof(pattern)*4)) : pattern32; while (likely(ip < iEnd-(sizeof(pattern)-1))) { reg_t const diff = LZ4_read_ARCH(ip) ^ pattern; @@ -156,16 +198,16 @@ static unsigned LZ4HC_countPattern(const BYTE* ip, const BYTE* const iEnd, U32 c BYTE const byte = (BYTE)(pattern >> bitOffset); if (*ip != byte) break; ip ++; bitOffset -= 8; - } - } + } } return (unsigned)(ip - iStart); } /* LZ4HC_reverseCountPattern() : * pattern must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) - * read using natural platform endianess */ -static unsigned LZ4HC_reverseCountPattern(const BYTE* ip, const BYTE* const iLow, U32 pattern) + * read using natural platform endianness */ +static unsigned +LZ4HC_reverseCountPattern(const BYTE* ip, const BYTE* const iLow, U32 pattern) { const BYTE* const iStart = ip; @@ -173,7 +215,7 @@ static unsigned LZ4HC_reverseCountPattern(const BYTE* ip, const BYTE* const iLow if (LZ4_read32(ip-4) != pattern) break; ip -= 4; } - { const BYTE* bytePtr = (const BYTE*)(&pattern) + 3; /* works for any endianess */ + { const BYTE* bytePtr = (const BYTE*)(&pattern) + 3; /* works for any endianness */ while (likely(ip>iLow)) { if (ip[-1] != *bytePtr) break; ip--; bytePtr--; @@ -181,28 +223,46 @@ static unsigned LZ4HC_reverseCountPattern(const BYTE* ip, const BYTE* const iLow return (unsigned)(iStart - ip); } -typedef enum { rep_untested, rep_not, rep_confirmed } repeat_state_e; +/* LZ4HC_protectDictEnd() : + * Checks if the match is in the last 3 bytes of the dictionary, so reading the + * 4 byte MINMATCH would overflow. + * @returns true if the match index is okay. + */ +static int LZ4HC_protectDictEnd(U32 const dictLimit, U32 const matchIndex) +{ + return ((U32)((dictLimit - 1) - matchIndex) >= 3); +} -LZ4_FORCE_INLINE int LZ4HC_InsertAndGetWiderMatch ( - LZ4HC_CCtx_internal* hc4, - const BYTE* const ip, - const BYTE* const iLowLimit, - const BYTE* const iHighLimit, - int longest, - const BYTE** matchpos, - const BYTE** startpos, - const int maxNbAttempts, - const int patternAnalysis) +typedef enum { rep_untested, rep_not, rep_confirmed } repeat_state_e; +typedef enum { favorCompressionRatio=0, favorDecompressionSpeed } HCfavor_e; + +LZ4_FORCE_INLINE int +LZ4HC_InsertAndGetWiderMatch ( + LZ4HC_CCtx_internal* const hc4, + const BYTE* const ip, + const BYTE* const iLowLimit, const BYTE* const iHighLimit, + int longest, + const BYTE** matchpos, + const BYTE** startpos, + const int maxNbAttempts, + const int patternAnalysis, const int chainSwap, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) { U16* const chainTable = hc4->chainTable; U32* const HashTable = hc4->hashTable; - const BYTE* const base = hc4->base; - const U32 dictLimit = hc4->dictLimit; - const BYTE* const lowPrefixPtr = base + dictLimit; - const U32 lowLimit = (hc4->lowLimit + 64 KB > (U32)(ip-base)) ? hc4->lowLimit : (U32)(ip - base) - MAX_DISTANCE; - const BYTE* const dictBase = hc4->dictBase; - int const delta = (int)(ip-iLowLimit); + const LZ4HC_CCtx_internal * const dictCtx = hc4->dictCtx; + const BYTE* const prefixPtr = hc4->prefixStart; + const U32 prefixIdx = hc4->dictLimit; + const U32 ipIndex = (U32)(ip - prefixPtr) + prefixIdx; + const int withinStartDistance = (hc4->lowLimit + (LZ4_DISTANCE_MAX + 1) > ipIndex); + const U32 lowestMatchIndex = (withinStartDistance) ? hc4->lowLimit : ipIndex - LZ4_DISTANCE_MAX; + const BYTE* const dictStart = hc4->dictStart; + const U32 dictIdx = hc4->lowLimit; + const BYTE* const dictEnd = dictStart + prefixIdx - dictIdx; + int const lookBackLength = (int)(ip-iLowLimit); int nbAttempts = maxNbAttempts; + U32 matchChainPos = 0; U32 const pattern = LZ4_read32(ip); U32 matchIndex; repeat_state_e repeat = rep_untested; @@ -212,200 +272,297 @@ LZ4_FORCE_INLINE int LZ4HC_InsertAndGetWiderMatch ( /* First Match */ LZ4HC_Insert(hc4, ip); matchIndex = HashTable[LZ4HC_hashPtr(ip)]; - DEBUGLOG(7, "First match at index %u / %u (lowLimit)", - matchIndex, lowLimit); + DEBUGLOG(7, "First match at index %u / %u (lowestMatchIndex)", + matchIndex, lowestMatchIndex); - while ((matchIndex>=lowLimit) && (nbAttempts)) { - DEBUGLOG(7, "remaining attempts : %i", nbAttempts); + while ((matchIndex>=lowestMatchIndex) && (nbAttempts>0)) { + int matchLength=0; nbAttempts--; - if (matchIndex >= dictLimit) { - const BYTE* const matchPtr = base + matchIndex; - if (*(iLowLimit + longest) == *(matchPtr - delta + longest)) { + assert(matchIndex < ipIndex); + if (favorDecSpeed && (ipIndex - matchIndex < 8)) { + /* do nothing */ + } else if (matchIndex >= prefixIdx) { /* within current Prefix */ + const BYTE* const matchPtr = prefixPtr + matchIndex - prefixIdx; + assert(matchPtr < ip); + assert(longest >= 1); + if (LZ4_read16(iLowLimit + longest - 1) == LZ4_read16(matchPtr - lookBackLength + longest - 1)) { if (LZ4_read32(matchPtr) == pattern) { - int mlt = MINMATCH + LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, iHighLimit); - #if 0 - /* more generic but unfortunately slower on clang */ - int const back = LZ4HC_countBack(ip, matchPtr, iLowLimit, lowPrefixPtr); - #else - int back = 0; - while ( (ip+back > iLowLimit) - && (matchPtr+back > lowPrefixPtr) - && (ip[back-1] == matchPtr[back-1])) { - back--; - } - #endif - mlt -= back; - - if (mlt > longest) { - longest = mlt; - *matchpos = matchPtr+back; - *startpos = ip+back; - } } - } - } else { /* matchIndex < dictLimit */ - const BYTE* const matchPtr = dictBase + matchIndex; - if (LZ4_read32(matchPtr) == pattern) { - int mlt; + int const back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, prefixPtr) : 0; + matchLength = MINMATCH + (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, iHighLimit); + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + *matchpos = matchPtr + back; + *startpos = ip + back; + } } } + } else { /* lowestMatchIndex <= matchIndex < dictLimit */ + const BYTE* const matchPtr = dictStart + (matchIndex - dictIdx); + assert(matchIndex >= dictIdx); + if ( likely(matchIndex <= prefixIdx - 4) + && (LZ4_read32(matchPtr) == pattern) ) { int back = 0; - const BYTE* vLimit = ip + (dictLimit - matchIndex); + const BYTE* vLimit = ip + (prefixIdx - matchIndex); if (vLimit > iHighLimit) vLimit = iHighLimit; - mlt = LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; - if ((ip+mlt == vLimit) && (vLimit < iHighLimit)) - mlt += LZ4_count(ip+mlt, base+dictLimit, iHighLimit); - while ( (ip+back > iLowLimit) - && (matchIndex+back > lowLimit) - && (ip[back-1] == matchPtr[back-1])) - back--; - mlt -= back; - if (mlt > longest) { - longest = mlt; - *matchpos = base + matchIndex + back; + matchLength = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + if ((ip+matchLength == vLimit) && (vLimit < iHighLimit)) + matchLength += LZ4_count(ip+matchLength, prefixPtr, iHighLimit); + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictStart) : 0; + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + *matchpos = prefixPtr - prefixIdx + matchIndex + back; /* virtual pos, relative to ip, to retrieve offset */ *startpos = ip + back; } } } - { U32 const nextOffset = DELTANEXTU16(chainTable, matchIndex); - matchIndex -= nextOffset; - if (patternAnalysis && nextOffset==1) { + if (chainSwap && matchLength==longest) { /* better match => select a better chain */ + assert(lookBackLength==0); /* search forward only */ + if (matchIndex + (U32)longest <= ipIndex) { + int const kTrigger = 4; + U32 distanceToNextMatch = 1; + int const end = longest - MINMATCH + 1; + int step = 1; + int accel = 1 << kTrigger; + int pos; + for (pos = 0; pos < end; pos += step) { + U32 const candidateDist = DELTANEXTU16(chainTable, matchIndex + (U32)pos); + step = (accel++ >> kTrigger); + if (candidateDist > distanceToNextMatch) { + distanceToNextMatch = candidateDist; + matchChainPos = (U32)pos; + accel = 1 << kTrigger; + } } + if (distanceToNextMatch > 1) { + if (distanceToNextMatch > matchIndex) break; /* avoid overflow */ + matchIndex -= distanceToNextMatch; + continue; + } } } + + { U32 const distNextMatch = DELTANEXTU16(chainTable, matchIndex); + if (patternAnalysis && distNextMatch==1 && matchChainPos==0) { + U32 const matchCandidateIdx = matchIndex-1; /* may be a repeated pattern */ if (repeat == rep_untested) { if ( ((pattern & 0xFFFF) == (pattern >> 16)) & ((pattern & 0xFF) == (pattern >> 24)) ) { repeat = rep_confirmed; - srcPatternLength = LZ4HC_countPattern(ip+4, iHighLimit, pattern) + 4; + srcPatternLength = LZ4HC_countPattern(ip+sizeof(pattern), iHighLimit, pattern) + sizeof(pattern); } else { repeat = rep_not; } } - if ( (repeat == rep_confirmed) - && (matchIndex >= dictLimit) ) { /* same segment only */ - const BYTE* const matchPtr = base + matchIndex; + if ( (repeat == rep_confirmed) && (matchCandidateIdx >= lowestMatchIndex) + && LZ4HC_protectDictEnd(prefixIdx, matchCandidateIdx) ) { + const int extDict = matchCandidateIdx < prefixIdx; + const BYTE* const matchPtr = (extDict ? dictStart - dictIdx : prefixPtr - prefixIdx) + matchCandidateIdx; if (LZ4_read32(matchPtr) == pattern) { /* good candidate */ - size_t const forwardPatternLength = LZ4HC_countPattern(matchPtr+sizeof(pattern), iHighLimit, pattern) + sizeof(pattern); - const BYTE* const maxLowPtr = (lowPrefixPtr + MAX_DISTANCE >= ip) ? lowPrefixPtr : ip - MAX_DISTANCE; - size_t const backLength = LZ4HC_reverseCountPattern(matchPtr, maxLowPtr, pattern); - size_t const currentSegmentLength = backLength + forwardPatternLength; - - if ( (currentSegmentLength >= srcPatternLength) /* current pattern segment large enough to contain full srcPatternLength */ - && (forwardPatternLength <= srcPatternLength) ) { /* haven't reached this position yet */ - matchIndex += (U32)forwardPatternLength - (U32)srcPatternLength; /* best position, full pattern, might be followed by more match */ - } else { - matchIndex -= (U32)backLength; /* let's go to farthest segment position, will find a match of length currentSegmentLength + maybe some back */ + const BYTE* const iLimit = extDict ? dictEnd : iHighLimit; + size_t forwardPatternLength = LZ4HC_countPattern(matchPtr+sizeof(pattern), iLimit, pattern) + sizeof(pattern); + if (extDict && matchPtr + forwardPatternLength == iLimit) { + U32 const rotatedPattern = LZ4HC_rotatePattern(forwardPatternLength, pattern); + forwardPatternLength += LZ4HC_countPattern(prefixPtr, iHighLimit, rotatedPattern); } - } } } } - } /* while ((matchIndex>=lowLimit) && (nbAttempts)) */ + { const BYTE* const lowestMatchPtr = extDict ? dictStart : prefixPtr; + size_t backLength = LZ4HC_reverseCountPattern(matchPtr, lowestMatchPtr, pattern); + size_t currentSegmentLength; + if (!extDict + && matchPtr - backLength == prefixPtr + && dictIdx < prefixIdx) { + U32 const rotatedPattern = LZ4HC_rotatePattern((U32)(-(int)backLength), pattern); + backLength += LZ4HC_reverseCountPattern(dictEnd, dictStart, rotatedPattern); + } + /* Limit backLength not go further than lowestMatchIndex */ + backLength = matchCandidateIdx - MAX(matchCandidateIdx - (U32)backLength, lowestMatchIndex); + assert(matchCandidateIdx - backLength >= lowestMatchIndex); + currentSegmentLength = backLength + forwardPatternLength; + /* Adjust to end of pattern if the source pattern fits, otherwise the beginning of the pattern */ + if ( (currentSegmentLength >= srcPatternLength) /* current pattern segment large enough to contain full srcPatternLength */ + && (forwardPatternLength <= srcPatternLength) ) { /* haven't reached this position yet */ + U32 const newMatchIndex = matchCandidateIdx + (U32)forwardPatternLength - (U32)srcPatternLength; /* best position, full pattern, might be followed by more match */ + if (LZ4HC_protectDictEnd(prefixIdx, newMatchIndex)) + matchIndex = newMatchIndex; + else { + /* Can only happen if started in the prefix */ + assert(newMatchIndex >= prefixIdx - 3 && newMatchIndex < prefixIdx && !extDict); + matchIndex = prefixIdx; + } + } else { + U32 const newMatchIndex = matchCandidateIdx - (U32)backLength; /* farthest position in current segment, will find a match of length currentSegmentLength + maybe some back */ + if (!LZ4HC_protectDictEnd(prefixIdx, newMatchIndex)) { + assert(newMatchIndex >= prefixIdx - 3 && newMatchIndex < prefixIdx && !extDict); + matchIndex = prefixIdx; + } else { + matchIndex = newMatchIndex; + if (lookBackLength==0) { /* no back possible */ + size_t const maxML = MIN(currentSegmentLength, srcPatternLength); + if ((size_t)longest < maxML) { + assert(prefixPtr - prefixIdx + matchIndex != ip); + if ((size_t)(ip - prefixPtr) + prefixIdx - matchIndex > LZ4_DISTANCE_MAX) break; + assert(maxML < 2 GB); + longest = (int)maxML; + *matchpos = prefixPtr - prefixIdx + matchIndex; /* virtual pos, relative to ip, to retrieve offset */ + *startpos = ip; + } + { U32 const distToNextPattern = DELTANEXTU16(chainTable, matchIndex); + if (distToNextPattern > matchIndex) break; /* avoid overflow */ + matchIndex -= distToNextPattern; + } } } } } + continue; + } } + } } /* PA optimization */ + + /* follow current chain */ + matchIndex -= DELTANEXTU16(chainTable, matchIndex + matchChainPos); + + } /* while ((matchIndex>=lowestMatchIndex) && (nbAttempts)) */ + + if ( dict == usingDictCtxHc + && nbAttempts > 0 + && ipIndex - lowestMatchIndex < LZ4_DISTANCE_MAX) { + size_t const dictEndOffset = (size_t)(dictCtx->end - dictCtx->prefixStart) + dictCtx->dictLimit; + U32 dictMatchIndex = dictCtx->hashTable[LZ4HC_hashPtr(ip)]; + assert(dictEndOffset <= 1 GB); + matchIndex = dictMatchIndex + lowestMatchIndex - (U32)dictEndOffset; + while (ipIndex - matchIndex <= LZ4_DISTANCE_MAX && nbAttempts--) { + const BYTE* const matchPtr = dictCtx->prefixStart - dictCtx->dictLimit + dictMatchIndex; + + if (LZ4_read32(matchPtr) == pattern) { + int mlt; + int back = 0; + const BYTE* vLimit = ip + (dictEndOffset - dictMatchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + mlt = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictCtx->prefixStart) : 0; + mlt -= back; + if (mlt > longest) { + longest = mlt; + *matchpos = prefixPtr - prefixIdx + matchIndex + back; + *startpos = ip + back; + } } + + { U32 const nextOffset = DELTANEXTU16(dictCtx->chainTable, dictMatchIndex); + dictMatchIndex -= nextOffset; + matchIndex -= nextOffset; + } } } return longest; } -LZ4_FORCE_INLINE -int LZ4HC_InsertAndFindBestMatch(LZ4HC_CCtx_internal* const hc4, /* Index table will be updated */ - const BYTE* const ip, const BYTE* const iLimit, - const BYTE** matchpos, - const int maxNbAttempts, - const int patternAnalysis) +LZ4_FORCE_INLINE int +LZ4HC_InsertAndFindBestMatch(LZ4HC_CCtx_internal* const hc4, /* Index table will be updated */ + const BYTE* const ip, const BYTE* const iLimit, + const BYTE** matchpos, + const int maxNbAttempts, + const int patternAnalysis, + const dictCtx_directive dict) { const BYTE* uselessPtr = ip; /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), * but this won't be the case here, as we define iLowLimit==ip, * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ - return LZ4HC_InsertAndGetWiderMatch(hc4, ip, ip, iLimit, MINMATCH-1, matchpos, &uselessPtr, maxNbAttempts, patternAnalysis); + return LZ4HC_InsertAndGetWiderMatch(hc4, ip, ip, iLimit, MINMATCH-1, matchpos, &uselessPtr, maxNbAttempts, patternAnalysis, 0 /*chainSwap*/, dict, favorCompressionRatio); } - - -typedef enum { - noLimit = 0, - limitedOutput = 1, - limitedDestSize = 2, -} limitedOutput_directive; - /* LZ4HC_encodeSequence() : * @return : 0 if ok, * 1 if buffer issue detected */ LZ4_FORCE_INLINE int LZ4HC_encodeSequence ( - const BYTE** ip, - BYTE** op, - const BYTE** anchor, + const BYTE** _ip, + BYTE** _op, + const BYTE** _anchor, int matchLength, const BYTE* const match, limitedOutput_directive limit, BYTE* oend) { +#define ip (*_ip) +#define op (*_op) +#define anchor (*_anchor) + size_t length; - BYTE* const token = (*op)++; + BYTE* const token = op++; -#if defined(LZ4_DEBUG) && (LZ4_DEBUG >= 2) +#if defined(LZ4_DEBUG) && (LZ4_DEBUG >= 6) static const BYTE* start = NULL; static U32 totalCost = 0; - U32 const pos = (start==NULL) ? 0 : (U32)(*anchor - start); - U32 const ll = (U32)(*ip - *anchor); + U32 const pos = (start==NULL) ? 0 : (U32)(anchor - start); + U32 const ll = (U32)(ip - anchor); U32 const llAdd = (ll>=15) ? ((ll-15) / 255) + 1 : 0; U32 const mlAdd = (matchLength>=19) ? ((matchLength-19) / 255) + 1 : 0; U32 const cost = 1 + llAdd + ll + 2 + mlAdd; - if (start==NULL) start = *anchor; /* only works for single segment */ - //g_debuglog_enable = (pos >= 2228) & (pos <= 2262); - DEBUGLOG(2, "pos:%7u -- literals:%3u, match:%4i, offset:%5u, cost:%3u + %u", + if (start==NULL) start = anchor; /* only works for single segment */ + /* g_debuglog_enable = (pos >= 2228) & (pos <= 2262); */ + DEBUGLOG(6, "pos:%7u -- literals:%4u, match:%4i, offset:%5u, cost:%4u + %5u", pos, - (U32)(*ip - *anchor), matchLength, (U32)(*ip-match), + (U32)(ip - anchor), matchLength, (U32)(ip-match), cost, totalCost); totalCost += cost; #endif /* Encode Literal length */ - length = (size_t)(*ip - *anchor); - if ((limit) && ((*op + (length >> 8) + length + (2 + 1 + LASTLITERALS)) > oend)) return 1; /* Check output limit */ + length = (size_t)(ip - anchor); + LZ4_STATIC_ASSERT(notLimited == 0); + /* Check output limit */ + if (limit && ((op + (length / 255) + length + (2 + 1 + LASTLITERALS)) > oend)) { + DEBUGLOG(6, "Not enough room to write %i literals (%i bytes remaining)", + (int)length, (int)(oend - op)); + return 1; + } if (length >= RUN_MASK) { size_t len = length - RUN_MASK; *token = (RUN_MASK << ML_BITS); - for(; len >= 255 ; len -= 255) *(*op)++ = 255; - *(*op)++ = (BYTE)len; + for(; len >= 255 ; len -= 255) *op++ = 255; + *op++ = (BYTE)len; } else { *token = (BYTE)(length << ML_BITS); } /* Copy Literals */ - LZ4_wildCopy(*op, *anchor, (*op) + length); - *op += length; + LZ4_wildCopy8(op, anchor, op + length); + op += length; /* Encode Offset */ - LZ4_writeLE16(*op, (U16)(*ip-match)); *op += 2; + assert( (ip - match) <= LZ4_DISTANCE_MAX ); /* note : consider providing offset as a value, rather than as a pointer difference */ + LZ4_writeLE16(op, (U16)(ip - match)); op += 2; /* Encode MatchLength */ assert(matchLength >= MINMATCH); - length = (size_t)(matchLength - MINMATCH); - if ((limit) && (*op + (length >> 8) + (1 + LASTLITERALS) > oend)) return 1; /* Check output limit */ + length = (size_t)matchLength - MINMATCH; + if (limit && (op + (length / 255) + (1 + LASTLITERALS) > oend)) { + DEBUGLOG(6, "Not enough room to write match length"); + return 1; /* Check output limit */ + } if (length >= ML_MASK) { *token += ML_MASK; length -= ML_MASK; - for(; length >= 510 ; length -= 510) { *(*op)++ = 255; *(*op)++ = 255; } - if (length >= 255) { length -= 255; *(*op)++ = 255; } - *(*op)++ = (BYTE)length; + for(; length >= 510 ; length -= 510) { *op++ = 255; *op++ = 255; } + if (length >= 255) { length -= 255; *op++ = 255; } + *op++ = (BYTE)length; } else { *token += (BYTE)(length); } /* Prepare next loop */ - *ip += matchLength; - *anchor = *ip; + ip += matchLength; + anchor = ip; return 0; } +#undef ip +#undef op +#undef anchor -/* btopt */ -#include "lz4opt.h" - - -static int LZ4HC_compress_hashChain ( +LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( LZ4HC_CCtx_internal* const ctx, const char* const source, char* const dest, int* srcSizePtr, int const maxOutputSize, - unsigned maxNbAttempts, - limitedOutput_directive limit + int maxNbAttempts, + const limitedOutput_directive limit, + const dictCtx_directive dict ) { const int inputSize = *srcSizePtr; - const int patternAnalysis = (maxNbAttempts > 64); /* levels 8+ */ + const int patternAnalysis = (maxNbAttempts > 128); /* levels 9+ */ const BYTE* ip = (const BYTE*) source; const BYTE* anchor = ip; @@ -417,51 +574,47 @@ static int LZ4HC_compress_hashChain ( BYTE* op = (BYTE*) dest; BYTE* oend = op + maxOutputSize; - int ml, ml2, ml3, ml0; + int ml0, ml, ml2, ml3; + const BYTE* start0; + const BYTE* ref0; const BYTE* ref = NULL; const BYTE* start2 = NULL; const BYTE* ref2 = NULL; const BYTE* start3 = NULL; const BYTE* ref3 = NULL; - const BYTE* start0; - const BYTE* ref0; /* init */ *srcSizePtr = 0; - if (limit == limitedDestSize) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ - if (inputSize < LZ4_minLength) goto _last_literals; /* Input too small, no compression (all literals) */ + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (inputSize < LZ4_minLength) goto _last_literals; /* Input too small, no compression (all literals) */ /* Main Loop */ - while (ip < mflimit) { - ml = LZ4HC_InsertAndFindBestMatch (ctx, ip, matchlimit, &ref, maxNbAttempts, patternAnalysis); + while (ip <= mflimit) { + ml = LZ4HC_InsertAndFindBestMatch(ctx, ip, matchlimit, &ref, maxNbAttempts, patternAnalysis, dict); if (ml encode ML1 */ optr = op; - if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ref, limit, oend)) goto _dest_overflow; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; continue; } - if (start0 < ip) { - if (start2 < ip + ml0) { /* empirical */ - ip = start0; - ref = ref0; - ml = ml0; - } - } + if (start0 < ip) { /* first match was skipped at least once */ + if (start2 < ip + ml0) { /* squeezing ML1 between ML0(original ML1) and ML2 */ + ip = start0; ref = ref0; ml = ml0; /* restore initial ML1 */ + } } /* Here, start0==ip */ if ((start2 - ip) < 3) { /* First Match too small : removed */ @@ -489,22 +642,27 @@ static int LZ4HC_compress_hashChain ( } /* Now, we have start2 = ip+new_ml, with new_ml = min(ml, OPTIMAL_ML=18) */ - if (start2 + ml2 < mflimit) + if (start2 + ml2 <= mflimit) { ml3 = LZ4HC_InsertAndGetWiderMatch(ctx, start2 + ml2 - 3, start2, matchlimit, ml2, &ref3, &start3, - maxNbAttempts, patternAnalysis); - else + maxNbAttempts, patternAnalysis, 0, dict, favorCompressionRatio); + } else { ml3 = ml2; + } - if (ml3 == ml2) { /* No better match : 2 sequences to encode */ + if (ml3 == ml2) { /* No better match => encode ML1 and ML2 */ /* ip & ref are known; Now for ml */ if (start2 < ip+ml) ml = (int)(start2 - ip); /* Now, encode 2 sequences */ optr = op; - if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ref, limit, oend)) goto _dest_overflow; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; ip = start2; optr = op; - if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml2, ref2, limit, oend)) goto _dest_overflow; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml2, ref2, limit, oend)) { + ml = ml2; + ref = ref2; + goto _dest_overflow; + } continue; } @@ -523,7 +681,7 @@ static int LZ4HC_compress_hashChain ( } optr = op; - if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ref, limit, oend)) goto _dest_overflow; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; ip = start3; ref = ref3; ml = ml3; @@ -541,11 +699,12 @@ static int LZ4HC_compress_hashChain ( } /* - * OK, now we have 3 ascending matches; let's write at least the first one - * ip & ref are known; Now for ml + * OK, now we have 3 ascending matches; + * let's write the first one ML1. + * ip & ref are known; Now decide ml. */ if (start2 < ip+ml) { - if ((start2 - ip) < (int)ML_MASK) { + if ((start2 - ip) < OPTIMAL_ML) { int correction; if (ml > OPTIMAL_ML) ml = OPTIMAL_ML; if (ip + ml > start2 + ml2 - MINMATCH) ml = (int)(start2 - ip) + ml2 - MINMATCH; @@ -560,33 +719,33 @@ static int LZ4HC_compress_hashChain ( } } optr = op; - if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ref, limit, oend)) goto _dest_overflow; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; - ip = start2; - ref = ref2; - ml = ml2; + /* ML2 becomes ML1 */ + ip = start2; ref = ref2; ml = ml2; - start2 = start3; - ref2 = ref3; - ml2 = ml3; + /* ML3 becomes ML2 */ + start2 = start3; ref2 = ref3; ml2 = ml3; + /* let's find a new ML3 */ goto _Search3; } _last_literals: /* Encode Last Literals */ { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ - size_t litLength = (lastRunSize + 255 - RUN_MASK) / 255; - size_t const totalSize = 1 + litLength + lastRunSize; - if (limit == limitedDestSize) oend += LASTLITERALS; /* restore correct value */ + size_t llAdd = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + llAdd + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ if (limit && (op + totalSize > oend)) { - if (limit == limitedOutput) return 0; /* Check output limit */ + if (limit == limitedOutput) return 0; /* adapt lastRunSize to fill 'dest' */ - lastRunSize = (size_t)(oend - op) - 1; - litLength = (lastRunSize + 255 - RUN_MASK) / 255; - lastRunSize -= litLength; + lastRunSize = (size_t)(oend - op) - 1 /*token*/; + llAdd = (lastRunSize + 256 - RUN_MASK) / 256; + lastRunSize -= llAdd; } - ip = anchor + lastRunSize; + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRunSize); + ip = anchor + lastRunSize; /* can be != iend if limit==fillOutput */ if (lastRunSize >= RUN_MASK) { size_t accumulator = lastRunSize - RUN_MASK; @@ -596,7 +755,7 @@ static int LZ4HC_compress_hashChain ( } else { *op++ = (BYTE)(lastRunSize << ML_BITS); } - memcpy(op, anchor, lastRunSize); + LZ4_memcpy(op, anchor, lastRunSize); op += lastRunSize; } @@ -605,103 +764,222 @@ static int LZ4HC_compress_hashChain ( return (int) (((char*)op)-dest); _dest_overflow: - if (limit == limitedDestSize) { + if (limit == fillOutput) { + /* Assumption : ip, anchor, ml and ref must be set correctly */ + size_t const ll = (size_t)(ip - anchor); + size_t const ll_addbytes = (ll + 240) / 255; + size_t const ll_totalCost = 1 + ll_addbytes + ll; + BYTE* const maxLitPos = oend - 3; /* 2 for offset, 1 for token */ + DEBUGLOG(6, "Last sequence overflowing"); op = optr; /* restore correct out pointer */ + if (op + ll_totalCost <= maxLitPos) { + /* ll validated; now adjust match length */ + size_t const bytesLeftForMl = (size_t)(maxLitPos - (op+ll_totalCost)); + size_t const maxMlSize = MINMATCH + (ML_MASK-1) + (bytesLeftForMl * 255); + assert(maxMlSize < INT_MAX); assert(ml >= 0); + if ((size_t)ml > maxMlSize) ml = (int)maxMlSize; + if ((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1 + ml >= MFLIMIT) { + LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, notLimited, oend); + } } goto _last_literals; } + /* compression failed */ return 0; } -static int LZ4HC_compress_generic ( +static int LZ4HC_compress_optimal( LZ4HC_CCtx_internal* ctx, + const char* const source, char* dst, + int* srcSizePtr, int dstCapacity, + int const nbSearches, size_t sufficient_len, + const limitedOutput_directive limit, int const fullUpdate, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed); + + +LZ4_FORCE_INLINE int LZ4HC_compress_generic_internal ( LZ4HC_CCtx_internal* const ctx, const char* const src, char* const dst, int* const srcSizePtr, int const dstCapacity, int cLevel, - limitedOutput_directive limit + const limitedOutput_directive limit, + const dictCtx_directive dict ) { typedef enum { lz4hc, lz4opt } lz4hc_strat_e; typedef struct { lz4hc_strat_e strat; - U32 nbSearches; + int nbSearches; U32 targetLength; } cParams_t; static const cParams_t clTable[LZ4HC_CLEVEL_MAX+1] = { - { lz4hc, 2, 16 }, /* 0, unused */ - { lz4hc, 2, 16 }, /* 1, unused */ - { lz4hc, 2, 16 }, /* 2, unused */ - { lz4hc, 4, 16 }, /* 3 */ - { lz4hc, 8, 16 }, /* 4 */ - { lz4hc, 16, 16 }, /* 5 */ - { lz4hc, 32, 16 }, /* 6 */ - { lz4hc, 64, 16 }, /* 7 */ - { lz4hc, 128, 16 }, /* 8 */ - { lz4hc, 256, 16 }, /* 9 */ - { lz4opt, 96, 64 }, /*10==LZ4HC_CLEVEL_OPT_MIN*/ - { lz4opt, 512,128 }, /*11 */ - { lz4opt,8192, LZ4_OPT_NUM }, /* 12==LZ4HC_CLEVEL_MAX */ + { lz4hc, 2, 16 }, /* 0, unused */ + { lz4hc, 2, 16 }, /* 1, unused */ + { lz4hc, 2, 16 }, /* 2, unused */ + { lz4hc, 4, 16 }, /* 3 */ + { lz4hc, 8, 16 }, /* 4 */ + { lz4hc, 16, 16 }, /* 5 */ + { lz4hc, 32, 16 }, /* 6 */ + { lz4hc, 64, 16 }, /* 7 */ + { lz4hc, 128, 16 }, /* 8 */ + { lz4hc, 256, 16 }, /* 9 */ + { lz4opt, 96, 64 }, /*10==LZ4HC_CLEVEL_OPT_MIN*/ + { lz4opt, 512,128 }, /*11 */ + { lz4opt,16384,LZ4_OPT_NUM }, /* 12==LZ4HC_CLEVEL_MAX */ }; - if (limit == limitedDestSize && dstCapacity < 1) return 0; /* Impossible to store anything */ - if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size (too large or negative) */ + DEBUGLOG(4, "LZ4HC_compress_generic(ctx=%p, src=%p, srcSize=%d, limit=%d)", + ctx, src, *srcSizePtr, limit); + + if (limit == fillOutput && dstCapacity < 1) return 0; /* Impossible to store anything */ + if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size (too large or negative) */ ctx->end += *srcSizePtr; if (cLevel < 1) cLevel = LZ4HC_CLEVEL_DEFAULT; /* note : convention is different from lz4frame, maybe something to review */ cLevel = MIN(LZ4HC_CLEVEL_MAX, cLevel); - assert(cLevel >= 0); - assert(cLevel <= LZ4HC_CLEVEL_MAX); { cParams_t const cParam = clTable[cLevel]; - if (cParam.strat == lz4hc) - return LZ4HC_compress_hashChain(ctx, + HCfavor_e const favor = ctx->favorDecSpeed ? favorDecompressionSpeed : favorCompressionRatio; + int result; + + if (cParam.strat == lz4hc) { + result = LZ4HC_compress_hashChain(ctx, + src, dst, srcSizePtr, dstCapacity, + cParam.nbSearches, limit, dict); + } else { + assert(cParam.strat == lz4opt); + result = LZ4HC_compress_optimal(ctx, src, dst, srcSizePtr, dstCapacity, - cParam.nbSearches, limit); - assert(cParam.strat == lz4opt); - return LZ4HC_compress_optimal(ctx, - src, dst, srcSizePtr, dstCapacity, - cParam.nbSearches, cParam.targetLength, limit, - cLevel == LZ4HC_CLEVEL_MAX); /* ultra mode */ + cParam.nbSearches, cParam.targetLength, limit, + cLevel == LZ4HC_CLEVEL_MAX, /* ultra mode */ + dict, favor); + } + if (result <= 0) ctx->dirty = 1; + return result; } } +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock); + +static int +LZ4HC_compress_generic_noDictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + assert(ctx->dictCtx == NULL); + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, noDictCtx); +} -int LZ4_sizeofStateHC(void) { return sizeof(LZ4_streamHC_t); } +static int +LZ4HC_compress_generic_dictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + const size_t position = (size_t)(ctx->end - ctx->prefixStart) + (ctx->dictLimit - ctx->lowLimit); + assert(ctx->dictCtx != NULL); + if (position >= 64 KB) { + ctx->dictCtx = NULL; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else if (position == 0 && *srcSizePtr > 4 KB) { + LZ4_memcpy(ctx, ctx->dictCtx, sizeof(LZ4HC_CCtx_internal)); + LZ4HC_setExternalDict(ctx, (const BYTE *)src); + ctx->compressionLevel = (short)cLevel; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, usingDictCtxHc); + } +} -int LZ4_compress_HC_extStateHC (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +static int +LZ4HC_compress_generic ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + if (ctx->dictCtx == NULL) { + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_dictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } +} + + +int LZ4_sizeofStateHC(void) { return (int)sizeof(LZ4_streamHC_t); } + +static size_t LZ4_streamHC_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_streamHC_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_streamHC_t); +#else + return 1; /* effectively disabled */ +#endif +} + +/* state is presumed correctly initialized, + * in which case its size and alignment have already been validate */ +int LZ4_compress_HC_extStateHC_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) { LZ4HC_CCtx_internal* const ctx = &((LZ4_streamHC_t*)state)->internal_donotuse; - if (((size_t)(state)&(sizeof(void*)-1)) != 0) return 0; /* Error : state is not aligned for pointers (32 or 64 bits) */ - LZ4HC_init (ctx, (const BYTE*)src); + if (!LZ4_isAligned(state, LZ4_streamHC_t_alignment())) return 0; + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)state, compressionLevel); + LZ4HC_init_internal (ctx, (const BYTE*)src); if (dstCapacity < LZ4_compressBound(srcSize)) return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, limitedOutput); else - return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, noLimit); + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, notLimited); +} + +int LZ4_compress_HC_extStateHC (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + LZ4_streamHC_t* const ctx = LZ4_initStreamHC(state, sizeof(*ctx)); + if (ctx==NULL) return 0; /* init failure */ + return LZ4_compress_HC_extStateHC_fastReset(state, src, dst, srcSize, dstCapacity, compressionLevel); } int LZ4_compress_HC(const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) { + int cSize; #if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 - LZ4_streamHC_t* const statePtr = (LZ4_streamHC_t*)malloc(sizeof(LZ4_streamHC_t)); + LZ4_streamHC_t* const statePtr = (LZ4_streamHC_t*)ALLOC(sizeof(LZ4_streamHC_t)); + if (statePtr==NULL) return 0; #else LZ4_streamHC_t state; LZ4_streamHC_t* const statePtr = &state; #endif - int const cSize = LZ4_compress_HC_extStateHC(statePtr, src, dst, srcSize, dstCapacity, compressionLevel); + cSize = LZ4_compress_HC_extStateHC(statePtr, src, dst, srcSize, dstCapacity, compressionLevel); #if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 - free(statePtr); + FREEMEM(statePtr); #endif return cSize; } -/* LZ4_compress_HC_destSize() : - * only compatible with regular HC parser */ -int LZ4_compress_HC_destSize(void* LZ4HC_Data, const char* source, char* dest, int* sourceSizePtr, int targetDestSize, int cLevel) +/* state is presumed sized correctly (>= sizeof(LZ4_streamHC_t)) */ +int LZ4_compress_HC_destSize(void* state, const char* source, char* dest, int* sourceSizePtr, int targetDestSize, int cLevel) { - LZ4HC_CCtx_internal* const ctx = &((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse; - LZ4HC_init(ctx, (const BYTE*) source); - return LZ4HC_compress_generic(ctx, source, dest, sourceSizePtr, targetDestSize, cLevel, limitedDestSize); + LZ4_streamHC_t* const ctx = LZ4_initStreamHC(state, sizeof(*ctx)); + if (ctx==NULL) return 0; /* init failure */ + LZ4HC_init_internal(&ctx->internal_donotuse, (const BYTE*) source); + LZ4_setCompressionLevel(ctx, cLevel); + return LZ4HC_compress_generic(&ctx->internal_donotuse, source, dest, sourceSizePtr, targetDestSize, cLevel, fillOutput); } @@ -710,87 +988,162 @@ int LZ4_compress_HC_destSize(void* LZ4HC_Data, const char* source, char* dest, i * Streaming Functions **************************************/ /* allocation */ -LZ4_streamHC_t* LZ4_createStreamHC(void) { return (LZ4_streamHC_t*)malloc(sizeof(LZ4_streamHC_t)); } -int LZ4_freeStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr) { +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4_streamHC_t* LZ4_createStreamHC(void) +{ + LZ4_streamHC_t* const state = + (LZ4_streamHC_t*)ALLOC_AND_ZERO(sizeof(LZ4_streamHC_t)); + if (state == NULL) return NULL; + LZ4_setCompressionLevel(state, LZ4HC_CLEVEL_DEFAULT); + return state; +} + +int LZ4_freeStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr) +{ + DEBUGLOG(4, "LZ4_freeStreamHC(%p)", LZ4_streamHCPtr); if (!LZ4_streamHCPtr) return 0; /* support free on NULL */ - free(LZ4_streamHCPtr); + FREEMEM(LZ4_streamHCPtr); return 0; } +#endif -/* initialization */ +LZ4_streamHC_t* LZ4_initStreamHC (void* buffer, size_t size) +{ + LZ4_streamHC_t* const LZ4_streamHCPtr = (LZ4_streamHC_t*)buffer; + DEBUGLOG(4, "LZ4_initStreamHC(%p, %u)", buffer, (unsigned)size); + /* check conditions */ + if (buffer == NULL) return NULL; + if (size < sizeof(LZ4_streamHC_t)) return NULL; + if (!LZ4_isAligned(buffer, LZ4_streamHC_t_alignment())) return NULL; + /* init */ + { LZ4HC_CCtx_internal* const hcstate = &(LZ4_streamHCPtr->internal_donotuse); + MEM_INIT(hcstate, 0, sizeof(*hcstate)); } + LZ4_setCompressionLevel(LZ4_streamHCPtr, LZ4HC_CLEVEL_DEFAULT); + return LZ4_streamHCPtr; +} + +/* just a stub */ void LZ4_resetStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) { - LZ4_STATIC_ASSERT(sizeof(LZ4HC_CCtx_internal) <= sizeof(size_t) * LZ4_STREAMHCSIZE_SIZET); /* if compilation fails here, LZ4_STREAMHCSIZE must be increased */ - LZ4_streamHCPtr->internal_donotuse.base = NULL; + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_resetStreamHC_fast (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + DEBUGLOG(4, "LZ4_resetStreamHC_fast(%p, %d)", LZ4_streamHCPtr, compressionLevel); + if (LZ4_streamHCPtr->internal_donotuse.dirty) { + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + } else { + /* preserve end - prefixStart : can trigger clearTable's threshold */ + if (LZ4_streamHCPtr->internal_donotuse.end != NULL) { + LZ4_streamHCPtr->internal_donotuse.end -= (uptrval)LZ4_streamHCPtr->internal_donotuse.prefixStart; + } else { + assert(LZ4_streamHCPtr->internal_donotuse.prefixStart == NULL); + } + LZ4_streamHCPtr->internal_donotuse.prefixStart = NULL; + LZ4_streamHCPtr->internal_donotuse.dictCtx = NULL; + } LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); } void LZ4_setCompressionLevel(LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) { - if (compressionLevel < 1) compressionLevel = 1; + DEBUGLOG(5, "LZ4_setCompressionLevel(%p, %d)", LZ4_streamHCPtr, compressionLevel); + if (compressionLevel < 1) compressionLevel = LZ4HC_CLEVEL_DEFAULT; if (compressionLevel > LZ4HC_CLEVEL_MAX) compressionLevel = LZ4HC_CLEVEL_MAX; - LZ4_streamHCPtr->internal_donotuse.compressionLevel = compressionLevel; + LZ4_streamHCPtr->internal_donotuse.compressionLevel = (short)compressionLevel; } -int LZ4_loadDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, const char* dictionary, int dictSize) +void LZ4_favorDecompressionSpeed(LZ4_streamHC_t* LZ4_streamHCPtr, int favor) +{ + LZ4_streamHCPtr->internal_donotuse.favorDecSpeed = (favor!=0); +} + +/* LZ4_loadDictHC() : + * LZ4_streamHCPtr is presumed properly initialized */ +int LZ4_loadDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* dictionary, int dictSize) { LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(4, "LZ4_loadDictHC(ctx:%p, dict:%p, dictSize:%d)", LZ4_streamHCPtr, dictionary, dictSize); + assert(LZ4_streamHCPtr != NULL); if (dictSize > 64 KB) { - dictionary += dictSize - 64 KB; + dictionary += (size_t)dictSize - 64 KB; dictSize = 64 KB; } - LZ4HC_init (ctxPtr, (const BYTE*)dictionary); + /* need a full initialization, there are bad side-effects when using resetFast() */ + { int const cLevel = ctxPtr->compressionLevel; + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + LZ4_setCompressionLevel(LZ4_streamHCPtr, cLevel); + } + LZ4HC_init_internal (ctxPtr, (const BYTE*)dictionary); ctxPtr->end = (const BYTE*)dictionary + dictSize; if (dictSize >= 4) LZ4HC_Insert (ctxPtr, ctxPtr->end-3); return dictSize; } +void LZ4_attach_HC_dictionary(LZ4_streamHC_t *working_stream, const LZ4_streamHC_t *dictionary_stream) { + working_stream->internal_donotuse.dictCtx = dictionary_stream != NULL ? &(dictionary_stream->internal_donotuse) : NULL; +} /* compression */ static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock) { - if (ctxPtr->end >= ctxPtr->base + 4) LZ4HC_Insert (ctxPtr, ctxPtr->end-3); /* Referencing remaining dictionary content */ + DEBUGLOG(4, "LZ4HC_setExternalDict(%p, %p)", ctxPtr, newBlock); + if (ctxPtr->end >= ctxPtr->prefixStart + 4) + LZ4HC_Insert (ctxPtr, ctxPtr->end-3); /* Referencing remaining dictionary content */ /* Only one memory segment for extDict, so any previous extDict is lost at this stage */ ctxPtr->lowLimit = ctxPtr->dictLimit; - ctxPtr->dictLimit = (U32)(ctxPtr->end - ctxPtr->base); - ctxPtr->dictBase = ctxPtr->base; - ctxPtr->base = newBlock - ctxPtr->dictLimit; + ctxPtr->dictStart = ctxPtr->prefixStart; + ctxPtr->dictLimit += (U32)(ctxPtr->end - ctxPtr->prefixStart); + ctxPtr->prefixStart = newBlock; ctxPtr->end = newBlock; ctxPtr->nextToUpdate = ctxPtr->dictLimit; /* match referencing will resume from there */ + + /* cannot reference an extDict and a dictCtx at the same time */ + ctxPtr->dictCtx = NULL; } -static int LZ4_compressHC_continue_generic (LZ4_streamHC_t* LZ4_streamHCPtr, - const char* src, char* dst, - int* srcSizePtr, int dstCapacity, - limitedOutput_directive limit) +static int +LZ4_compressHC_continue_generic (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int dstCapacity, + limitedOutput_directive limit) { LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(5, "LZ4_compressHC_continue_generic(ctx=%p, src=%p, srcSize=%d, limit=%d)", + LZ4_streamHCPtr, src, *srcSizePtr, limit); + assert(ctxPtr != NULL); /* auto-init if forgotten */ - if (ctxPtr->base == NULL) LZ4HC_init (ctxPtr, (const BYTE*) src); + if (ctxPtr->prefixStart == NULL) LZ4HC_init_internal (ctxPtr, (const BYTE*) src); /* Check overflow */ - if ((size_t)(ctxPtr->end - ctxPtr->base) > 2 GB) { - size_t dictSize = (size_t)(ctxPtr->end - ctxPtr->base) - ctxPtr->dictLimit; + if ((size_t)(ctxPtr->end - ctxPtr->prefixStart) + ctxPtr->dictLimit > 2 GB) { + size_t dictSize = (size_t)(ctxPtr->end - ctxPtr->prefixStart); if (dictSize > 64 KB) dictSize = 64 KB; LZ4_loadDictHC(LZ4_streamHCPtr, (const char*)(ctxPtr->end) - dictSize, (int)dictSize); } /* Check if blocks follow each other */ - if ((const BYTE*)src != ctxPtr->end) LZ4HC_setExternalDict(ctxPtr, (const BYTE*)src); + if ((const BYTE*)src != ctxPtr->end) + LZ4HC_setExternalDict(ctxPtr, (const BYTE*)src); /* Check overlapping input/dictionary space */ { const BYTE* sourceEnd = (const BYTE*) src + *srcSizePtr; - const BYTE* const dictBegin = ctxPtr->dictBase + ctxPtr->lowLimit; - const BYTE* const dictEnd = ctxPtr->dictBase + ctxPtr->dictLimit; + const BYTE* const dictBegin = ctxPtr->dictStart; + const BYTE* const dictEnd = ctxPtr->dictStart + (ctxPtr->dictLimit - ctxPtr->lowLimit); if ((sourceEnd > dictBegin) && ((const BYTE*)src < dictEnd)) { if (sourceEnd > dictEnd) sourceEnd = dictEnd; - ctxPtr->lowLimit = (U32)(sourceEnd - ctxPtr->dictBase); - if (ctxPtr->dictLimit - ctxPtr->lowLimit < 4) ctxPtr->lowLimit = ctxPtr->dictLimit; - } - } + ctxPtr->lowLimit += (U32)(sourceEnd - ctxPtr->dictStart); + ctxPtr->dictStart += (U32)(sourceEnd - ctxPtr->dictStart); + if (ctxPtr->dictLimit - ctxPtr->lowLimit < 4) { + ctxPtr->lowLimit = ctxPtr->dictLimit; + ctxPtr->dictStart = ctxPtr->prefixStart; + } } } return LZ4HC_compress_generic (ctxPtr, src, dst, srcSizePtr, dstCapacity, ctxPtr->compressionLevel, limit); } @@ -800,42 +1153,53 @@ int LZ4_compress_HC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, if (dstCapacity < LZ4_compressBound(srcSize)) return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, limitedOutput); else - return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, noLimit); + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, notLimited); } int LZ4_compress_HC_continue_destSize (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int* srcSizePtr, int targetDestSize) { - return LZ4_compressHC_continue_generic(LZ4_streamHCPtr, src, dst, srcSizePtr, targetDestSize, limitedDestSize); + return LZ4_compressHC_continue_generic(LZ4_streamHCPtr, src, dst, srcSizePtr, targetDestSize, fillOutput); } -/* dictionary saving */ - +/* LZ4_saveDictHC : + * save history content + * into a user-provided buffer + * which is then used to continue compression + */ int LZ4_saveDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, char* safeBuffer, int dictSize) { LZ4HC_CCtx_internal* const streamPtr = &LZ4_streamHCPtr->internal_donotuse; - int const prefixSize = (int)(streamPtr->end - (streamPtr->base + streamPtr->dictLimit)); + int const prefixSize = (int)(streamPtr->end - streamPtr->prefixStart); + DEBUGLOG(5, "LZ4_saveDictHC(%p, %p, %d)", LZ4_streamHCPtr, safeBuffer, dictSize); + assert(prefixSize >= 0); if (dictSize > 64 KB) dictSize = 64 KB; if (dictSize < 4) dictSize = 0; if (dictSize > prefixSize) dictSize = prefixSize; - memmove(safeBuffer, streamPtr->end - dictSize, dictSize); - { U32 const endIndex = (U32)(streamPtr->end - streamPtr->base); + if (safeBuffer == NULL) assert(dictSize == 0); + if (dictSize > 0) + LZ4_memmove(safeBuffer, streamPtr->end - dictSize, dictSize); + { U32 const endIndex = (U32)(streamPtr->end - streamPtr->prefixStart) + streamPtr->dictLimit; streamPtr->end = (const BYTE*)safeBuffer + dictSize; - streamPtr->base = streamPtr->end - endIndex; - streamPtr->dictLimit = endIndex - dictSize; - streamPtr->lowLimit = endIndex - dictSize; - if (streamPtr->nextToUpdate < streamPtr->dictLimit) streamPtr->nextToUpdate = streamPtr->dictLimit; + streamPtr->prefixStart = streamPtr->end - dictSize; + streamPtr->dictLimit = endIndex - (U32)dictSize; + streamPtr->lowLimit = endIndex - (U32)dictSize; + streamPtr->dictStart = streamPtr->prefixStart; + if (streamPtr->nextToUpdate < streamPtr->dictLimit) + streamPtr->nextToUpdate = streamPtr->dictLimit; } return dictSize; } -/*********************************** +/*************************************************** * Deprecated Functions -***********************************/ +***************************************************/ + /* These functions currently generate deprecation warnings */ -/* Deprecated compression functions */ + +/* Wrappers for deprecated compression functions */ int LZ4_compressHC(const char* src, char* dst, int srcSize) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), 0); } int LZ4_compressHC_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, 0); } int LZ4_compressHC2(const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } @@ -849,35 +1213,38 @@ int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* ctx, const char* src, /* Deprecated streaming functions */ -int LZ4_sizeofStreamStateHC(void) { return LZ4_STREAMHCSIZE; } +int LZ4_sizeofStreamStateHC(void) { return sizeof(LZ4_streamHC_t); } +/* state is presumed correctly sized, aka >= sizeof(LZ4_streamHC_t) + * @return : 0 on success, !=0 if error */ int LZ4_resetStreamStateHC(void* state, char* inputBuffer) { - LZ4HC_CCtx_internal *ctx = &((LZ4_streamHC_t*)state)->internal_donotuse; - if ((((size_t)state) & (sizeof(void*)-1)) != 0) return 1; /* Error : pointer is not aligned for pointer (32 or 64 bits) */ - LZ4HC_init(ctx, (const BYTE*)inputBuffer); - ctx->inputBuffer = (BYTE*)inputBuffer; + LZ4_streamHC_t* const hc4 = LZ4_initStreamHC(state, sizeof(*hc4)); + if (hc4 == NULL) return 1; /* init failed */ + LZ4HC_init_internal (&hc4->internal_donotuse, (const BYTE*)inputBuffer); return 0; } -void* LZ4_createHC (char* inputBuffer) +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +void* LZ4_createHC (const char* inputBuffer) { - LZ4_streamHC_t* hc4 = (LZ4_streamHC_t*)ALLOCATOR(1, sizeof(LZ4_streamHC_t)); + LZ4_streamHC_t* const hc4 = LZ4_createStreamHC(); if (hc4 == NULL) return NULL; /* not enough memory */ - LZ4HC_init (&hc4->internal_donotuse, (const BYTE*)inputBuffer); - hc4->internal_donotuse.inputBuffer = (BYTE*)inputBuffer; + LZ4HC_init_internal (&hc4->internal_donotuse, (const BYTE*)inputBuffer); return hc4; } -int LZ4_freeHC (void* LZ4HC_Data) { +int LZ4_freeHC (void* LZ4HC_Data) +{ if (!LZ4HC_Data) return 0; /* support free on NULL */ FREEMEM(LZ4HC_Data); return 0; } +#endif int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int cLevel) { - return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, 0, cLevel, noLimit); + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, 0, cLevel, notLimited); } int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int dstCapacity, int cLevel) @@ -887,7 +1254,378 @@ int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* src, c char* LZ4_slideInputBufferHC(void* LZ4HC_Data) { - LZ4HC_CCtx_internal* const hc4 = &((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse; - int const dictSize = LZ4_saveDictHC((LZ4_streamHC_t*)LZ4HC_Data, (char*)(hc4->inputBuffer), 64 KB); - return (char*)(hc4->inputBuffer + dictSize); + LZ4_streamHC_t* const ctx = (LZ4_streamHC_t*)LZ4HC_Data; + const BYTE* bufferStart = ctx->internal_donotuse.prefixStart - ctx->internal_donotuse.dictLimit + ctx->internal_donotuse.lowLimit; + LZ4_resetStreamHC_fast(ctx, ctx->internal_donotuse.compressionLevel); + /* avoid const char * -> char * conversion warning :( */ + return (char*)(uptrval)bufferStart; +} + + +/* ================================================ + * LZ4 Optimal parser (levels [LZ4HC_CLEVEL_OPT_MIN - LZ4HC_CLEVEL_MAX]) + * ===============================================*/ +typedef struct { + int price; + int off; + int mlen; + int litlen; +} LZ4HC_optimal_t; + +/* price in bytes */ +LZ4_FORCE_INLINE int LZ4HC_literalsPrice(int const litlen) +{ + int price = litlen; + assert(litlen >= 0); + if (litlen >= (int)RUN_MASK) + price += 1 + ((litlen-(int)RUN_MASK) / 255); + return price; +} + + +/* requires mlen >= MINMATCH */ +LZ4_FORCE_INLINE int LZ4HC_sequencePrice(int litlen, int mlen) +{ + int price = 1 + 2 ; /* token + 16-bit offset */ + assert(litlen >= 0); + assert(mlen >= MINMATCH); + + price += LZ4HC_literalsPrice(litlen); + + if (mlen >= (int)(ML_MASK+MINMATCH)) + price += 1 + ((mlen-(int)(ML_MASK+MINMATCH)) / 255); + + return price; +} + + +typedef struct { + int off; + int len; +} LZ4HC_match_t; + +LZ4_FORCE_INLINE LZ4HC_match_t +LZ4HC_FindLongerMatch(LZ4HC_CCtx_internal* const ctx, + const BYTE* ip, const BYTE* const iHighLimit, + int minLen, int nbSearches, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + LZ4HC_match_t match = { 0 , 0 }; + const BYTE* matchPtr = NULL; + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + int matchLength = LZ4HC_InsertAndGetWiderMatch(ctx, ip, ip, iHighLimit, minLen, &matchPtr, &ip, nbSearches, 1 /*patternAnalysis*/, 1 /*chainSwap*/, dict, favorDecSpeed); + if (matchLength <= minLen) return match; + if (favorDecSpeed) { + if ((matchLength>18) & (matchLength<=36)) matchLength=18; /* favor shortcut */ + } + match.len = matchLength; + match.off = (int)(ip-matchPtr); + return match; +} + + +static int LZ4HC_compress_optimal ( LZ4HC_CCtx_internal* ctx, + const char* const source, + char* dst, + int* srcSizePtr, + int dstCapacity, + int const nbSearches, + size_t sufficient_len, + const limitedOutput_directive limit, + int const fullUpdate, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + int retval = 0; +#define TRAILING_LITERALS 3 +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + LZ4HC_optimal_t* const opt = (LZ4HC_optimal_t*)ALLOC(sizeof(LZ4HC_optimal_t) * (LZ4_OPT_NUM + TRAILING_LITERALS)); +#else + LZ4HC_optimal_t opt[LZ4_OPT_NUM + TRAILING_LITERALS]; /* ~64 KB, which is a bit large for stack... */ +#endif + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + BYTE* op = (BYTE*) dst; + BYTE* opSaved = (BYTE*) dst; + BYTE* oend = op + dstCapacity; + int ovml = MINMATCH; /* overflow - last sequence */ + const BYTE* ovref = NULL; + + /* init */ +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + if (opt == NULL) goto _return_label; +#endif + DEBUGLOG(5, "LZ4HC_compress_optimal(dst=%p, dstCapa=%u)", dst, (unsigned)dstCapacity); + *srcSizePtr = 0; + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (sufficient_len >= LZ4_OPT_NUM) sufficient_len = LZ4_OPT_NUM-1; + + /* Main Loop */ + while (ip <= mflimit) { + int const llen = (int)(ip - anchor); + int best_mlen, best_off; + int cur, last_match_pos = 0; + + LZ4HC_match_t const firstMatch = LZ4HC_FindLongerMatch(ctx, ip, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + if (firstMatch.len==0) { ip++; continue; } + + if ((size_t)firstMatch.len > sufficient_len) { + /* good enough solution : immediate encoding */ + int const firstML = firstMatch.len; + const BYTE* const matchPos = ip - firstMatch.off; + opSaved = op; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), firstML, matchPos, limit, oend) ) { /* updates ip, op and anchor */ + ovml = firstML; + ovref = matchPos; + goto _dest_overflow; + } + continue; + } + + /* set prices for first positions (literals) */ + { int rPos; + for (rPos = 0 ; rPos < MINMATCH ; rPos++) { + int const cost = LZ4HC_literalsPrice(llen + rPos); + opt[rPos].mlen = 1; + opt[rPos].off = 0; + opt[rPos].litlen = llen + rPos; + opt[rPos].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + rPos, cost, opt[rPos].litlen); + } } + /* set prices using initial match */ + { int mlen = MINMATCH; + int const matchML = firstMatch.len; /* necessarily < sufficient_len < LZ4_OPT_NUM */ + int const offset = firstMatch.off; + assert(matchML < LZ4_OPT_NUM); + for ( ; mlen <= matchML ; mlen++) { + int const cost = LZ4HC_sequencePrice(llen, mlen); + opt[mlen].mlen = mlen; + opt[mlen].off = offset; + opt[mlen].litlen = llen; + opt[mlen].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i) -- initial setup", + mlen, cost, mlen); + } } + last_match_pos = firstMatch.len; + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + + /* check further positions */ + for (cur = 1; cur < last_match_pos; cur++) { + const BYTE* const curPtr = ip + cur; + LZ4HC_match_t newMatch; + + if (curPtr > mflimit) break; + DEBUGLOG(7, "rPos:%u[%u] vs [%u]%u", + cur, opt[cur].price, opt[cur+1].price, cur+1); + if (fullUpdate) { + /* not useful to search here if next position has same (or lower) cost */ + if ( (opt[cur+1].price <= opt[cur].price) + /* in some cases, next position has same cost, but cost rises sharply after, so a small match would still be beneficial */ + && (opt[cur+MINMATCH].price < opt[cur].price + 3/*min seq price*/) ) + continue; + } else { + /* not useful to search here if next position has same (or lower) cost */ + if (opt[cur+1].price <= opt[cur].price) continue; + } + + DEBUGLOG(7, "search at rPos:%u", cur); + if (fullUpdate) + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + else + /* only test matches of minimum length; slightly faster, but misses a few bytes */ + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, last_match_pos - cur, nbSearches, dict, favorDecSpeed); + if (!newMatch.len) continue; + + if ( ((size_t)newMatch.len > sufficient_len) + || (newMatch.len + cur >= LZ4_OPT_NUM) ) { + /* immediate encoding */ + best_mlen = newMatch.len; + best_off = newMatch.off; + last_match_pos = cur + 1; + goto encode; + } + + /* before match : set price with literals at beginning */ + { int const baseLitlen = opt[cur].litlen; + int litlen; + for (litlen = 1; litlen < MINMATCH; litlen++) { + int const price = opt[cur].price - LZ4HC_literalsPrice(baseLitlen) + LZ4HC_literalsPrice(baseLitlen+litlen); + int const pos = cur + litlen; + if (price < opt[pos].price) { + opt[pos].mlen = 1; /* literal */ + opt[pos].off = 0; + opt[pos].litlen = baseLitlen+litlen; + opt[pos].price = price; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", + pos, price, opt[pos].litlen); + } } } + + /* set prices using match at position = cur */ + { int const matchML = newMatch.len; + int ml = MINMATCH; + + assert(cur + newMatch.len < LZ4_OPT_NUM); + for ( ; ml <= matchML ; ml++) { + int const pos = cur + ml; + int const offset = newMatch.off; + int price; + int ll; + DEBUGLOG(7, "testing price rPos %i (last_match_pos=%i)", + pos, last_match_pos); + if (opt[cur].mlen == 1) { + ll = opt[cur].litlen; + price = ((cur > ll) ? opt[cur - ll].price : 0) + + LZ4HC_sequencePrice(ll, ml); + } else { + ll = 0; + price = opt[cur].price + LZ4HC_sequencePrice(0, ml); + } + + assert((U32)favorDecSpeed <= 1); + if (pos > last_match_pos+TRAILING_LITERALS + || price <= opt[pos].price - (int)favorDecSpeed) { + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i)", + pos, price, ml); + assert(pos < LZ4_OPT_NUM); + if ( (ml == matchML) /* last pos of last match */ + && (last_match_pos < pos) ) + last_match_pos = pos; + opt[pos].mlen = ml; + opt[pos].off = offset; + opt[pos].litlen = ll; + opt[pos].price = price; + } } } + /* complete following positions with literals */ + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + } /* for (cur = 1; cur <= last_match_pos; cur++) */ + + assert(last_match_pos < LZ4_OPT_NUM + TRAILING_LITERALS); + best_mlen = opt[last_match_pos].mlen; + best_off = opt[last_match_pos].off; + cur = last_match_pos - best_mlen; + +encode: /* cur, last_match_pos, best_mlen, best_off must be set */ + assert(cur < LZ4_OPT_NUM); + assert(last_match_pos >= 1); /* == 1 when only one candidate */ + DEBUGLOG(6, "reverse traversal, looking for shortest path (last_match_pos=%i)", last_match_pos); + { int candidate_pos = cur; + int selected_matchLength = best_mlen; + int selected_offset = best_off; + while (1) { /* from end to beginning */ + int const next_matchLength = opt[candidate_pos].mlen; /* can be 1, means literal */ + int const next_offset = opt[candidate_pos].off; + DEBUGLOG(7, "pos %i: sequence length %i", candidate_pos, selected_matchLength); + opt[candidate_pos].mlen = selected_matchLength; + opt[candidate_pos].off = selected_offset; + selected_matchLength = next_matchLength; + selected_offset = next_offset; + if (next_matchLength > candidate_pos) break; /* last match elected, first match to encode */ + assert(next_matchLength > 0); /* can be 1, means literal */ + candidate_pos -= next_matchLength; + } } + + /* encode all recorded sequences in order */ + { int rPos = 0; /* relative position (to ip) */ + while (rPos < last_match_pos) { + int const ml = opt[rPos].mlen; + int const offset = opt[rPos].off; + if (ml == 1) { ip++; rPos++; continue; } /* literal; note: can end up with several literals, in which case, skip them */ + rPos += ml; + assert(ml >= MINMATCH); + assert((offset >= 1) && (offset <= LZ4_DISTANCE_MAX)); + opSaved = op; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ip - offset, limit, oend) ) { /* updates ip, op and anchor */ + ovml = ml; + ovref = ip - offset; + goto _dest_overflow; + } } } + } /* while (ip <= mflimit) */ + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t llAdd = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + llAdd + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) { /* Check output limit */ + retval = 0; + goto _return_label; + } + /* adapt lastRunSize to fill 'dst' */ + lastRunSize = (size_t)(oend - op) - 1 /*token*/; + llAdd = (lastRunSize + 256 - RUN_MASK) / 256; + lastRunSize -= llAdd; + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRunSize); + ip = anchor + lastRunSize; /* can be != iend if limit==fillOutput */ + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + LZ4_memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + retval = (int) ((char*)op-dst); + goto _return_label; + +_dest_overflow: +if (limit == fillOutput) { + /* Assumption : ip, anchor, ovml and ovref must be set correctly */ + size_t const ll = (size_t)(ip - anchor); + size_t const ll_addbytes = (ll + 240) / 255; + size_t const ll_totalCost = 1 + ll_addbytes + ll; + BYTE* const maxLitPos = oend - 3; /* 2 for offset, 1 for token */ + DEBUGLOG(6, "Last sequence overflowing (only %i bytes remaining)", (int)(oend-1-opSaved)); + op = opSaved; /* restore correct out pointer */ + if (op + ll_totalCost <= maxLitPos) { + /* ll validated; now adjust match length */ + size_t const bytesLeftForMl = (size_t)(maxLitPos - (op+ll_totalCost)); + size_t const maxMlSize = MINMATCH + (ML_MASK-1) + (bytesLeftForMl * 255); + assert(maxMlSize < INT_MAX); assert(ovml >= 0); + if ((size_t)ovml > maxMlSize) ovml = (int)maxMlSize; + if ((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1 + ovml >= MFLIMIT) { + DEBUGLOG(6, "Space to end : %i + ml (%i)", (int)((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1), ovml); + DEBUGLOG(6, "Before : ip = %p, anchor = %p", ip, anchor); + LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ovml, ovref, notLimited, oend); + DEBUGLOG(6, "After : ip = %p, anchor = %p", ip, anchor); + } } + goto _last_literals; +} +_return_label: +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + FREEMEM(opt); +#endif + return retval; } diff --git a/lz4libs/lz4hc.h b/lz4libs/lz4hc.h index d41bf420..e937acfe 100644 --- a/lz4libs/lz4hc.h +++ b/lz4libs/lz4hc.h @@ -1,7 +1,7 @@ /* LZ4 HC - High Compression Mode of LZ4 Header File - Copyright (C) 2011-2017, Yann Collet. + Copyright (C) 2011-2020, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) Redistribution and use in source and binary forms, with or without @@ -54,7 +54,7 @@ extern "C" { * Block Compression **************************************/ /*! LZ4_compress_HC() : - * Compress data from `src` into `dst`, using the more powerful but slower "HC" algorithm. + * Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm. * `dst` must be already allocated. * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") @@ -77,7 +77,21 @@ LZ4LIB_API int LZ4_compress_HC (const char* src, char* dst, int srcSize, int dst * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). */ LZ4LIB_API int LZ4_sizeofStateHC(void); -LZ4LIB_API int LZ4_compress_HC_extStateHC(void* state, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel); +LZ4LIB_API int LZ4_compress_HC_extStateHC(void* stateHC, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel); + + +/*! LZ4_compress_HC_destSize() : v1.9.0+ + * Will compress as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr is updated to indicate how much bytes were read from `src` + */ +LZ4LIB_API int LZ4_compress_HC_destSize(void* stateHC, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize, + int compressionLevel); /*-************************************ @@ -89,46 +103,92 @@ LZ4LIB_API int LZ4_compress_HC_extStateHC(void* state, const char* src, char* ds /*! LZ4_createStreamHC() and LZ4_freeStreamHC() : * These functions create and release memory for LZ4 HC streaming state. * Newly created states are automatically initialized. - * Existing states can be re-used several times, using LZ4_resetStreamHC(). - * These methods are API and ABI stable, they can be used in combination with a DLL. + * A same state can be used multiple times consecutively, + * starting with LZ4_resetStreamHC_fast() to start a new stream of blocks. */ LZ4LIB_API LZ4_streamHC_t* LZ4_createStreamHC(void); LZ4LIB_API int LZ4_freeStreamHC (LZ4_streamHC_t* streamHCPtr); -LZ4LIB_API void LZ4_resetStreamHC (LZ4_streamHC_t* streamHCPtr, int compressionLevel); -LZ4LIB_API int LZ4_loadDictHC (LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize); - -LZ4LIB_API int LZ4_compress_HC_continue (LZ4_streamHC_t* streamHCPtr, const char* src, char* dst, int srcSize, int maxDstSize); - -LZ4LIB_API int LZ4_saveDictHC (LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize); - /* - These functions compress data in successive blocks of any size, using previous blocks as dictionary. + These functions compress data in successive blocks of any size, + using previous blocks as dictionary, to improve compression ratio. One key assumption is that previous blocks (up to 64 KB) remain read-accessible while compressing next blocks. There is an exception for ring buffers, which can be smaller than 64 KB. - Ring buffers scenario is automatically detected and handled by LZ4_compress_HC_continue(). + Ring-buffer scenario is automatically detected and handled within LZ4_compress_HC_continue(). + + Before starting compression, state must be allocated and properly initialized. + LZ4_createStreamHC() does both, though compression level is set to LZ4HC_CLEVEL_DEFAULT. + + Selecting the compression level can be done with LZ4_resetStreamHC_fast() (starts a new stream) + or LZ4_setCompressionLevel() (anytime, between blocks in the same stream) (experimental). + LZ4_resetStreamHC_fast() only works on states which have been properly initialized at least once, + which is automatically the case when state is created using LZ4_createStreamHC(). + + After reset, a first "fictional block" can be designated as initial dictionary, + using LZ4_loadDictHC() (Optional). + + Invoke LZ4_compress_HC_continue() to compress each successive block. + The number of blocks is unlimited. + Previous input blocks, including initial dictionary when present, + must remain accessible and unmodified during compression. + + It's allowed to update compression level anytime between blocks, + using LZ4_setCompressionLevel() (experimental). + + 'dst' buffer should be sized to handle worst case scenarios + (see LZ4_compressBound(), it ensures compression success). + In case of failure, the API does not guarantee recovery, + so the state _must_ be reset. + To ensure compression success + whenever `dst` buffer size cannot be made >= LZ4_compressBound(), + consider using LZ4_compress_HC_continue_destSize(). + + Whenever previous input blocks can't be preserved unmodified in-place during compression of next blocks, + it's possible to copy the last blocks into a more stable memory space, using LZ4_saveDictHC(). + Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer' (<= 64 KB) + + After completing a streaming compression, + it's possible to start a new stream of blocks, using the same LZ4_streamHC_t state, + just by resetting it, using LZ4_resetStreamHC_fast(). +*/ + +LZ4LIB_API void LZ4_resetStreamHC_fast(LZ4_streamHC_t* streamHCPtr, int compressionLevel); /* v1.9.0+ */ +LZ4LIB_API int LZ4_loadDictHC (LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize); - Before starting compression, state must be properly initialized, using LZ4_resetStreamHC(). - A first "fictional block" can then be designated as initial dictionary, using LZ4_loadDictHC() (Optional). +LZ4LIB_API int LZ4_compress_HC_continue (LZ4_streamHC_t* streamHCPtr, + const char* src, char* dst, + int srcSize, int maxDstSize); - Then, use LZ4_compress_HC_continue() to compress each successive block. - Previous memory blocks (including initial dictionary when present) must remain accessible and unmodified during compression. - 'dst' buffer should be sized to handle worst case scenarios (see LZ4_compressBound()), to ensure operation success. - Because in case of failure, the API does not guarantee context recovery, and context will have to be reset. - If `dst` buffer budget cannot be >= LZ4_compressBound(), consider using LZ4_compress_HC_continue_destSize() instead. +/*! LZ4_compress_HC_continue_destSize() : v1.9.0+ + * Similar to LZ4_compress_HC_continue(), + * but will read as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided into 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr will be updated to indicate how much bytes were read from `src`. + * Note that this function may not consume the entire input. + */ +LZ4LIB_API int LZ4_compress_HC_continue_destSize(LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize); + +LZ4LIB_API int LZ4_saveDictHC (LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize); - If, for any reason, previous data block can't be preserved unmodified in memory for next compression block, - you can save it to a more stable memory space, using LZ4_saveDictHC(). - Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer'. -*/ -/*-************************************************************** +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ + +/*-****************************************************************** * PRIVATE DEFINITIONS : - * Do not use these definitions. - * They are exposed to allow static allocation of `LZ4_streamHC_t`. - * Using these definitions makes the code vulnerable to potential API break when upgrading LZ4 - ****************************************************************/ + * Do not use these definitions directly. + * They are merely exposed to allow static allocation of `LZ4_streamHC_t`. + * Declare an `LZ4_streamHC_t` directly, rather than any type below. + * Even then, only do so in the context of static linking, as definitions may change between versions. + ********************************************************************/ + #define LZ4HC_DICTIONARY_LOGSIZE 16 #define LZ4HC_MAXD (1<= 199901L) /* C99 */) -#include - -typedef struct -{ - uint32_t hashTable[LZ4HC_HASHTABLESIZE]; - uint16_t chainTable[LZ4HC_MAXD]; - const uint8_t* end; /* next block here to continue on current prefix */ - const uint8_t* base; /* All index relative to this position */ - const uint8_t* dictBase; /* alternate base for extDict */ - uint8_t* inputBuffer; /* deprecated */ - uint32_t dictLimit; /* below that point, need extDict */ - uint32_t lowLimit; /* below that point, no more dict */ - uint32_t nextToUpdate; /* index from which to continue dictionary update */ - int compressionLevel; -} LZ4HC_CCtx_internal; - -#else - -typedef struct +/* Never ever use these definitions directly ! + * Declare or allocate an LZ4_streamHC_t instead. +**/ +typedef struct LZ4HC_CCtx_internal LZ4HC_CCtx_internal; +struct LZ4HC_CCtx_internal { - unsigned int hashTable[LZ4HC_HASHTABLESIZE]; - unsigned short chainTable[LZ4HC_MAXD]; - const unsigned char* end; /* next block here to continue on current prefix */ - const unsigned char* base; /* All index relative to this position */ - const unsigned char* dictBase; /* alternate base for extDict */ - unsigned char* inputBuffer; /* deprecated */ - unsigned int dictLimit; /* below that point, need extDict */ - unsigned int lowLimit; /* below that point, no more dict */ - unsigned int nextToUpdate; /* index from which to continue dictionary update */ - int compressionLevel; -} LZ4HC_CCtx_internal; - -#endif - -#define LZ4_STREAMHCSIZE (4*LZ4HC_HASHTABLESIZE + 2*LZ4HC_MAXD + 56) /* 262200 */ -#define LZ4_STREAMHCSIZE_SIZET (LZ4_STREAMHCSIZE / sizeof(size_t)) + LZ4_u32 hashTable[LZ4HC_HASHTABLESIZE]; + LZ4_u16 chainTable[LZ4HC_MAXD]; + const LZ4_byte* end; /* next block here to continue on current prefix */ + const LZ4_byte* prefixStart; /* Indexes relative to this position */ + const LZ4_byte* dictStart; /* alternate reference for extDict */ + LZ4_u32 dictLimit; /* below that point, need extDict */ + LZ4_u32 lowLimit; /* below that point, no more dict */ + LZ4_u32 nextToUpdate; /* index from which to continue dictionary update */ + short compressionLevel; + LZ4_i8 favorDecSpeed; /* favor decompression speed if this flag set, + otherwise, favor compression ratio */ + LZ4_i8 dirty; /* stream has to be fully reset if this flag is set */ + const LZ4HC_CCtx_internal* dictCtx; +}; + +#define LZ4_STREAMHC_MINSIZE 262200 /* static size, for inter-version compatibility */ union LZ4_streamHC_u { - size_t table[LZ4_STREAMHCSIZE_SIZET]; + char minStateSize[LZ4_STREAMHC_MINSIZE]; LZ4HC_CCtx_internal internal_donotuse; -}; /* previously typedef'd to LZ4_streamHC_t */ -/* - LZ4_streamHC_t : - This structure allows static allocation of LZ4 HC streaming state. - State must be initialized using LZ4_resetStreamHC() before first use. +}; /* previously typedef'd to LZ4_streamHC_t */ + +/* LZ4_streamHC_t : + * This structure allows static allocation of LZ4 HC streaming state. + * This can be used to allocate statically on stack, or as part of a larger structure. + * + * Such state **must** be initialized using LZ4_initStreamHC() before first use. + * + * Note that invoking LZ4_initStreamHC() is not required when + * the state was created using LZ4_createStreamHC() (which is recommended). + * Using the normal builder, a newly created state is automatically initialized. + * + * Static allocation shall only be used in combination with static linking. + */ - Static allocation shall only be used in combination with static linking. - When invoking LZ4 from a DLL, use create/free functions instead, which are API and ABI stable. -*/ +/* LZ4_initStreamHC() : v1.9.0+ + * Required before first use of a statically allocated LZ4_streamHC_t. + * Before v1.9.0 : use LZ4_resetStreamHC() instead + */ +LZ4LIB_API LZ4_streamHC_t* LZ4_initStreamHC(void* buffer, size_t size); /*-************************************ @@ -195,25 +251,45 @@ union LZ4_streamHC_u { /* see lz4.h LZ4_DISABLE_DEPRECATE_WARNINGS to turn off deprecation warnings */ /* deprecated compression functions */ -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC() instead") int LZ4_compressHC (const char* source, char* dest, int inputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC() instead") int LZ4_compressHC_limitedOutput (const char* source, char* dest, int inputSize, int maxOutputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC() instead") int LZ4_compressHC2 (const char* source, char* dest, int inputSize, int compressionLevel); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC() instead") int LZ4_compressHC2_limitedOutput (const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") int LZ4_compressHC_withStateHC (void* state, const char* source, char* dest, int inputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") int LZ4_compressHC_limitedOutput_withStateHC (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") int LZ4_compressHC2_withStateHC (void* state, const char* source, char* dest, int inputSize, int compressionLevel); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") int LZ4_compressHC2_limitedOutput_withStateHC(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") int LZ4_compressHC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize, int maxOutputSize); - -/* Deprecated Streaming functions using older model; should no longer be used */ -LZ4LIB_API LZ4_DEPRECATED("use LZ4_createStreamHC() instead") void* LZ4_createHC (char* inputBuffer); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_saveDictHC() instead") char* LZ4_slideInputBufferHC (void* LZ4HC_Data); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_freeStreamHC() instead") int LZ4_freeHC (void* LZ4HC_Data); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* source, char* dest, int inputSize, int compressionLevel); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_createStreamHC() instead") int LZ4_sizeofStreamStateHC(void); -LZ4LIB_API LZ4_DEPRECATED("use LZ4_resetStreamHC() instead") int LZ4_resetStreamStateHC(void* state, char* inputBuffer); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC (const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput (const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC2 (const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC_withStateHC (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput_withStateHC (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC2_withStateHC (void* state, const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput_withStateHC(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/* Obsolete streaming functions; degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, use of + * LZ4_slideInputBufferHC() will truncate the history of the stream, rather + * than preserve a window-sized chunk of history. + */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4_DEPRECATED("use LZ4_createStreamHC() instead") LZ4LIB_API void* LZ4_createHC (const char* inputBuffer); +LZ4_DEPRECATED("use LZ4_freeStreamHC() instead") LZ4LIB_API int LZ4_freeHC (void* LZ4HC_Data); +#endif +LZ4_DEPRECATED("use LZ4_saveDictHC() instead") LZ4LIB_API char* LZ4_slideInputBufferHC (void* LZ4HC_Data); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_createStreamHC() instead") LZ4LIB_API int LZ4_sizeofStreamStateHC(void); +LZ4_DEPRECATED("use LZ4_initStreamHC() instead") LZ4LIB_API int LZ4_resetStreamStateHC(void* state, char* inputBuffer); + + +/* LZ4_resetStreamHC() is now replaced by LZ4_initStreamHC(). + * The intention is to emphasize the difference with LZ4_resetStreamHC_fast(), + * which is now the recommended function to start a new stream of blocks, + * but cannot be used to initialize a memory segment containing arbitrary garbage data. + * + * It is recommended to switch to LZ4_initStreamHC(). + * LZ4_resetStreamHC() will generate deprecation warnings in a future version. + */ +LZ4LIB_API void LZ4_resetStreamHC (LZ4_streamHC_t* streamHCPtr, int compressionLevel); #if defined (__cplusplus) @@ -229,44 +305,109 @@ LZ4LIB_API LZ4_DEPRECATED("use LZ4_resetStreamHC() instead") int LZ4_resetStr * They should not be linked from DLL, * as there is no guarantee of API stability yet. * Prototypes will be promoted to "stable" status - * after successfull usage in real-life scenarios. + * after successful usage in real-life scenarios. ***************************************************/ #ifdef LZ4_HC_STATIC_LINKING_ONLY /* protection macro */ #ifndef LZ4_HC_SLO_098092834 #define LZ4_HC_SLO_098092834 -/*! LZ4_compress_HC_destSize() : v1.8.0 (experimental) - * Will try to compress as much data from `src` as possible - * that can fit into `targetDstSize` budget. - * Result is provided in 2 parts : - * @return : the number of bytes written into 'dst' - * or 0 if compression fails. - * `srcSizePtr` : value will be updated to indicate how much bytes were read from `src` - */ -int LZ4_compress_HC_destSize(void* LZ4HC_Data, - const char* src, char* dst, - int* srcSizePtr, int targetDstSize, - int compressionLevel); - -/*! LZ4_compress_HC_continue_destSize() : v1.8.0 (experimental) - * Similar as LZ4_compress_HC_continue(), - * but will read a variable nb of bytes from `src` - * to fit into `targetDstSize` budget. - * Result is provided in 2 parts : - * @return : the number of bytes written into 'dst' - * or 0 if compression fails. - * `srcSizePtr` : value will be updated to indicate how much bytes were read from `src`. - */ -int LZ4_compress_HC_continue_destSize(LZ4_streamHC_t* LZ4_streamHCPtr, - const char* src, char* dst, - int* srcSizePtr, int targetDstSize); +#define LZ4_STATIC_LINKING_ONLY /* LZ4LIB_STATIC_API */ +#include "lz4.h" + +#if defined (__cplusplus) +extern "C" { +#endif -/*! LZ4_setCompressionLevel() : v1.8.0 (experimental) - * It's possible to change compression level between 2 invocations of LZ4_compress_HC_continue*() +/*! LZ4_setCompressionLevel() : v1.8.0+ (experimental) + * It's possible to change compression level + * between successive invocations of LZ4_compress_HC_continue*() + * for dynamic adaptation. */ -void LZ4_setCompressionLevel(LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); +LZ4LIB_STATIC_API void LZ4_setCompressionLevel( + LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); +/*! LZ4_favorDecompressionSpeed() : v1.8.2+ (experimental) + * Opt. Parser will favor decompression speed over compression ratio. + * Only applicable to levels >= LZ4HC_CLEVEL_OPT_MIN. + */ +LZ4LIB_STATIC_API void LZ4_favorDecompressionSpeed( + LZ4_streamHC_t* LZ4_streamHCPtr, int favor); + +/*! LZ4_resetStreamHC_fast() : v1.9.0+ + * When an LZ4_streamHC_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStreamHC()). + * + * LZ4_streamHCs are guaranteed to be in a valid state when: + * - returned from LZ4_createStreamHC() + * - reset by LZ4_resetStreamHC() + * - memset(stream, 0, sizeof(LZ4_streamHC_t)) + * - the stream was in a valid state and was reset by LZ4_resetStreamHC_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_HC_extStateHC()) and that + * returned success + * + * Note: + * A stream that was last used in a compression call that returned an error + * may be passed to this function. However, it will be fully reset, which will + * clear any existing history and settings from the context. + */ +LZ4LIB_STATIC_API void LZ4_resetStreamHC_fast( + LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_compress_HC_extStateHC_fastReset() : + * A variant of LZ4_compress_HC_extStateHC(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStreamHC_fast() for a definition of + * "correctly initialized"). From a high level, the difference is that this + * function initializes the provided state with a call to + * LZ4_resetStreamHC_fast() while LZ4_compress_HC_extStateHC() starts with a + * call to LZ4_resetStreamHC(). + */ +LZ4LIB_STATIC_API int LZ4_compress_HC_extStateHC_fastReset ( + void* state, + const char* src, char* dst, + int srcSize, int dstCapacity, + int compressionLevel); + +/*! LZ4_attach_HC_dictionary() : + * This is an experimental API that allows for the efficient use of a + * static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ +LZ4LIB_STATIC_API void LZ4_attach_HC_dictionary( + LZ4_streamHC_t *working_stream, + const LZ4_streamHC_t *dictionary_stream); +#if defined (__cplusplus) +} +#endif #endif /* LZ4_HC_SLO_098092834 */ #endif /* LZ4_HC_STATIC_LINKING_ONLY */ diff --git a/lz4libs/lz4opt.h b/lz4libs/lz4opt.h deleted file mode 100644 index 5a8438c1..00000000 --- a/lz4libs/lz4opt.h +++ /dev/null @@ -1,356 +0,0 @@ -/* - lz4opt.h - Optimal Mode of LZ4 - Copyright (C) 2015-2017, Przemyslaw Skibinski - Note : this file is intended to be included within lz4hc.c - - BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - You can contact the author at : - - LZ4 source repository : https://github.com/lz4/lz4 - - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c -*/ - -#define LZ4_OPT_NUM (1<<12) - -typedef struct { - int price; - int off; - int mlen; - int litlen; -} LZ4HC_optimal_t; - - -/* price in bytes */ -LZ4_FORCE_INLINE int LZ4HC_literalsPrice(int const litlen) -{ - int price = litlen; - if (litlen >= (int)RUN_MASK) - price += 1 + (litlen-RUN_MASK)/255; - return price; -} - - -/* requires mlen >= MINMATCH */ -LZ4_FORCE_INLINE int LZ4HC_sequencePrice(int litlen, int mlen) -{ - int price = 1 + 2 ; /* token + 16-bit offset */ - - price += LZ4HC_literalsPrice(litlen); - - if (mlen >= (int)(ML_MASK+MINMATCH)) - price += 1 + (mlen-(ML_MASK+MINMATCH))/255; - - return price; -} - - -/*-************************************* -* Match finder -***************************************/ -typedef struct { - int off; - int len; -} LZ4HC_match_t; - -LZ4_FORCE_INLINE -LZ4HC_match_t LZ4HC_FindLongerMatch(LZ4HC_CCtx_internal* const ctx, - const BYTE* ip, const BYTE* const iHighLimit, - int minLen, int nbSearches) -{ - LZ4HC_match_t match = { 0 , 0 }; - const BYTE* matchPtr = NULL; - /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), - * but this won't be the case here, as we define iLowLimit==ip, - * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ - int const matchLength = LZ4HC_InsertAndGetWiderMatch(ctx, - ip, ip, iHighLimit, minLen, &matchPtr, &ip, - nbSearches, 1 /* patternAnalysis */); - if (matchLength <= minLen) return match; - match.len = matchLength; - match.off = (int)(ip-matchPtr); - return match; -} - - -static int LZ4HC_compress_optimal ( - LZ4HC_CCtx_internal* ctx, - const char* const source, - char* dst, - int* srcSizePtr, - int dstCapacity, - int const nbSearches, - size_t sufficient_len, - limitedOutput_directive limit, - int const fullUpdate - ) -{ -#define TRAILING_LITERALS 3 - LZ4HC_optimal_t opt[LZ4_OPT_NUM + TRAILING_LITERALS]; /* this uses a bit too much stack memory to my taste ... */ - - const BYTE* ip = (const BYTE*) source; - const BYTE* anchor = ip; - const BYTE* const iend = ip + *srcSizePtr; - const BYTE* const mflimit = iend - MFLIMIT; - const BYTE* const matchlimit = iend - LASTLITERALS; - BYTE* op = (BYTE*) dst; - BYTE* opSaved = (BYTE*) dst; - BYTE* oend = op + dstCapacity; - - /* init */ - DEBUGLOG(5, "LZ4HC_compress_optimal"); - *srcSizePtr = 0; - if (limit == limitedDestSize) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ - if (sufficient_len >= LZ4_OPT_NUM) sufficient_len = LZ4_OPT_NUM-1; - - /* Main Loop */ - assert(ip - anchor < LZ4_MAX_INPUT_SIZE); - while (ip < mflimit) { - int const llen = (int)(ip - anchor); - int best_mlen, best_off; - int cur, last_match_pos = 0; - - LZ4HC_match_t const firstMatch = LZ4HC_FindLongerMatch(ctx, ip, matchlimit, MINMATCH-1, nbSearches); - if (firstMatch.len==0) { ip++; continue; } - - if ((size_t)firstMatch.len > sufficient_len) { - /* good enough solution : immediate encoding */ - int const firstML = firstMatch.len; - const BYTE* const matchPos = ip - firstMatch.off; - opSaved = op; - if ( LZ4HC_encodeSequence(&ip, &op, &anchor, firstML, matchPos, limit, oend) ) /* updates ip, op and anchor */ - goto _dest_overflow; - continue; - } - - /* set prices for first positions (literals) */ - { int rPos; - for (rPos = 0 ; rPos < MINMATCH ; rPos++) { - int const cost = LZ4HC_literalsPrice(llen + rPos); - opt[rPos].mlen = 1; - opt[rPos].off = 0; - opt[rPos].litlen = llen + rPos; - opt[rPos].price = cost; - DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", - rPos, cost, opt[rPos].litlen); - } } - /* set prices using initial match */ - { int mlen = MINMATCH; - int const matchML = firstMatch.len; /* necessarily < sufficient_len < LZ4_OPT_NUM */ - int const offset = firstMatch.off; - assert(matchML < LZ4_OPT_NUM); - for ( ; mlen <= matchML ; mlen++) { - int const cost = LZ4HC_sequencePrice(llen, mlen); - opt[mlen].mlen = mlen; - opt[mlen].off = offset; - opt[mlen].litlen = llen; - opt[mlen].price = cost; - DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i) -- initial setup", - mlen, cost, mlen); - } } - last_match_pos = firstMatch.len; - { int addLit; - for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { - opt[last_match_pos+addLit].mlen = 1; /* literal */ - opt[last_match_pos+addLit].off = 0; - opt[last_match_pos+addLit].litlen = addLit; - opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); - DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", - last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); - } } - - /* check further positions */ - for (cur = 1; cur < last_match_pos; cur++) { - const BYTE* const curPtr = ip + cur; - LZ4HC_match_t newMatch; - - if (curPtr >= mflimit) break; - DEBUGLOG(7, "rPos:%u[%u] vs [%u]%u", - cur, opt[cur].price, opt[cur+1].price, cur+1); - if (fullUpdate) { - /* not useful to search here if next position has same (or lower) cost */ - if ( (opt[cur+1].price <= opt[cur].price) - /* in some cases, next position has same cost, but cost rises sharply after, so a small match would still be beneficial */ - && (opt[cur+MINMATCH].price < opt[cur].price + 3/*min seq price*/) ) - continue; - } else { - /* not useful to search here if next position has same (or lower) cost */ - if (opt[cur+1].price <= opt[cur].price) continue; - } - - DEBUGLOG(7, "search at rPos:%u", cur); - if (fullUpdate) - newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, MINMATCH-1, nbSearches); - else - /* only test matches of minimum length; slightly faster, but misses a few bytes */ - newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, last_match_pos - cur, nbSearches); - if (!newMatch.len) continue; - - if ( ((size_t)newMatch.len > sufficient_len) - || (newMatch.len + cur >= LZ4_OPT_NUM) ) { - /* immediate encoding */ - best_mlen = newMatch.len; - best_off = newMatch.off; - last_match_pos = cur + 1; - goto encode; - } - - /* before match : set price with literals at beginning */ - { int const baseLitlen = opt[cur].litlen; - int litlen; - for (litlen = 1; litlen < MINMATCH; litlen++) { - int const price = opt[cur].price - LZ4HC_literalsPrice(baseLitlen) + LZ4HC_literalsPrice(baseLitlen+litlen); - int const pos = cur + litlen; - if (price < opt[pos].price) { - opt[pos].mlen = 1; /* literal */ - opt[pos].off = 0; - opt[pos].litlen = baseLitlen+litlen; - opt[pos].price = price; - DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", - pos, price, opt[pos].litlen); - } } } - - /* set prices using match at position = cur */ - { int const matchML = newMatch.len; - int ml = MINMATCH; - - assert(cur + newMatch.len < LZ4_OPT_NUM); - for ( ; ml <= matchML ; ml++) { - int const pos = cur + ml; - int const offset = newMatch.off; - int price; - int ll; - DEBUGLOG(7, "testing price rPos %i (last_match_pos=%i)", - pos, last_match_pos); - if (opt[cur].mlen == 1) { - ll = opt[cur].litlen; - price = ((cur > ll) ? opt[cur - ll].price : 0) - + LZ4HC_sequencePrice(ll, ml); - } else { - ll = 0; - price = opt[cur].price + LZ4HC_sequencePrice(0, ml); - } - - if (pos > last_match_pos+TRAILING_LITERALS || price <= opt[pos].price) { - DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i)", - pos, price, ml); - assert(pos < LZ4_OPT_NUM); - if ( (ml == matchML) /* last pos of last match */ - && (last_match_pos < pos) ) - last_match_pos = pos; - opt[pos].mlen = ml; - opt[pos].off = offset; - opt[pos].litlen = ll; - opt[pos].price = price; - } } } - /* complete following positions with literals */ - { int addLit; - for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { - opt[last_match_pos+addLit].mlen = 1; /* literal */ - opt[last_match_pos+addLit].off = 0; - opt[last_match_pos+addLit].litlen = addLit; - opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); - DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); - } } - } /* for (cur = 1; cur <= last_match_pos; cur++) */ - - best_mlen = opt[last_match_pos].mlen; - best_off = opt[last_match_pos].off; - cur = last_match_pos - best_mlen; - -encode: /* cur, last_match_pos, best_mlen, best_off must be set */ - assert(cur < LZ4_OPT_NUM); - assert(last_match_pos >= 1); /* == 1 when only one candidate */ - DEBUGLOG(6, "reverse traversal, looking for shortest path") - DEBUGLOG(6, "last_match_pos = %i", last_match_pos); - { int candidate_pos = cur; - int selected_matchLength = best_mlen; - int selected_offset = best_off; - while (1) { /* from end to beginning */ - int const next_matchLength = opt[candidate_pos].mlen; /* can be 1, means literal */ - int const next_offset = opt[candidate_pos].off; - DEBUGLOG(6, "pos %i: sequence length %i", candidate_pos, selected_matchLength); - opt[candidate_pos].mlen = selected_matchLength; - opt[candidate_pos].off = selected_offset; - selected_matchLength = next_matchLength; - selected_offset = next_offset; - if (next_matchLength > candidate_pos) break; /* last match elected, first match to encode */ - assert(next_matchLength > 0); /* can be 1, means literal */ - candidate_pos -= next_matchLength; - } } - - /* encode all recorded sequences in order */ - { int rPos = 0; /* relative position (to ip) */ - while (rPos < last_match_pos) { - int const ml = opt[rPos].mlen; - int const offset = opt[rPos].off; - if (ml == 1) { ip++; rPos++; continue; } /* literal; note: can end up with several literals, in which case, skip them */ - rPos += ml; - assert(ml >= MINMATCH); - assert((offset >= 1) && (offset <= MAX_DISTANCE)); - opSaved = op; - if ( LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ip - offset, limit, oend) ) /* updates ip, op and anchor */ - goto _dest_overflow; - } } - } /* while (ip < mflimit) */ - -_last_literals: - /* Encode Last Literals */ - { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ - size_t litLength = (lastRunSize + 255 - RUN_MASK) / 255; - size_t const totalSize = 1 + litLength + lastRunSize; - if (limit == limitedDestSize) oend += LASTLITERALS; /* restore correct value */ - if (limit && (op + totalSize > oend)) { - if (limit == limitedOutput) return 0; /* Check output limit */ - /* adapt lastRunSize to fill 'dst' */ - lastRunSize = (size_t)(oend - op) - 1; - litLength = (lastRunSize + 255 - RUN_MASK) / 255; - lastRunSize -= litLength; - } - ip = anchor + lastRunSize; - - if (lastRunSize >= RUN_MASK) { - size_t accumulator = lastRunSize - RUN_MASK; - *op++ = (RUN_MASK << ML_BITS); - for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; - *op++ = (BYTE) accumulator; - } else { - *op++ = (BYTE)(lastRunSize << ML_BITS); - } - memcpy(op, anchor, lastRunSize); - op += lastRunSize; - } - - /* End */ - *srcSizePtr = (int) (((const char*)ip) - source); - return (int) ((char*)op-dst); - -_dest_overflow: - if (limit == limitedDestSize) { - op = opSaved; /* restore correct out pointer */ - goto _last_literals; - } - return 0; -} diff --git a/lz4libs/xxhash.c b/lz4libs/xxhash.c index bcf1f1db..ff28749e 100644 --- a/lz4libs/xxhash.c +++ b/lz4libs/xxhash.c @@ -50,20 +50,26 @@ * Prefer these methods in priority order (0 > 1 > 2) */ #ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ -# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ + || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ + || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) # define XXH_FORCE_MEMORY_ACCESS 2 -# elif defined(__INTEL_COMPILER) || \ - (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) )) +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \ + (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ + || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ + || defined(__ARM_ARCH_7S__) )) # define XXH_FORCE_MEMORY_ACCESS 1 # endif #endif /*!XXH_ACCEPT_NULL_INPUT_POINTER : - * If the input pointer is a null pointer, xxHash default behavior is to trigger a memory access error, since it is a bad pointer. - * When this option is enabled, xxHash output for null input pointers will be the same as a null-length input. - * By default, this option is disabled. To enable it, uncomment below define : + * If input pointer is NULL, xxHash default behavior is to dereference it, triggering a segfault. + * When this macro is enabled, xxHash actively checks input for null pointer. + * It it is, result for null input pointers is the same as a null-length input. */ -/* #define XXH_ACCEPT_NULL_INPUT_POINTER 1 */ +#ifndef XXH_ACCEPT_NULL_INPUT_POINTER /* can be defined externally */ +# define XXH_ACCEPT_NULL_INPUT_POINTER 0 +#endif /*!XXH_FORCE_NATIVE_FORMAT : * By default, xxHash library provides endian-independent Hash values, based on little-endian convention. @@ -80,8 +86,9 @@ /*!XXH_FORCE_ALIGN_CHECK : * This is a minor performance trick, only useful with lots of very small keys. * It means : check for aligned/unaligned input. - * The check costs one initial branch per hash; set to 0 when the input data - * is guaranteed to be aligned. + * The check costs one initial branch per hash; + * set it to 0 when the input is guaranteed to be aligned, + * or when alignment doesn't matter for performance. */ #ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ # if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) @@ -104,6 +111,8 @@ static void XXH_free (void* p) { free(p); } #include static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); } +#include /* assert */ + #define XXH_STATIC_LINKING_ONLY #include "xxhash.h" @@ -113,40 +122,35 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcp ***************************************/ #ifdef _MSC_VER /* Visual Studio */ # pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ -#endif - -#ifndef XXH_FORCE_INLINE -# ifdef _MSC_VER /* Visual Studio */ -# define XXH_FORCE_INLINE static __forceinline -# else -# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ -# ifdef __GNUC__ -# define XXH_FORCE_INLINE static inline __attribute__((always_inline)) -# else -# define XXH_FORCE_INLINE static inline -# endif +# define FORCE_INLINE static __forceinline +#else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define FORCE_INLINE static inline __attribute__((always_inline)) # else -# define XXH_FORCE_INLINE static -# endif /* __STDC_VERSION__ */ -# endif /* _MSC_VER */ -#endif /* XXH_FORCE_INLINE */ +# define FORCE_INLINE static inline +# endif +# else +# define FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +#endif /* ************************************* * Basic Types ***************************************/ #ifndef MEM_MODULE -# if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) # include typedef uint8_t BYTE; typedef uint16_t U16; typedef uint32_t U32; - typedef int32_t S32; # else typedef unsigned char BYTE; typedef unsigned short U16; typedef unsigned int U32; - typedef signed int S32; # endif #endif @@ -213,8 +217,12 @@ typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess; /* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */ #ifndef XXH_CPU_LITTLE_ENDIAN - static const int g_one = 1; -# define XXH_CPU_LITTLE_ENDIAN (*(const char*)(&g_one)) +static int XXH_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} +# define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian() #endif @@ -223,7 +231,7 @@ typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess; *****************************/ typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment; -XXH_FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) { if (align==XXH_unaligned) return endian==XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); @@ -231,7 +239,7 @@ XXH_FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, X return endian==XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr); } -XXH_FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) +FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) { return XXH_readLE32_align(ptr, endian, XXH_unaligned); } @@ -245,12 +253,12 @@ static U32 XXH_readBE32(const void* ptr) /* ************************************* * Macros ***************************************/ -#define XXH_STATIC_ASSERT(c) { enum { XXH_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ +#define XXH_STATIC_ASSERT(c) { enum { XXH_sa = 1/(int)(!!(c)) }; } /* use after variable declarations */ XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } /* ******************************************************************* -* 32-bits hash functions +* 32-bit hash functions *********************************************************************/ static const U32 PRIME32_1 = 2654435761U; static const U32 PRIME32_2 = 2246822519U; @@ -266,14 +274,89 @@ static U32 XXH32_round(U32 seed, U32 input) return seed; } -XXH_FORCE_INLINE U32 XXH32_endian_align(const void* input, size_t len, U32 seed, XXH_endianess endian, XXH_alignment align) +/* mix all bits */ +static U32 XXH32_avalanche(U32 h32) +{ + h32 ^= h32 >> 15; + h32 *= PRIME32_2; + h32 ^= h32 >> 13; + h32 *= PRIME32_3; + h32 ^= h32 >> 16; + return(h32); +} + +#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) + +static U32 +XXH32_finalize(U32 h32, const void* ptr, size_t len, + XXH_endianess endian, XXH_alignment align) + +{ + const BYTE* p = (const BYTE*)ptr; + +#define PROCESS1 \ + h32 += (*p++) * PRIME32_5; \ + h32 = XXH_rotl32(h32, 11) * PRIME32_1 ; + +#define PROCESS4 \ + h32 += XXH_get32bits(p) * PRIME32_3; \ + p+=4; \ + h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; + + switch(len&15) /* or switch(bEnd - p) */ + { + case 12: PROCESS4; + /* fallthrough */ + case 8: PROCESS4; + /* fallthrough */ + case 4: PROCESS4; + return XXH32_avalanche(h32); + + case 13: PROCESS4; + /* fallthrough */ + case 9: PROCESS4; + /* fallthrough */ + case 5: PROCESS4; + PROCESS1; + return XXH32_avalanche(h32); + + case 14: PROCESS4; + /* fallthrough */ + case 10: PROCESS4; + /* fallthrough */ + case 6: PROCESS4; + PROCESS1; + PROCESS1; + return XXH32_avalanche(h32); + + case 15: PROCESS4; + /* fallthrough */ + case 11: PROCESS4; + /* fallthrough */ + case 7: PROCESS4; + /* fallthrough */ + case 3: PROCESS1; + /* fallthrough */ + case 2: PROCESS1; + /* fallthrough */ + case 1: PROCESS1; + /* fallthrough */ + case 0: return XXH32_avalanche(h32); + } + assert(0); + return h32; /* reaching this point is deemed impossible */ +} + + +FORCE_INLINE U32 +XXH32_endian_align(const void* input, size_t len, U32 seed, + XXH_endianess endian, XXH_alignment align) { const BYTE* p = (const BYTE*)input; const BYTE* bEnd = p + len; U32 h32; -#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) -#ifdef XXH_ACCEPT_NULL_INPUT_POINTER +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) if (p==NULL) { len=0; bEnd=p=(const BYTE*)(size_t)16; @@ -281,7 +364,7 @@ XXH_FORCE_INLINE U32 XXH32_endian_align(const void* input, size_t len, U32 seed, #endif if (len>=16) { - const BYTE* const limit = bEnd - 16; + const BYTE* const limit = bEnd - 15; U32 v1 = seed + PRIME32_1 + PRIME32_2; U32 v2 = seed + PRIME32_2; U32 v3 = seed + 0; @@ -292,34 +375,17 @@ XXH_FORCE_INLINE U32 XXH32_endian_align(const void* input, size_t len, U32 seed, v2 = XXH32_round(v2, XXH_get32bits(p)); p+=4; v3 = XXH32_round(v3, XXH_get32bits(p)); p+=4; v4 = XXH32_round(v4, XXH_get32bits(p)); p+=4; - } while (p<=limit); + } while (p < limit); - h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); + h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); } else { h32 = seed + PRIME32_5; } - h32 += (U32) len; - - while (p+4<=bEnd) { - h32 += XXH_get32bits(p) * PRIME32_3; - h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; - p+=4; - } - - while (p> 15; - h32 *= PRIME32_2; - h32 ^= h32 >> 13; - h32 *= PRIME32_3; - h32 ^= h32 >> 16; + h32 += (U32)len; - return h32; + return XXH32_finalize(h32, p, len&15, endian, align); } @@ -371,74 +437,81 @@ XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed) { XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ - memset(&state, 0, sizeof(state)-4); /* do not write into reserved, for future removal */ + memset(&state, 0, sizeof(state)); state.v1 = seed + PRIME32_1 + PRIME32_2; state.v2 = seed + PRIME32_2; state.v3 = seed + 0; state.v4 = seed - PRIME32_1; - memcpy(statePtr, &state, sizeof(state)); + /* do not write into reserved, planned to be removed in a future version */ + memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); return XXH_OK; } -XXH_FORCE_INLINE XXH_errorcode XXH32_update_endian (XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) +FORCE_INLINE XXH_errorcode +XXH32_update_endian(XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) { - const BYTE* p = (const BYTE*)input; - const BYTE* const bEnd = p + len; - -#ifdef XXH_ACCEPT_NULL_INPUT_POINTER - if (input==NULL) return XXH_ERROR; + if (input==NULL) +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + return XXH_OK; +#else + return XXH_ERROR; #endif - state->total_len_32 += (unsigned)len; - state->large_len |= (len>=16) | (state->total_len_32>=16); + { const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; - if (state->memsize + len < 16) { /* fill in tmp buffer */ - XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); - state->memsize += (unsigned)len; - return XXH_OK; - } + state->total_len_32 += (unsigned)len; + state->large_len |= (len>=16) | (state->total_len_32>=16); - if (state->memsize) { /* some data left from previous update */ - XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize); - { const U32* p32 = state->mem32; - state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; - state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; - state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; - state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); p32++; + if (state->memsize + len < 16) { /* fill in tmp buffer */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); + state->memsize += (unsigned)len; + return XXH_OK; } - p += 16-state->memsize; - state->memsize = 0; - } - - if (p <= bEnd-16) { - const BYTE* const limit = bEnd - 16; - U32 v1 = state->v1; - U32 v2 = state->v2; - U32 v3 = state->v3; - U32 v4 = state->v4; - do { - v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4; - v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4; - v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4; - v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4; - } while (p<=limit); + if (state->memsize) { /* some data left from previous update */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize); + { const U32* p32 = state->mem32; + state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; + state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; + state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; + state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); + } + p += 16-state->memsize; + state->memsize = 0; + } - state->v1 = v1; - state->v2 = v2; - state->v3 = v3; - state->v4 = v4; - } + if (p <= bEnd-16) { + const BYTE* const limit = bEnd - 16; + U32 v1 = state->v1; + U32 v2 = state->v2; + U32 v3 = state->v3; + U32 v4 = state->v4; + + do { + v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4; + v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4; + v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4; + v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } - if (p < bEnd) { - XXH_memcpy(state->mem32, p, (size_t)(bEnd-p)); - state->memsize = (unsigned)(bEnd-p); + if (p < bEnd) { + XXH_memcpy(state->mem32, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } } return XXH_OK; } + XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* input, size_t len) { XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; @@ -450,40 +523,23 @@ XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* } - -XXH_FORCE_INLINE U32 XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian) +FORCE_INLINE U32 +XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian) { - const BYTE * p = (const BYTE*)state->mem32; - const BYTE* const bEnd = (const BYTE*)(state->mem32) + state->memsize; U32 h32; if (state->large_len) { - h32 = XXH_rotl32(state->v1, 1) + XXH_rotl32(state->v2, 7) + XXH_rotl32(state->v3, 12) + XXH_rotl32(state->v4, 18); + h32 = XXH_rotl32(state->v1, 1) + + XXH_rotl32(state->v2, 7) + + XXH_rotl32(state->v3, 12) + + XXH_rotl32(state->v4, 18); } else { h32 = state->v3 /* == seed */ + PRIME32_5; } h32 += state->total_len_32; - while (p+4<=bEnd) { - h32 += XXH_readLE32(p, endian) * PRIME32_3; - h32 = XXH_rotl32(h32, 17) * PRIME32_4; - p+=4; - } - - while (p> 15; - h32 *= PRIME32_2; - h32 ^= h32 >> 13; - h32 *= PRIME32_3; - h32 ^= h32 >> 16; - - return h32; + return XXH32_finalize(h32, state->mem32, state->memsize, endian, XXH_aligned); } @@ -503,7 +559,7 @@ XXH_PUBLIC_API unsigned int XXH32_digest (const XXH32_state_t* state_in) /*! Default XXH result types are basic unsigned 32 and 64 bits. * The canonical representation follows human-readable write convention, aka big-endian (large digits first). * These functions allow transformation of hash result into and from its canonical format. -* This way, hash values can be written into a file or buffer, and remain comparable across different systems and programs. +* This way, hash values can be written into a file or buffer, remaining comparable across different systems. */ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) @@ -522,18 +578,21 @@ XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src #ifndef XXH_NO_LONG_LONG /* ******************************************************************* -* 64-bits hash functions +* 64-bit hash functions *********************************************************************/ /*====== Memory access ======*/ #ifndef MEM_MODULE # define MEM_MODULE -# if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) # include typedef uint64_t U64; # else - typedef unsigned long long U64; /* if your compiler doesn't support unsigned long long, replace by another 64-bit type here. Note that xxhash.h will also need to be updated. */ + /* if compiler doesn't support unsigned long long, replace by another 64-bit type */ + typedef unsigned long long U64; # endif #endif @@ -583,7 +642,7 @@ static U64 XXH_swap64 (U64 x) } #endif -XXH_FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) { if (align==XXH_unaligned) return endian==XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); @@ -591,7 +650,7 @@ XXH_FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, X return endian==XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr); } -XXH_FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) +FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) { return XXH_readLE64_align(ptr, endian, XXH_unaligned); } @@ -626,14 +685,137 @@ static U64 XXH64_mergeRound(U64 acc, U64 val) return acc; } -XXH_FORCE_INLINE U64 XXH64_endian_align(const void* input, size_t len, U64 seed, XXH_endianess endian, XXH_alignment align) +static U64 XXH64_avalanche(U64 h64) +{ + h64 ^= h64 >> 33; + h64 *= PRIME64_2; + h64 ^= h64 >> 29; + h64 *= PRIME64_3; + h64 ^= h64 >> 32; + return h64; +} + + +#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) + +static U64 +XXH64_finalize(U64 h64, const void* ptr, size_t len, + XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)ptr; + +#define PROCESS1_64 \ + h64 ^= (*p++) * PRIME64_5; \ + h64 = XXH_rotl64(h64, 11) * PRIME64_1; + +#define PROCESS4_64 \ + h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; \ + p+=4; \ + h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; + +#define PROCESS8_64 { \ + U64 const k1 = XXH64_round(0, XXH_get64bits(p)); \ + p+=8; \ + h64 ^= k1; \ + h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; \ +} + + switch(len&31) { + case 24: PROCESS8_64; + /* fallthrough */ + case 16: PROCESS8_64; + /* fallthrough */ + case 8: PROCESS8_64; + return XXH64_avalanche(h64); + + case 28: PROCESS8_64; + /* fallthrough */ + case 20: PROCESS8_64; + /* fallthrough */ + case 12: PROCESS8_64; + /* fallthrough */ + case 4: PROCESS4_64; + return XXH64_avalanche(h64); + + case 25: PROCESS8_64; + /* fallthrough */ + case 17: PROCESS8_64; + /* fallthrough */ + case 9: PROCESS8_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 29: PROCESS8_64; + /* fallthrough */ + case 21: PROCESS8_64; + /* fallthrough */ + case 13: PROCESS8_64; + /* fallthrough */ + case 5: PROCESS4_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 26: PROCESS8_64; + /* fallthrough */ + case 18: PROCESS8_64; + /* fallthrough */ + case 10: PROCESS8_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 30: PROCESS8_64; + /* fallthrough */ + case 22: PROCESS8_64; + /* fallthrough */ + case 14: PROCESS8_64; + /* fallthrough */ + case 6: PROCESS4_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 27: PROCESS8_64; + /* fallthrough */ + case 19: PROCESS8_64; + /* fallthrough */ + case 11: PROCESS8_64; + PROCESS1_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 31: PROCESS8_64; + /* fallthrough */ + case 23: PROCESS8_64; + /* fallthrough */ + case 15: PROCESS8_64; + /* fallthrough */ + case 7: PROCESS4_64; + /* fallthrough */ + case 3: PROCESS1_64; + /* fallthrough */ + case 2: PROCESS1_64; + /* fallthrough */ + case 1: PROCESS1_64; + /* fallthrough */ + case 0: return XXH64_avalanche(h64); + } + + /* impossible to reach */ + assert(0); + return 0; /* unreachable, but some compilers complain without it */ +} + +FORCE_INLINE U64 +XXH64_endian_align(const void* input, size_t len, U64 seed, + XXH_endianess endian, XXH_alignment align) { const BYTE* p = (const BYTE*)input; - const BYTE* const bEnd = p + len; + const BYTE* bEnd = p + len; U64 h64; -#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) -#ifdef XXH_ACCEPT_NULL_INPUT_POINTER +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) if (p==NULL) { len=0; bEnd=p=(const BYTE*)(size_t)32; @@ -666,32 +848,7 @@ XXH_FORCE_INLINE U64 XXH64_endian_align(const void* input, size_t len, U64 seed, h64 += (U64) len; - while (p+8<=bEnd) { - U64 const k1 = XXH64_round(0, XXH_get64bits(p)); - h64 ^= k1; - h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; - p+=8; - } - - if (p+4<=bEnd) { - h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; - h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; - p+=4; - } - - while (p> 33; - h64 *= PRIME64_2; - h64 ^= h64 >> 29; - h64 *= PRIME64_3; - h64 ^= h64 >> 32; - - return h64; + return XXH64_finalize(h64, p, len, endian, align); } @@ -741,65 +898,71 @@ XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed) { XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ - memset(&state, 0, sizeof(state)-8); /* do not write into reserved, for future removal */ + memset(&state, 0, sizeof(state)); state.v1 = seed + PRIME64_1 + PRIME64_2; state.v2 = seed + PRIME64_2; state.v3 = seed + 0; state.v4 = seed - PRIME64_1; - memcpy(statePtr, &state, sizeof(state)); + /* do not write into reserved, planned to be removed in a future version */ + memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); return XXH_OK; } -XXH_FORCE_INLINE XXH_errorcode XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) +FORCE_INLINE XXH_errorcode +XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) { - const BYTE* p = (const BYTE*)input; - const BYTE* const bEnd = p + len; - -#ifdef XXH_ACCEPT_NULL_INPUT_POINTER - if (input==NULL) return XXH_ERROR; + if (input==NULL) +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + return XXH_OK; +#else + return XXH_ERROR; #endif - state->total_len += len; - - if (state->memsize + len < 32) { /* fill in tmp buffer */ - XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); - state->memsize += (U32)len; - return XXH_OK; - } + { const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; - if (state->memsize) { /* tmp buffer is full */ - XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize); - state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian)); - state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian)); - state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian)); - state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian)); - p += 32-state->memsize; - state->memsize = 0; - } + state->total_len += len; - if (p+32 <= bEnd) { - const BYTE* const limit = bEnd - 32; - U64 v1 = state->v1; - U64 v2 = state->v2; - U64 v3 = state->v3; - U64 v4 = state->v4; + if (state->memsize + len < 32) { /* fill in tmp buffer */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); + state->memsize += (U32)len; + return XXH_OK; + } - do { - v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8; - v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8; - v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8; - v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8; - } while (p<=limit); + if (state->memsize) { /* tmp buffer is full */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize); + state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian)); + state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian)); + state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian)); + state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian)); + p += 32-state->memsize; + state->memsize = 0; + } - state->v1 = v1; - state->v2 = v2; - state->v3 = v3; - state->v4 = v4; - } + if (p+32 <= bEnd) { + const BYTE* const limit = bEnd - 32; + U64 v1 = state->v1; + U64 v2 = state->v2; + U64 v3 = state->v3; + U64 v4 = state->v4; + + do { + v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8; + v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8; + v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8; + v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } - if (p < bEnd) { - XXH_memcpy(state->mem64, p, (size_t)(bEnd-p)); - state->memsize = (unsigned)(bEnd-p); + if (p < bEnd) { + XXH_memcpy(state->mem64, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } } return XXH_OK; @@ -815,10 +978,8 @@ XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* state_in, const void* return XXH64_update_endian(state_in, input, len, XXH_bigEndian); } -XXH_FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian) +FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian) { - const BYTE * p = (const BYTE*)state->mem64; - const BYTE* const bEnd = (const BYTE*)state->mem64 + state->memsize; U64 h64; if (state->total_len >= 32) { @@ -833,37 +994,12 @@ XXH_FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endian h64 = XXH64_mergeRound(h64, v3); h64 = XXH64_mergeRound(h64, v4); } else { - h64 = state->v3 + PRIME64_5; + h64 = state->v3 /*seed*/ + PRIME64_5; } h64 += (U64) state->total_len; - while (p+8<=bEnd) { - U64 const k1 = XXH64_round(0, XXH_readLE64(p, endian)); - h64 ^= k1; - h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; - p+=8; - } - - if (p+4<=bEnd) { - h64 ^= (U64)(XXH_readLE32(p, endian)) * PRIME64_1; - h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; - p+=4; - } - - while (p> 33; - h64 *= PRIME64_2; - h64 ^= h64 >> 29; - h64 *= PRIME64_3; - h64 ^= h64 >> 32; - - return h64; + return XXH64_finalize(h64, state->mem64, (size_t)state->total_len, endian, XXH_aligned); } XXH_PUBLIC_API unsigned long long XXH64_digest (const XXH64_state_t* state_in) diff --git a/lz4libs/xxhash.h b/lz4libs/xxhash.h index 870a6d91..d6bad943 100644 --- a/lz4libs/xxhash.h +++ b/lz4libs/xxhash.h @@ -57,8 +57,8 @@ Q.Score is a measure of quality of the hash function. It depends on successfully passing SMHasher test set. 10 is a perfect score. -A 64-bits version, named XXH64, is available since r35. -It offers much better speed, but for 64-bits applications only. +A 64-bit version, named XXH64, is available since r35. +It offers much better speed, but for 64-bit applications only. Name Speed on 64 bits Speed on 32 bits XXH64 13.8 GB/s 1.9 GB/s XXH32 6.8 GB/s 6.0 GB/s @@ -80,18 +80,19 @@ typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; /* **************************** -* API modifier -******************************/ -/** XXH_PRIVATE_API -* This is useful to include xxhash functions in `static` mode -* in order to inline them, and remove their symbol from the public list. -* Methodology : -* #define XXH_PRIVATE_API -* #include "xxhash.h" -* `xxhash.c` is automatically included. -* It's not useful to compile and link it as a separate module. -*/ -#ifdef XXH_PRIVATE_API + * API modifier + ******************************/ +/** XXH_INLINE_ALL (and XXH_PRIVATE_API) + * This is useful to include xxhash functions in `static` mode + * in order to inline them, and remove their symbol from the public list. + * Inlining can offer dramatic performance improvement on small keys. + * Methodology : + * #define XXH_INLINE_ALL + * #include "xxhash.h" + * `xxhash.c` is automatically included. + * It's not useful to compile and link it as a separate module. + */ +#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) # ifndef XXH_STATIC_LINKING_ONLY # define XXH_STATIC_LINKING_ONLY # endif @@ -102,23 +103,24 @@ typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; # elif defined(_MSC_VER) # define XXH_PUBLIC_API static __inline # else -# define XXH_PUBLIC_API static /* this version may generate warnings for unused static functions; disable the relevant warning */ + /* this version may generate warnings for unused static functions */ +# define XXH_PUBLIC_API static # endif #else # define XXH_PUBLIC_API /* do nothing */ -#endif /* XXH_PRIVATE_API */ - -/*!XXH_NAMESPACE, aka Namespace Emulation : - -If you want to include _and expose_ xxHash functions from within your own library, -but also want to avoid symbol collisions with other libraries which may also include xxHash, - -you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library -with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values). - -Note that no change is required within the calling program as long as it includes `xxhash.h` : -regular symbol name will be automatically translated by this header. -*/ +#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */ + +/*! XXH_NAMESPACE, aka Namespace Emulation : + * + * If you want to include _and expose_ xxHash functions from within your own library, + * but also want to avoid symbol collisions with other libraries which may also include xxHash, + * + * you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library + * with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values). + * + * Note that no change is required within the calling program as long as it includes `xxhash.h` : + * regular symbol name will be automatically translated by this header. + */ #ifdef XXH_NAMESPACE # define XXH_CAT(A,B) A##B # define XXH_NAME2(A,B) XXH_CAT(A,B) @@ -149,18 +151,18 @@ regular symbol name will be automatically translated by this header. ***************************************/ #define XXH_VERSION_MAJOR 0 #define XXH_VERSION_MINOR 6 -#define XXH_VERSION_RELEASE 2 +#define XXH_VERSION_RELEASE 5 #define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) XXH_PUBLIC_API unsigned XXH_versionNumber (void); /*-********************************************************************** -* 32-bits hash +* 32-bit hash ************************************************************************/ -typedef unsigned int XXH32_hash_t; +typedef unsigned int XXH32_hash_t; /*! XXH32() : - Calculate the 32-bits hash of sequence "length" bytes stored at memory address "input". + Calculate the 32-bit hash of sequence "length" bytes stored at memory address "input". The memory between input & input+length must be valid (allocated and read-accessible). "seed" can be used to alter the result predictably. Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */ @@ -177,26 +179,25 @@ XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); /* -These functions generate the xxHash of an input provided in multiple segments. -Note that, for small input, they are slower than single-call functions, due to state management. -For small input, prefer `XXH32()` and `XXH64()` . - -XXH state must first be allocated, using XXH*_createState() . - -Start a new hash by initializing state with a seed, using XXH*_reset(). - -Then, feed the hash state by calling XXH*_update() as many times as necessary. -Obviously, input must be allocated and read accessible. -The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. - -Finally, a hash value can be produced anytime, by using XXH*_digest(). -This function returns the nn-bits hash as an int or long long. - -It's still possible to continue inserting input into the hash state after a digest, -and generate some new hashes later on, by calling again XXH*_digest(). - -When done, free XXH state space if it was allocated dynamically. -*/ + * Streaming functions generate the xxHash of an input provided in multiple segments. + * Note that, for small input, they are slower than single-call functions, due to state management. + * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. + * + * XXH state must first be allocated, using XXH*_createState() . + * + * Start a new hash by initializing state with a seed, using XXH*_reset(). + * + * Then, feed the hash state by calling XXH*_update() as many times as necessary. + * The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. + * + * Finally, a hash value can be produced anytime, by using XXH*_digest(). + * This function returns the nn-bits hash as an int or long long. + * + * It's still possible to continue inserting input into the hash state after a digest, + * and generate some new hashes later on, by calling again XXH*_digest(). + * + * When done, free XXH state space if it was allocated dynamically. + */ /*====== Canonical representation ======*/ @@ -205,22 +206,22 @@ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); /* Default result type for XXH functions are primitive unsigned 32 and 64 bits. -* The canonical representation uses human-readable write convention, aka big-endian (large digits first). -* These functions allow transformation of hash result into and from its canonical format. -* This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. -*/ + * The canonical representation uses human-readable write convention, aka big-endian (large digits first). + * These functions allow transformation of hash result into and from its canonical format. + * This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. + */ #ifndef XXH_NO_LONG_LONG /*-********************************************************************** -* 64-bits hash +* 64-bit hash ************************************************************************/ typedef unsigned long long XXH64_hash_t; /*! XXH64() : - Calculate the 64-bits hash of sequence of length "len" stored at memory address "input". + Calculate the 64-bit hash of sequence of length "len" stored at memory address "input". "seed" can be used to alter the result predictably. - This function runs faster on 64-bits systems, but slower on 32-bits systems (see benchmark). + This function runs faster on 64-bit systems, but slower on 32-bit systems (see benchmark). */ XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, unsigned long long seed); @@ -241,48 +242,82 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src #endif /* XXH_NO_LONG_LONG */ + #ifdef XXH_STATIC_LINKING_ONLY /* ================================================================================================ - This section contains definitions which are not guaranteed to remain stable. + This section contains declarations which are not guaranteed to remain stable. They may change in future versions, becoming incompatible with a different version of the library. - They shall only be used with static linking. - Never use these definitions in association with dynamic linking ! + These declarations should only be used with static linking. + Never use them in association with dynamic linking ! =================================================================================================== */ -/* These definitions are only meant to allow allocation of XXH state - statically, on stack, or in a struct for example. - Do not use members directly. */ - - struct XXH32_state_s { - unsigned total_len_32; - unsigned large_len; - unsigned v1; - unsigned v2; - unsigned v3; - unsigned v4; - unsigned mem32[4]; /* buffer defined as U32 for alignment */ - unsigned memsize; - unsigned reserved; /* never read nor write, will be removed in a future version */ - }; /* typedef'd to XXH32_state_t */ - -#ifndef XXH_NO_LONG_LONG - struct XXH64_state_s { - unsigned long long total_len; - unsigned long long v1; - unsigned long long v2; - unsigned long long v3; - unsigned long long v4; - unsigned long long mem64[4]; /* buffer defined as U64 for alignment */ - unsigned memsize; - unsigned reserved[2]; /* never read nor write, will be removed in a future version */ - }; /* typedef'd to XXH64_state_t */ +/* These definitions are only present to allow + * static allocation of XXH state, on stack or in a struct for example. + * Never **ever** use members directly. */ + +#if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + +struct XXH32_state_s { + uint32_t total_len_32; + uint32_t large_len; + uint32_t v1; + uint32_t v2; + uint32_t v3; + uint32_t v4; + uint32_t mem32[4]; + uint32_t memsize; + uint32_t reserved; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH32_state_t */ + +struct XXH64_state_s { + uint64_t total_len; + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t v4; + uint64_t mem64[4]; + uint32_t memsize; + uint32_t reserved[2]; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH64_state_t */ + +# else + +struct XXH32_state_s { + unsigned total_len_32; + unsigned large_len; + unsigned v1; + unsigned v2; + unsigned v3; + unsigned v4; + unsigned mem32[4]; + unsigned memsize; + unsigned reserved; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH32_state_t */ + +# ifndef XXH_NO_LONG_LONG /* remove 64-bit support */ +struct XXH64_state_s { + unsigned long long total_len; + unsigned long long v1; + unsigned long long v2; + unsigned long long v3; + unsigned long long v4; + unsigned long long mem64[4]; + unsigned memsize; + unsigned reserved[2]; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH64_state_t */ +# endif + +# endif + + +#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) +# include "xxhash.c" /* include xxhash function bodies as `static`, for inlining */ #endif -# ifdef XXH_PRIVATE_API -# include "xxhash.c" /* include xxhash function bodies as `static`, for inlining */ -# endif - #endif /* XXH_STATIC_LINKING_ONLY */ diff --git a/py3c/py3c.h b/py3c/py3c.h deleted file mode 100644 index b86324b3..00000000 --- a/py3c/py3c.h +++ /dev/null @@ -1,34 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2015, Red Hat, Inc. and/or its affiliates - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#ifndef _PY3COMPAT_H_ -#define _PY3COMPAT_H_ - -#include - -#include -#include -#include - -#endif diff --git a/py3c/py3c/capsulethunk.h b/py3c/py3c/capsulethunk.h deleted file mode 100644 index 6ad70041..00000000 --- a/py3c/py3c/capsulethunk.h +++ /dev/null @@ -1,141 +0,0 @@ -/* Copyright (c) 2011, Larry Hastings - * Copyright (c) 2015, py3c contributors - * Licensed under the MIT license; see py3c.h - * - * (Note: Relicensed from PSF: http://bugs.python.org/issue24937#msg250191 ) - */ - -#ifndef __CAPSULETHUNK_H -#define __CAPSULETHUNK_H - -#if ( (PY_VERSION_HEX < 0x02070000) \ - || ((PY_VERSION_HEX >= 0x03000000) \ - && (PY_VERSION_HEX < 0x03010000)) ) - -#define __PyCapsule_GetField(capsule, field, error_value) \ - ( PyCapsule_CheckExact(capsule) \ - ? (((PyCObject *)capsule)->field) \ - : (PyErr_SetString(PyExc_TypeError, "CObject required"), (error_value)) \ - ) \ - -#define __PyCapsule_SetField(capsule, field, value) \ - ( PyCapsule_CheckExact(capsule) \ - ? (((PyCObject *)capsule)->field = value), 0 \ - : (PyErr_SetString(PyExc_TypeError, "CObject required"), 1) \ - ) \ - - -#define PyCapsule_Type PyCObject_Type - -#define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) -#define PyCapsule_IsValid(capsule, name) (PyCObject_Check(capsule)) - - -#define PyCapsule_New(pointer, name, destructor) \ - (PyCObject_FromVoidPtr(pointer, (void (*)(void*)) (destructor))) - - -#define PyCapsule_GetPointer(capsule, name) \ - (PyCObject_AsVoidPtr(capsule)) - -/* Don't call PyCObject_SetPointer here, it fails if there's a destructor */ -#define PyCapsule_SetPointer(capsule, pointer) \ - __PyCapsule_SetField(capsule, cobject, pointer) - - -#define PyCapsule_GetDestructor(capsule) \ - __PyCapsule_GetField(capsule, destructor, (void (*)(void*)) NULL) - -#define PyCapsule_SetDestructor(capsule, dtor) \ - __PyCapsule_SetField(capsule, destructor, (void (*)(void*)) dtor) - - -/* - * Sorry, there's simply no place - * to store a Capsule "name" in a CObject. - */ -#define PyCapsule_GetName(capsule) NULL - -static int -PyCapsule_SetName(PyObject *capsule, const char *unused) -{ - unused = unused; - PyErr_SetString(PyExc_NotImplementedError, - "can't use PyCapsule_SetName with CObjects"); - return 1; -} - - - -#define PyCapsule_GetContext(capsule) \ - __PyCapsule_GetField(capsule, desc, (void*) NULL) - -#define PyCapsule_SetContext(capsule, context) \ - __PyCapsule_SetField(capsule, desc, context) - - -static void * -PyCapsule_Import(const char *name, int no_block) -{ - PyObject *object = NULL; - void *return_value = NULL; - char *trace; - size_t name_length = (strlen(name) + 1) * sizeof(char); - char *name_dup = (char *)PyMem_MALLOC(name_length); - - if (!name_dup) { - return NULL; - } - - memcpy(name_dup, name, name_length); - - trace = name_dup; - while (trace) { - char *dot = strchr(trace, '.'); - if (dot) { - *dot++ = '\0'; - } - - if (object == NULL) { - if (no_block) { - object = PyImport_ImportModuleNoBlock(trace); - } else { - object = PyImport_ImportModule(trace); - if (!object) { - PyErr_Format(PyExc_ImportError, - "PyCapsule_Import could not " - "import module \"%s\"", trace); - } - } - } else { - PyObject *object2 = PyObject_GetAttrString(object, trace); - Py_DECREF(object); - object = object2; - } - if (!object) { - goto EXIT; - } - - trace = dot; - } - - if (PyCObject_Check(object)) { - PyCObject *cobject = (PyCObject *)object; - return_value = cobject->cobject; - } else { - PyErr_Format(PyExc_AttributeError, - "PyCapsule_Import \"%s\" is not valid", - name); - } - -EXIT: - Py_XDECREF(object); - if (name_dup) { - PyMem_FREE(name_dup); - } - return return_value; -} - -#endif /* #if PY_VERSION_HEX < 0x02070000 */ - -#endif /* __CAPSULETHUNK_H */ diff --git a/py3c/py3c/comparison.h b/py3c/py3c/comparison.h deleted file mode 100644 index 9d3765a4..00000000 --- a/py3c/py3c/comparison.h +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright (c) 2015, Red Hat, Inc. and/or its affiliates - * Licensed under the MIT license; see py3c.h - */ - -#ifndef _PY3C_COMPARISON_H_ -#define _PY3C_COMPARISON_H_ -#include - -/* Rich comparisons */ - -#ifndef Py_RETURN_NOTIMPLEMENTED -#define Py_RETURN_NOTIMPLEMENTED \ - return Py_INCREF(Py_NotImplemented), Py_NotImplemented -#endif - -#ifndef Py_UNREACHABLE -#define Py_UNREACHABLE() abort() -#endif - -#ifndef Py_RETURN_RICHCOMPARE -#define Py_RETURN_RICHCOMPARE(val1, val2, op) \ - do { \ - switch (op) { \ - case Py_EQ: if ((val1) == (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ - case Py_NE: if ((val1) != (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ - case Py_LT: if ((val1) < (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ - case Py_GT: if ((val1) > (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ - case Py_LE: if ((val1) <= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ - case Py_GE: if ((val1) >= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ - default: \ - Py_UNREACHABLE(); \ - } \ - } while (0) -#endif - -#define PY3C_RICHCMP(val1, val2, op) \ - ((op) == Py_EQ) ? PyBool_FromLong((val1) == (val2)) : \ - ((op) == Py_NE) ? PyBool_FromLong((val1) != (val2)) : \ - ((op) == Py_LT) ? PyBool_FromLong((val1) < (val2)) : \ - ((op) == Py_GT) ? PyBool_FromLong((val1) > (val2)) : \ - ((op) == Py_LE) ? PyBool_FromLong((val1) <= (val2)) : \ - ((op) == Py_GE) ? PyBool_FromLong((val1) >= (val2)) : \ - (Py_INCREF(Py_NotImplemented), Py_NotImplemented) - -#endif diff --git a/py3c/py3c/compat.h b/py3c/py3c/compat.h deleted file mode 100644 index 15d32def..00000000 --- a/py3c/py3c/compat.h +++ /dev/null @@ -1,139 +0,0 @@ -/* Copyright (c) 2015, Red Hat, Inc. and/or its affiliates - * Licensed under the MIT license; see py3c.h - */ - -#ifndef _PY3C_COMPAT_H_ -#define _PY3C_COMPAT_H_ -#include - -#if PY_MAJOR_VERSION >= 3 - -/***** Python 3 *****/ - -#define IS_PY3 1 - -/* Strings */ - -#define PyStr_Type PyUnicode_Type -#define PyStr_Check PyUnicode_Check -#define PyStr_CheckExact PyUnicode_CheckExact -#define PyStr_FromString PyUnicode_FromString -#define PyStr_FromStringAndSize PyUnicode_FromStringAndSize -#define PyStr_FromFormat PyUnicode_FromFormat -#define PyStr_FromFormatV PyUnicode_FromFormatV -#define PyStr_AsString PyUnicode_AsUTF8 -#define PyStr_Concat PyUnicode_Concat -#define PyStr_Format PyUnicode_Format -#define PyStr_InternInPlace PyUnicode_InternInPlace -#define PyStr_InternFromString PyUnicode_InternFromString -#define PyStr_Decode PyUnicode_Decode - -#define PyStr_AsUTF8String PyUnicode_AsUTF8String /* returns PyBytes */ -#define PyStr_AsUTF8 PyUnicode_AsUTF8 -#define PyStr_AsUTF8AndSize PyUnicode_AsUTF8AndSize - -/* Ints */ - -#define PyInt_Type PyLong_Type -#define PyInt_Check PyLong_Check -#define PyInt_CheckExact PyLong_CheckExact -#define PyInt_FromString PyLong_FromString -#define PyInt_FromLong PyLong_FromLong -#define PyInt_FromSsize_t PyLong_FromSsize_t -#define PyInt_FromSize_t PyLong_FromSize_t -#define PyInt_AsLong PyLong_AsLong -#define PyInt_AS_LONG PyLong_AS_LONG -#define PyInt_AsUnsignedLongLongMask PyLong_AsUnsignedLongLongMask -#define PyInt_AsSsize_t PyLong_AsSsize_t - -/* Module init */ - -#define MODULE_INIT_FUNC(name) \ - PyMODINIT_FUNC PyInit_ ## name(void); \ - PyMODINIT_FUNC PyInit_ ## name(void) - -#else - -/***** Python 2 *****/ - -#define IS_PY3 0 - -/* Strings */ - -#define PyStr_Type PyString_Type -#define PyStr_Check PyString_Check -#define PyStr_CheckExact PyString_CheckExact -#define PyStr_FromString PyString_FromString -#define PyStr_FromStringAndSize PyString_FromStringAndSize -#define PyStr_FromFormat PyString_FromFormat -#define PyStr_FromFormatV PyString_FromFormatV -#define PyStr_AsString PyString_AsString -#define PyStr_Format PyString_Format -#define PyStr_InternInPlace PyString_InternInPlace -#define PyStr_InternFromString PyString_InternFromString -#define PyStr_Decode PyString_Decode - -#ifdef __GNUC__ -static PyObject *PyStr_Concat(PyObject *left, PyObject *right) __attribute__ ((unused)); -#endif -static PyObject *PyStr_Concat(PyObject *left, PyObject *right) { - PyObject *str = left; - Py_INCREF(left); /* reference to old left will be stolen */ - PyString_Concat(&str, right); - if (str) { - return str; - } else { - return NULL; - } -} - -#define PyStr_AsUTF8String(str) (Py_INCREF(str), (str)) -#define PyStr_AsUTF8 PyString_AsString -#define PyStr_AsUTF8AndSize(pystr, sizeptr) \ - ((*sizeptr=PyString_Size(pystr)), PyString_AsString(pystr)) - -#define PyBytes_Type PyString_Type -#define PyBytes_Check PyString_Check -#define PyBytes_CheckExact PyString_CheckExact -#define PyBytes_FromString PyString_FromString -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#define PyBytes_FromFormat PyString_FromFormat -#define PyBytes_FromFormatV PyString_FromFormatV -#define PyBytes_Size PyString_Size -#define PyBytes_GET_SIZE PyString_GET_SIZE -#define PyBytes_AsString PyString_AsString -#define PyBytes_AS_STRING PyString_AS_STRING -#define PyBytes_AsStringAndSize PyString_AsStringAndSize -#define PyBytes_Concat PyString_Concat -#define PyBytes_ConcatAndDel PyString_ConcatAndDel -#define _PyBytes_Resize _PyString_Resize - -/* Floats */ - -#define PyFloat_FromString(str) PyFloat_FromString(str, NULL) - -/* Module init */ - -#define PyModuleDef_HEAD_INIT 0 - -typedef struct PyModuleDef { - int m_base; - const char* m_name; - const char* m_doc; - Py_ssize_t m_size; - PyMethodDef *m_methods; -} PyModuleDef; - -#define PyModule_Create(def) \ - Py_InitModule3((def)->m_name, (def)->m_methods, (def)->m_doc) - -#define MODULE_INIT_FUNC(name) \ - static PyObject *PyInit_ ## name(void); \ - PyMODINIT_FUNC init ## name(void); \ - PyMODINIT_FUNC init ## name(void) { PyInit_ ## name(); } \ - static PyObject *PyInit_ ## name(void) - - -#endif - -#endif diff --git a/py3c/py3c/fileshim.h b/py3c/py3c/fileshim.h deleted file mode 100644 index ecf31f40..00000000 --- a/py3c/py3c/fileshim.h +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright (c) 2015, Red Hat, Inc. and/or its affiliates - * Licensed under the MIT license; see py3c.h - */ - -#ifndef _PY3C_FILESHIM_H_ -#define _PY3C_FILESHIM_H_ -#include - -/* - -For debugging purposes only. -Caveats: - * Only works on file-like objects backed by an actual file - * All C-level writes should be done before additional - Python-level writes are allowed (e.g. by running Python code). - * Though the function tries to flush, there is no guarantee that - writes will be reordered due to different layers of buffering. - -*/ - -static char FLUSH[] = "flush"; -static char EMPTY_STRING[] = ""; - -static FILE* py3c_PyFile_AsFileWithMode(PyObject *py_file, const char *mode) { - FILE *f; - PyObject *ret; - int fd; - - ret = PyObject_CallMethod(py_file, FLUSH, EMPTY_STRING); - if (ret == NULL) { - return NULL; - } - Py_DECREF(ret); - - fd = PyObject_AsFileDescriptor(py_file); - if (fd == -1) { - return NULL; - } - - f = fdopen(fd, mode); - if (f == NULL) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - - return f; -} - -#endif /* _PY3C_FILESHIM_H_ */ diff --git a/py3c/py3c/py3shims.h b/py3c/py3c/py3shims.h deleted file mode 100644 index 947595e9..00000000 --- a/py3c/py3c/py3shims.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright (c) 2016, Red Hat, Inc. and/or its affiliates - * Licensed under the MIT license; see py3c.h - */ - -/* - * Shims for new functionality from in Python 3.3+ - * - * See https://docs.python.org/3/c-api/memory.html#raw-memory-interface - */ - -#ifndef _PY3C_RAWMALLOC_H_ -#define _PY3C_RAWMALLOC_H_ -#include -#include - - -/* Py_UNUSED - added in Python 3.4, documneted in 3.7 */ - -#ifndef Py_UNUSED -#ifdef __GNUC__ -#define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) -#else -#define Py_UNUSED(name) _unused_ ## name -#endif -#endif - - -/* PyMem_Raw{Malloc,Realloc,Free} - added in Python 3.4 */ - -#if PY_MAJOR_VERSION < 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 4) -#define PyMem_RawMalloc(n) malloc((n) || 1) -#define PyMem_RawRealloc(p, n) realloc(p, (n) || 1) -#define PyMem_RawFree(p) free(p) -#endif /* version < 3.4 */ - - -/* PyMem_RawCalloc - added in Python 3.5 */ - -#if PY_MAJOR_VERSION < 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 5) -#define PyMem_RawCalloc(n, s) calloc((n) || 1, (s) || 1) -#endif /* version < 3.5 */ - - -#endif /* _PY3C_RAWMALLOC_H_ */ diff --git a/py3c/py3c/tpflags.h b/py3c/py3c/tpflags.h deleted file mode 100644 index aa570352..00000000 --- a/py3c/py3c/tpflags.h +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright (c) 2015, Red Hat, Inc. and/or its affiliates - * Licensed under the MIT license; see py3c.h - */ - -/* - * WARNING: These flags are only to be used in class definitions. - * - * Before including this header file, check that you do not use - * these flags with with PyType_HasFeature. Example command: - * grep -r PyType_HasFeature . - * - * In Python 3, *all objects* have the features corresponding to removed flags. - */ - -#ifndef _PY3C_TPFLAGS_H_ -#define _PY3C_TPFLAGS_H_ -#include - -#if PY_MAJOR_VERSION >= 3 - -#define Py_TPFLAGS_HAVE_GETCHARBUFFER 0 -#define Py_TPFLAGS_HAVE_SEQUENCE_IN 0 -#define Py_TPFLAGS_HAVE_INPLACEOPS 0 -#define Py_TPFLAGS_CHECKTYPES 0 -#define Py_TPFLAGS_HAVE_RICHCOMPARE 0 -#define Py_TPFLAGS_HAVE_WEAKREFS 0 -#define Py_TPFLAGS_HAVE_ITER 0 -#define Py_TPFLAGS_HAVE_CLASS 0 -/* Py_TPFLAGS_HEAPTYPE is still optional in py3 */ -/* Py_TPFLAGS_BASETYPE is still optional in py3 */ -/* Py_TPFLAGS_READY is still useful in py3 */ -/* Py_TPFLAGS_READYING is still useful in py3 */ -/* Py_TPFLAGS_HAVE_GC is still optional in py3 */ -/* Py_TPFLAGS_HAVE_STACKLESS_EXTENSION is still optional in py3 */ -#define Py_TPFLAGS_HAVE_INDEX 0 -/* Py_TPFLAGS_HAVE_VERSION_TAG is still optional in py3 */ -/* Py_TPFLAGS_VALID_VERSION_TAG is still optional in py3 */ -/* Py_TPFLAGS_IS_ABSTRACT is still optional in py3 */ -#define Py_TPFLAGS_HAVE_NEWBUFFER 0 -/* Py_TPFLAGS_INT_SUBCLASS is still optional in py3 */ -/* Py_TPFLAGS_LONG_SUBCLASS is still optional in py3 */ -/* Py_TPFLAGS_LIST_SUBCLASS is still optional in py3 */ -/* Py_TPFLAGS_TUPLE_SUBCLASS is still optional in py3 */ -/* Py_TPFLAGS_STRING_SUBCLASS is still optional in py3 */ -/* Py_TPFLAGS_UNICODE_SUBCLASS is still optional in py3 */ -/* Py_TPFLAGS_DICT_SUBCLASS is still optional in py3 */ -/* Py_TPFLAGS_BASE_EXC_SUBCLASS is still optional in py3 */ -/* Py_TPFLAGS_TYPE_SUBCLASS is still optional in py3 */ - -/* py 3.4 adds Py_TPFLAGS_HAVE_FINALIZE */ -#endif -#endif /* _PY3C_TPFLAGS_H_ */ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..c5541b0a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2", "pkgconfig"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +write_to = "lz4/version.py" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3ff12fe6..65529ded 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,5 @@ tox pytest pytest-runner setuptools_scm -deprecation pkgconfig future - diff --git a/setup.py b/setup.py index 7dbc2331..eb9030fa 100644 --- a/setup.py +++ b/setup.py @@ -1,32 +1,46 @@ #!/usr/bin/env python -from setuptools import setup, find_packages, Extension -import subprocess import os +from setuptools import setup, find_packages, Extension +from setuptools.command.build_ext import new_compiler import sys -from distutils import ccompiler + # Note: if updating LZ4_REQUIRED_VERSION you need to update docs/install.rst as # well. LZ4_REQUIRED_VERSION = '>= 1.7.5' -PY3C_REQUIRED_VERSION = '>= 1.0' -# Check to see if we have a lz4 and py3c libraries installed on the system, and -# of suitable versions, and use if so. If not, we'll use the bundled libraries. +# Check to see if we have a suitable lz4 library installed on the system and +# use if so. If not, we'll use the bundled libraries. liblz4_found = False -py3c_found = False + try: - import pkgconfig + from pkgconfig import installed as pkgconfig_installed + from pkgconfig import parse as pkgconfig_parse except ImportError: # pkgconfig is not installed. It will be installed by setup_requires. pass else: - try: - liblz4_found = pkgconfig.installed('liblz4', LZ4_REQUIRED_VERSION) - py3c_found = pkgconfig.installed('py3c', PY3C_REQUIRED_VERSION) - except EnvironmentError: - # Windows, no pkg-config present - pass - + def pkgconfig_installed_check(lib, required_version, default): + installed = default + try: + installed = pkgconfig_installed(lib, required_version) + except EnvironmentError: + # Windows, no pkg-config present + pass + except ValueError: + # pkgconfig was unable to determine if + # required version of liblz4 is available + # Bundled version of liblz4 will be used + pass + return installed + liblz4_found = pkgconfig_installed_check('liblz4', LZ4_REQUIRED_VERSION, default=False) + +# Establish if we want to build experimental functionality or not. +experimental_env = os.environ.get("PYLZ4_EXPERIMENTAL", "False") +if experimental_env.upper() in ("1", "TRUE"): + experimental = True +else: + experimental = False # Set up the extension modules. If a system wide lz4 library is found, and is # recent enough, we'll use that. Otherwise we'll build with the bundled one. If @@ -35,8 +49,7 @@ # against the bundled lz4 files, we'll set the compiler flags to be consistent # with what upstream lz4 recommends. -include_dirs = [] -libraries = [] +extension_kwargs = {} lz4version_sources = [ 'lz4/_version.c' @@ -50,10 +63,20 @@ 'lz4/frame/_frame.c' ] -if liblz4_found is True: - libraries.append('lz4') +lz4stream_sources = [ + 'lz4/stream/_stream.c' +] + +use_system_liblz4_env = os.environ.get("PYLZ4_USE_SYSTEM_LZ4", "True") +if use_system_liblz4_env.upper() in ("1", "TRUE"): + use_system_liblz4 = True +else: + use_system_liblz4 = False + +if liblz4_found is True and use_system_liblz4 is True: + extension_kwargs['libraries'] = ['lz4'] else: - include_dirs.append('lz4libs') + extension_kwargs['include_dirs'] = ['lz4libs'] lz4version_sources.extend( [ 'lz4libs/lz4.c', @@ -73,62 +96,76 @@ 'lz4libs/xxhash.c', ] ) + lz4stream_sources.extend( + [ + 'lz4libs/lz4.c', + 'lz4libs/lz4hc.c', + ] + ) -if py3c_found is False: - include_dirs.append('py3c') - -compiler = ccompiler.get_default_compiler() - -extra_link_args = [] -extra_compile_args = [] +compiler = new_compiler().compiler_type if compiler == 'msvc': - extra_compile_args = ['/Ot', '/Wall'] + extension_kwargs['extra_compile_args'] = [ + '/Ot', + '/Wall', + '/wd4711', + '/wd4820', + ] elif compiler in ('unix', 'mingw32'): - if liblz4_found: - extra_link_args.append(pkgconfig.libs('liblz4')) - if pkgconfig.cflags('liblz4'): - extra_compile_args.append(pkgconfig.cflags('liblz4')) + if liblz4_found is True and use_system_liblz4 is True: + extension_kwargs = pkgconfig_parse('liblz4') else: - extra_compile_args = [ + extension_kwargs['extra_compile_args'] = [ '-O3', '-Wall', '-Wundef' ] + # strip debug symbols from libraries to reduce wheel size + extension_kwargs['extra_link_args'] = ['-s'] else: print('Unrecognized compiler: {0}'.format(compiler)) sys.exit(1) lz4version = Extension('lz4._version', lz4version_sources, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - libraries=libraries, - include_dirs=include_dirs, -) + **extension_kwargs) lz4block = Extension('lz4.block._block', lz4block_sources, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - libraries=libraries, - include_dirs=include_dirs, -) + **extension_kwargs) lz4frame = Extension('lz4.frame._frame', lz4frame_sources, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - libraries=libraries, - include_dirs=include_dirs, -) + **extension_kwargs) -install_requires=[] +lz4stream = Extension('lz4.stream._stream', + lz4stream_sources, + **extension_kwargs) -# On Python earlier than 3.0 the builtins package isn't included, but it is -# provided by the future package -if sys.version_info < (3, 0): - install_requires.append('future') +ext_modules = [lz4version, lz4block, lz4frame] + +if experimental is True: + ext_modules.append(lz4stream) + packages = find_packages() +else: + packages = find_packages(exclude=("lz4.stream",)) + +# Dependencies for testing. We define a list here, so that we can +# refer to it for the tests_require and the extras_require arguments +# to setup below. The latter enables us to use pip install .[tests] to +# install testing dependencies. +# Note: pytest 3.3.0 contains a bug with null bytes in parameter IDs: +# https://github.com/pytest-dev/pytest/issues/2957 +tests_require = [ + 'pytest!=3.3.0', + 'psutil', + 'pytest-cov', +], + +# Only require pytest-runner if actually running the tests +needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) +pytest_runner = ['pytest-runner'] if needs_pytest else [] # Finally call setup with the extension modules as defined above. setup( @@ -136,36 +173,41 @@ use_scm_version={ 'write_to': "lz4/version.py", }, - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + python_requires=">=3.9", setup_requires=[ 'setuptools_scm', 'pkgconfig', - 'pytest-runner', - ], - install_requires=install_requires, + ] + pytest_runner, description="LZ4 Bindings for Python", long_description=open('README.rst', 'r').read(), author='Jonathan Underwood', author_email='jonathan.underwood@gmail.com', + license='BSD-3-Clause', url='https://github.com/python-lz4/python-lz4', - packages=find_packages(), - ext_modules=[ - lz4version, - lz4block, - lz4frame - ], - tests_require=[ - 'pytest', - ], + packages=packages, + ext_modules=ext_modules, + tests_require=tests_require, + extras_require={ + 'tests': tests_require, + 'docs': [ + 'sphinx >= 1.6.0', + 'sphinx_bootstrap_theme', + ], + 'flake8': [ + 'flake8', + ] + }, classifiers=[ 'Development Status :: 5 - Production/Stable', - 'License :: OSI Approved :: BSD License', 'Intended Audience :: Developers', 'Programming Language :: C', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', + 'Programming Language :: Python :: Free Threading', ], ) diff --git a/tests/bench.py b/tests/bench.py deleted file mode 100644 index f99151ba..00000000 --- a/tests/bench.py +++ /dev/null @@ -1,24 +0,0 @@ -import uuid -import timeit -import lz4 -import os -from timeit import Timer -import sys -import blosc - -DATA = open(sys.argv[1], "rb").read() -LZ4_DATA = lz4.block.compress(DATA) -BLOSC_DATA = blosc.compress(DATA, cname='lz4', clevel=5, shuffle=True) -LOOPS = 100 - -print("Data Size:") -print(" Input: %d" % len(DATA)) -print(" LZ4: %d (%.2f)" % (len(LZ4_DATA), len(LZ4_DATA) / float(len(DATA)))) -print(" Blosc: %d (%.2f)" % (len(BLOSC_DATA), len(BLOSC_DATA) / float(len(DATA)))) -print(" LZ4 / Blosc: %f" % (float(len(LZ4_DATA)) / float(len(BLOSC_DATA)))) - -print("Benchmark: %d calls" % LOOPS) -print(" LZ4 Compression: %fs" % (Timer("lz4.block.compress(DATA)", "from __main__ import DATA; import lz4").timeit(number=LOOPS)/LOOPS)) -print(" Blosc Compression: %fs" % (Timer("blosc.compress(DATA, cname='lz4', clevel=5, shuffle=True)", "from __main__ import DATA; import blosc").timeit(number=LOOPS)/LOOPS)) -print(" LZ4 Decompression: %fs" % (Timer("lz4.block.decompress(LZ4_DATA)", "from __main__ import LZ4_DATA; import lz4").timeit(number=LOOPS)/LOOPS)) -print(" Blosc Decompression : %fs" % (Timer("blosc.decompress(BLOSC_DATA)", "from __main__ import BLOSC_DATA; import blosc").timeit(number=LOOPS)/LOOPS)) diff --git a/tests/block/conftest.py b/tests/block/conftest.py index d95a96e8..0b3578ea 100644 --- a/tests/block/conftest.py +++ b/tests/block/conftest.py @@ -2,19 +2,23 @@ import os import sys -test_data=[ + +test_data = [ (b''), (os.urandom(8 * 1024)), (b'0' * 8 * 1024), (bytearray(b'')), (bytearray(os.urandom(8 * 1024))), + (bytearray(open(os.path.join(os.path.dirname(__file__), 'numpy_byte_array.bin'), 'rb').read())) ] + if sys.version_info > (2, 7): test_data += [ (memoryview(b'')), (memoryview(os.urandom(8 * 1024))) ] + @pytest.fixture( params=test_data, ids=[ @@ -24,6 +28,7 @@ def data(request): return request.param + @pytest.fixture( params=[ ( @@ -41,6 +46,7 @@ def data(request): def store_size(request): return request.param + @pytest.fixture( params=[ ( @@ -58,14 +64,17 @@ def store_size(request): def return_bytearray(request): return request.param + @pytest.fixture def c_return_bytearray(return_bytearray): return return_bytearray + @pytest.fixture def d_return_bytearray(return_bytearray): return return_bytearray + @pytest.fixture( params=[ ('fast', None) @@ -81,3 +90,22 @@ def d_return_bytearray(return_bytearray): ) def mode(request): return request.param + + +dictionary = [ + None, + (0, 0), + (100, 200), + (0, 8 * 1024), + os.urandom(8 * 1024) +] + + +@pytest.fixture( + params=dictionary, + ids=[ + 'dictionary' + str(i) for i in range(len(dictionary)) + ] +) +def dictionary(request): + return request.param diff --git a/tests/block/numpy_byte_array.bin b/tests/block/numpy_byte_array.bin new file mode 100644 index 00000000..49537e2d Binary files /dev/null and b/tests/block/numpy_byte_array.bin differ diff --git a/tests/block/test_block.py b/tests/block/test_block.py deleted file mode 100644 index 658d94a3..00000000 --- a/tests/block/test_block.py +++ /dev/null @@ -1,159 +0,0 @@ -import lz4.block -import sys -from multiprocessing.pool import ThreadPool -import os -import pytest -if sys.version_info <= (3, 2): - import struct - - -def get_stored_size(buff): - if sys.version_info > (2, 7): - if isinstance(buff, memoryview): - b = buff.tobytes() - else: - b = bytes(buff) - else: - b = bytes(buff) - - if len(b) < 4: - return None - - if sys.version_info > (3, 2): - return int.from_bytes(b[:4], 'little') - else: - # This would not work on a memoryview object, hence buff.tobytes call - # above - return struct.unpack(' (2, 7): + if isinstance(buff, memoryview): + b = buff.tobytes() + else: + b = bytes(buff) + else: + b = bytes(buff) + + if len(b) < 4: + return None + + if sys.version_info > (3, 2): + return int.from_bytes(b[:4], 'little') + else: + # This would not work on a memoryview object, hence buff.tobytes call + # above + return struct.unpack('= 10800: p = [True, False] else: p = [False, ] + + @pytest.fixture( params=[ (pp) for pp in p - ] + ] ) def block_checksum(request): return request.param + compression_levels = [ - (lz4frame.COMPRESSIONLEVEL_MIN), - (lz4frame.COMPRESSIONLEVEL_MINHC), - (lz4frame.COMPRESSIONLEVEL_MAX), - ] + (lz4frame.COMPRESSIONLEVEL_MIN), + (lz4frame.COMPRESSIONLEVEL_MINHC), + (lz4frame.COMPRESSIONLEVEL_MAX), +] + + @pytest.fixture( params=compression_levels ) def compression_level(request): return request.param + @pytest.fixture( params=[ (True), @@ -64,6 +74,7 @@ def compression_level(request): def auto_flush(request): return request.param + @pytest.fixture( params=[ (True), @@ -73,6 +84,7 @@ def auto_flush(request): def store_size(request): return request.param + @pytest.fixture( params=[ (True), diff --git a/tests/frame/helpers.py b/tests/frame/helpers.py index 11cf622b..e6cb0c9e 100644 --- a/tests/frame/helpers.py +++ b/tests/frame/helpers.py @@ -1,5 +1,4 @@ import lz4.frame as lz4frame -import math def get_frame_info_check(compressed_data, @@ -15,7 +14,7 @@ def get_frame_info_check(compressed_data, assert frame_info["content_checksum"] == content_checksum assert frame_info["block_checksum"] == block_checksum - assert frame_info["skippable"] == False + assert frame_info["skippable"] is False if store_size is True: assert frame_info["content_size"] == source_size @@ -34,7 +33,8 @@ def get_frame_info_check(compressed_data, def get_chunked(data, nchunks): size = len(data) - stride = int(math.ceil(float(size)/nchunks)) # no // on py 2.6 + # stride = int(math.ceil(float(size)/nchunks)) # no // on py 2.6 + stride = size // nchunks start = 0 end = start + stride while end < size: diff --git a/tests/frame/test_frame_0.py b/tests/frame/test_frame_0.py index 7706a5c4..f03431d4 100644 --- a/tests/frame/test_frame_0.py +++ b/tests/frame/test_frame_0.py @@ -2,26 +2,31 @@ import lz4 import re + def test_library_version_number(): v = lz4.library_version_number() assert isinstance(v, int) assert v > 10000 + def test_library_version_string(): v = lz4.library_version_string() assert isinstance(v, str) assert v.count('.') == 2 - r=re.compile(r'^[0-9]*\.[0-9]*\.[0-9]*$') + r = re.compile(r'^[0-9]*\.[0-9]*\.[0-9]*$') assert r.match(v) is not None + def test_create_compression_context(): context = lz4frame.create_compression_context() assert context is not None + def test_create_decompression_context(): context = lz4frame.create_decompression_context() assert context is not None + def test_reset_decompression_context_1(): if lz4.library_version_number() >= 10800: context = lz4frame.create_decompression_context() @@ -30,14 +35,15 @@ def test_reset_decompression_context_1(): else: pass + def test_reset_decompression_context_2(): if lz4.library_version_number() >= 10800: c = lz4frame.compress(b'1234', return_bytearray=False) context = lz4frame.create_decompression_context() try: # Simulate an error by passing junk to decompress - d = lz4frame.decompress_chunk(context, c[1:3]) - except: + d = lz4frame.decompress_chunk(context, c[4:]) + except RuntimeError: pass r = lz4frame.reset_decompression_context(context) assert r is None @@ -45,18 +51,21 @@ def test_reset_decompression_context_2(): d, bytes_read, eof = lz4frame.decompress_chunk(context, c) assert d == b'1234' assert bytes_read == len(c) - assert eof == True + assert eof is True else: pass + def test_compress_return_type_1(): r = lz4frame.compress(b'', return_bytearray=False) assert isinstance(r, bytes) + def test_compress_return_type_2(): r = lz4frame.compress(b'', return_bytearray=True) assert isinstance(r, bytearray) + def test_decompress_return_type_1(): c = lz4frame.compress(b'', return_bytearray=False) r = lz4frame.decompress( @@ -66,6 +75,7 @@ def test_decompress_return_type_1(): ) assert isinstance(r, bytes) + def test_decompress_return_type_2(): c = lz4frame.compress(b'', return_bytearray=False) r = lz4frame.decompress( @@ -75,6 +85,7 @@ def test_decompress_return_type_2(): ) assert isinstance(r, bytearray) + def test_decompress_return_type_3(): c = lz4frame.compress(b'', return_bytearray=False) r = lz4frame.decompress( @@ -86,6 +97,7 @@ def test_decompress_return_type_3(): assert isinstance(r[0], bytes) assert isinstance(r[1], int) + def test_decompress_return_type_4(): c = lz4frame.compress(b'', return_bytearray=False) r = lz4frame.decompress( @@ -97,6 +109,7 @@ def test_decompress_return_type_4(): assert isinstance(r[0], bytearray) assert isinstance(r[1], int) + def test_decompress_chunk_return_type_1(): c = lz4frame.compress(b'', return_bytearray=False) d = lz4frame.create_decompression_context() @@ -109,6 +122,7 @@ def test_decompress_chunk_return_type_1(): assert isinstance(b, int) assert isinstance(e, bool) + def test_decompress_chunk_return_type_2(): c = lz4frame.compress(b'', return_bytearray=False) d = lz4frame.create_decompression_context() @@ -121,6 +135,7 @@ def test_decompress_chunk_return_type_2(): assert isinstance(b, int) assert isinstance(e, bool) + def test_decompress_chunk_return_type_3(): c = lz4frame.compress(b'', return_bytearray=False) d = lz4frame.create_decompression_context() @@ -134,6 +149,7 @@ def test_decompress_chunk_return_type_3(): assert isinstance(r[1], int) assert isinstance(r[2], bool) + def test_decompress_chunk_return_type_4(): c = lz4frame.compress(b'', return_bytearray=False) d = lz4frame.create_decompression_context() @@ -147,6 +163,7 @@ def test_decompress_chunk_return_type_4(): assert isinstance(r[1], int) assert isinstance(r[2], bool) + def test_block_size_constants(): assert lz4frame.BLOCKSIZE_DEFAULT == 0 assert lz4frame.BLOCKSIZE_MAX64KB == 4 diff --git a/tests/frame/test_frame_1.py b/tests/frame/test_frame_1.py index 444b6e8c..35110c44 100644 --- a/tests/frame/test_frame_1.py +++ b/tests/frame/test_frame_1.py @@ -5,7 +5,7 @@ from .helpers import get_frame_info_check -test_data=[ +test_data = [ (b''), (os.urandom(8 * 1024)), (b'0' * 8 * 1024), @@ -21,6 +21,7 @@ (memoryview(os.urandom(8 * 1024))) ] + @pytest.fixture( params=test_data, ids=[ @@ -59,7 +60,8 @@ def test_roundtrip_1( content_checksum, block_checksum, ) - decompressed, bytes_read = lz4frame.decompress(compressed, return_bytes_read=True) + decompressed, bytes_read = lz4frame.decompress( + compressed, return_bytes_read=True) assert bytes_read == len(compressed) assert decompressed == data @@ -103,6 +105,7 @@ def test_roundtrip_2(data, content_checksum, block_checksum, ) - decompressed, bytes_read = lz4frame.decompress(compressed, return_bytes_read=True) + decompressed, bytes_read = lz4frame.decompress( + compressed, return_bytes_read=True) assert bytes_read == len(compressed) assert decompressed == data diff --git a/tests/frame/test_frame_2.py b/tests/frame/test_frame_2.py index 3a0e6d6c..230867e6 100644 --- a/tests/frame/test_frame_2.py +++ b/tests/frame/test_frame_2.py @@ -1,6 +1,7 @@ import lz4.frame as lz4frame import pytest import os +import copy import sys from . helpers import ( get_chunked, @@ -8,7 +9,7 @@ ) -test_data=[ +test_data = [ (b'', 1, 1), (os.urandom(8 * 1024), 8, 1), (os.urandom(8 * 1024), 1, 8), @@ -41,6 +42,13 @@ def test_roundtrip_chunked(data, block_size, block_linked, data, c_chunks, d_chunks = data + if isinstance(data, memoryview): + data = memoryview(copy.deepcopy(data.obj)) + elif isinstance(data, bytearray): + data_2 = bytearray() + data_2[:] = data + data = data_2 + c_context = lz4frame.create_compression_context() kwargs = {} @@ -103,5 +111,5 @@ def test_roundtrip_chunked(data, block_size, block_linked, assert bytes_read == len(compressed) assert decompressed == data - assert eofs[-1] == True - assert (True in eofs[:-2]) == False + assert eofs[-1] is True + assert (True in eofs[:-2]) is False diff --git a/tests/frame/test_frame_3.py b/tests/frame/test_frame_3.py index ee1a554a..c0b0028e 100644 --- a/tests/frame/test_frame_3.py +++ b/tests/frame/test_frame_3.py @@ -3,10 +3,11 @@ import os import struct -test_data=[ +test_data = [ (os.urandom(256 * 1024)), ] + @pytest.fixture( params=test_data, ids=[ @@ -21,23 +22,23 @@ def test_decompress_truncated(data): compressed = lz4frame.compress(data) message = r'^LZ4F_getFrameInfo failed with code: ERROR_frameHeader_incomplete' - with pytest.raises(RuntimeError, message=message): + with pytest.raises(RuntimeError, match=message): lz4frame.decompress(compressed[:6]) - for i in range(16, len(compressed) - 1, 5): # 15 is the max size of the header - message = r'^Frame incomplete. LZ4F_decompress returned: {0}'.format(len(compressed) - i) + for i in range(16, len(compressed) - 1, 5): # 15 is the max size of the header + message = r'^Frame incomplete. LZ4F_decompress returned:' try: lz4frame.decompress(compressed[:i]) except RuntimeError as r: print(r) - with pytest.raises(RuntimeError, message=message): + with pytest.raises(RuntimeError, match=message): lz4frame.decompress(compressed[:i]) def test_content_checksum_failure(data): compressed = lz4frame.compress(data, content_checksum=True) message = r'^LZ4F_decompress failed with code: ERROR_contentChecksum_invalid$' - with pytest.raises(RuntimeError, message=message): + with pytest.raises(RuntimeError, match=message): last = struct.unpack('B', compressed[-1:])[0] lz4frame.decompress(compressed[:-1] + struct.pack('B', last ^ 0x42)) @@ -51,6 +52,6 @@ def test_block_checksum_failure(data): ) message = r'^LZ4F_decompress failed with code: ERROR_blockChecksum_invalid$' if len(compressed) > 32: - with pytest.raises(RuntimeError, message=message): - compressed[18] = compressed[18] ^ 0x42 + with pytest.raises(RuntimeError, match=message): + compressed[22] = compressed[22] ^ 0x42 lz4frame.decompress(compressed) diff --git a/tests/frame/test_frame_4.py b/tests/frame/test_frame_4.py index 490f6376..7fa16547 100644 --- a/tests/frame/test_frame_4.py +++ b/tests/frame/test_frame_4.py @@ -6,7 +6,7 @@ get_chunked, ) -test_data=[ +test_data = [ b'', (128 * (32 * os.urandom(32))), (256 * (32 * os.urandom(32))), @@ -14,6 +14,7 @@ (1024 * (32 * os.urandom(32))), ] + @pytest.fixture( params=test_data, ids=[ @@ -23,6 +24,7 @@ def data(request): return request.param + @pytest.fixture( params=[ (True), @@ -32,6 +34,7 @@ def data(request): def reset(request): return request.param + @pytest.fixture( params=[ (1), @@ -86,7 +89,8 @@ def do_compress(): block_checksum, ) - decompressed, bytes_read = lz4frame.decompress(compressed, return_bytes_read=True) + decompressed, bytes_read = lz4frame.decompress( + compressed, return_bytes_read=True) assert data == decompressed assert bytes_read == len(compressed) diff --git a/tests/frame/test_frame_5.py b/tests/frame/test_frame_5.py index 9d5c72dc..29e7765a 100644 --- a/tests/frame/test_frame_5.py +++ b/tests/frame/test_frame_5.py @@ -1,11 +1,14 @@ import lz4.frame -import time import pytest +import gc -test_data=[ +MEM_INCREASE_LIMIT = (1024 * 25) + +test_data = [ (b'a' * 1024 * 1024), ] + @pytest.fixture( params=test_data, ids=[ @@ -16,6 +19,11 @@ def data(request): return request.param +# coverage might allocate and cause this test to fail +@pytest.mark.no_cover +@pytest.mark.thread_unsafe( + reason="tracemalloc captures global snapshots" +) def test_frame_decompress_mem_usage(data): tracemalloc = pytest.importorskip('tracemalloc') @@ -25,18 +33,24 @@ def test_frame_decompress_mem_usage(data): prev_snapshot = None for i in range(1000): - decompressed = lz4.frame.decompress(compressed) + decompressed = lz4.frame.decompress(compressed) # noqa: F841 if i % 100 == 0: + gc.collect() snapshot = tracemalloc.take_snapshot() if prev_snapshot: stats = snapshot.compare_to(prev_snapshot, 'lineno') - assert stats[0].size_diff < (1024 * 4) + assert stats[0].size_diff < MEM_INCREASE_LIMIT prev_snapshot = snapshot +# coverage might allocate and cause this test to fail +@pytest.mark.no_cover +@pytest.mark.thread_unsafe( + reason="tracemalloc captures global snapshots" +) def test_frame_decompress_chunk_mem_usage(data): tracemalloc = pytest.importorskip('tracemalloc') tracemalloc.start() @@ -47,18 +61,26 @@ def test_frame_decompress_chunk_mem_usage(data): for i in range(1000): context = lz4.frame.create_decompression_context() - decompressed = lz4.frame.decompress_chunk(context, compressed) + decompressed = lz4.frame.decompress_chunk( # noqa: F841 + context, compressed + ) if i % 100 == 0: + gc.collect() snapshot = tracemalloc.take_snapshot() if prev_snapshot: stats = snapshot.compare_to(prev_snapshot, 'lineno') - assert stats[0].size_diff < (1024 * 10) + assert stats[0].size_diff < MEM_INCREASE_LIMIT prev_snapshot = snapshot +# coverage might allocate and cause this test to fail +@pytest.mark.no_cover +@pytest.mark.thread_unsafe( + reason="tracemalloc captures global snapshots" +) def test_frame_open_decompress_mem_usage(data): tracemalloc = pytest.importorskip('tracemalloc') tracemalloc.start() @@ -70,23 +92,23 @@ def test_frame_open_decompress_mem_usage(data): for i in range(1000): with lz4.frame.open('test.lz4', 'r') as f: - decompressed = f.read() + decompressed = f.read() # noqa: F841 if i % 100 == 0: + gc.collect() snapshot = tracemalloc.take_snapshot() if prev_snapshot: stats = snapshot.compare_to(prev_snapshot, 'lineno') - assert stats[0].size_diff < (1024 * 10) + assert stats[0].size_diff < MEM_INCREASE_LIMIT prev_snapshot = snapshot -# TODO: add many more memory usage tests along the lines of this one for other funcs +# TODO: add many more memory usage tests along the lines of this one +# for other funcs def test_dummy_always_pass(): # If pytest finds all tests are skipped, then it exits with code 5 rather # than 0, which tox sees as an error. Here we add a dummy test that always passes. assert True - - diff --git a/tests/frame/test_frame_6.py b/tests/frame/test_frame_6.py index 8608f675..4f4185ee 100644 --- a/tests/frame/test_frame_6.py +++ b/tests/frame/test_frame_6.py @@ -1,13 +1,15 @@ import os import pytest +import threading import lz4.frame as lz4frame -test_data=[ +test_data = [ b'', (128 * (32 * os.urandom(32))), (5 * 128 * os.urandom(1024)), ] + @pytest.fixture( params=test_data, ids=[ @@ -17,11 +19,14 @@ def data(request): return request.param + compression_levels = [ - (lz4frame.COMPRESSIONLEVEL_MIN), - # (lz4frame.COMPRESSIONLEVEL_MINHC), - # (lz4frame.COMPRESSIONLEVEL_MAX), - ] + (lz4frame.COMPRESSIONLEVEL_MIN), + # (lz4frame.COMPRESSIONLEVEL_MINHC), + # (lz4frame.COMPRESSIONLEVEL_MAX), +] + + @pytest.fixture( params=compression_levels ) @@ -29,26 +34,45 @@ def compression_level(request): return request.param -def test_lz4frame_open_write(data): - with lz4frame.open('testfile', mode='wb') as fp: +def test_lz4frame_open_write(tmp_path, data): + thread_id = threading.get_native_id() + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='wb') as fp: fp.write(data) -def test_lz4frame_open_write_read_defaults(data): - with lz4frame.open('testfile', mode='wb') as fp: + +def test_lz4frame_open_write_read_defaults(tmp_path, data): + thread_id = threading.get_native_id() + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='wb') as fp: fp.write(data) - with lz4frame.open('testfile', mode='r') as fp: + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='r') as fp: data_out = fp.read() assert data_out == data -def test_lz4frame_open_write_read_text(): + +def test_lz4frame_open_write_read_text(tmp_path): data = u'This is a test string' - with lz4frame.open('testfile', mode='wt') as fp: + thread_id = threading.get_native_id() + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='wt') as fp: fp.write(data) - with lz4frame.open('testfile', mode='rt') as fp: + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='rt') as fp: data_out = fp.read() assert data_out == data + +def test_lz4frame_open_write_read_text_iter(tmp_path): + data = u'This is a test string' + thread_id = threading.get_native_id() + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='wt') as fp: + fp.write(data) + data_out = '' + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='rt') as fp: + for line in fp: + data_out += line + assert data_out == data + + def test_lz4frame_open_write_read( + tmp_path, data, compression_level, block_linked, @@ -73,10 +97,31 @@ def test_lz4frame_open_write_read( kwargs['return_bytearray'] = return_bytearray kwargs['mode'] = 'wb' - with lz4frame.open('testfile', **kwargs) as fp: + thread_id = threading.get_native_id() + with lz4frame.open(tmp_path / f'testfile_{thread_id}', **kwargs) as fp: fp.write(data) - with lz4frame.open('testfile', mode='r') as fp: + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='r') as fp: data_out = fp.read() assert data_out == data + + +def test_lz4frame_flush(tmp_path): + data_1 = b"This is a..." + data_2 = b" test string!" + thread_id = threading.get_native_id() + + with lz4frame.open(tmp_path / f"testfile_{thread_id}", mode="w") as fp_write: + fp_write.write(data_1) + fp_write.flush() + + fp_write.write(data_2) + + with lz4frame.open(tmp_path / f"testfile_{thread_id}", mode="r") as fp_read: + assert fp_read.read() == data_1 + + fp_write.flush() + + with lz4frame.open(tmp_path / f"testfile_{thread_id}", mode="r") as fp_read: + assert fp_read.read() == data_1 + data_2 diff --git a/tests/frame/test_frame_7.py b/tests/frame/test_frame_7.py index 1ec48942..583f3fbb 100644 --- a/tests/frame/test_frame_7.py +++ b/tests/frame/test_frame_7.py @@ -2,10 +2,11 @@ import pytest import os -test_data=[ +test_data = [ (os.urandom(32) * 256), ] + @pytest.fixture( params=test_data, ids=[ @@ -15,6 +16,7 @@ def data(request): return request.param + def test_roundtrip_multiframe_1(data): nframes = 4 @@ -29,6 +31,7 @@ def test_roundtrip_multiframe_1(data): assert len(decompressed) == nframes * len(data) assert data * nframes == decompressed + def test_roundtrip_multiframe_2(data): nframes = 4 @@ -46,6 +49,7 @@ def test_roundtrip_multiframe_2(data): assert len(decompressed) == nframes * len(data) assert data * nframes == decompressed + def test_roundtrip_multiframe_3(data): nframes = 4 @@ -61,12 +65,13 @@ def test_roundtrip_multiframe_3(data): for _ in range(nframes): d, bytes_read, eof = lz4frame.decompress_chunk(ctx, compressed) decompressed += d - assert eof == True + assert eof is True assert bytes_read == len(compressed) // nframes assert len(decompressed) == nframes * len(data) assert data * nframes == decompressed + def test_roundtrip_multiframe_4(data): nframes = 4 @@ -85,12 +90,13 @@ def test_roundtrip_multiframe_4(data): else: d = decompressor.unused_data decompressed += decompressor.decompress(d) - assert decompressor.eof == True - assert decompressor.needs_input == True + assert decompressor.eof is True + assert decompressor.needs_input is True if i == nframes - 1: - assert decompressor.unused_data == None + assert decompressor.unused_data is None else: - assert len(decompressor.unused_data) == len(compressed) * (nframes - i - 1) / nframes + assert len(decompressor.unused_data) == len( + compressed) * (nframes - i - 1) / nframes assert len(decompressed) == nframes * len(data) assert data * nframes == decompressed diff --git a/tests/frame/test_frame_8.py b/tests/frame/test_frame_8.py new file mode 100644 index 00000000..cfaeaace --- /dev/null +++ b/tests/frame/test_frame_8.py @@ -0,0 +1,14 @@ +import threading +import lz4.frame as lz4frame + + +def test_lz4frame_open_write_read_text_iter(tmp_path): + data = u'This is a test string' + thread_id = threading.get_native_id() + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='wt') as fp: + fp.write(data) + data_out = '' + with lz4frame.open(tmp_path / f'testfile_{thread_id}', mode='rt') as fp: + for line in fp: + data_out += line + assert data_out == data diff --git a/tests/frame/test_frame_9.py b/tests/frame/test_frame_9.py new file mode 100644 index 00000000..6f7fc0db --- /dev/null +++ b/tests/frame/test_frame_9.py @@ -0,0 +1,76 @@ +import array +import os +import io +import pickle +import sys +import threading +import lz4.frame +import pytest + + +def test_issue_172_1(tmp_path): + """Test reproducer for issue 172 + + Issue 172 is a reported failure occurring on Windows 10 only. This bug was + due to incorrect handling of Py_ssize_t types when doing comparisons and + using them as a size when allocating memory. + + """ + input_data = 8 * os.urandom(1024) + thread_id = threading.get_native_id() + + with lz4.frame.open(tmp_path / f'testfile_small_{thread_id}', 'wb') as fp: + bytes_written = fp.write(input_data) # noqa: F841 + + with lz4.frame.open(tmp_path / f'testfile_small_{thread_id}', 'rb') as fp: + data = fp.read(10) + assert len(data) == 10 + + +def test_issue_172_2(tmp_path): + input_data = 9 * os.urandom(1024) + thread_id = threading.get_native_id() + with lz4.frame.open(tmp_path / f'testfile_small_{thread_id}', 'w') as fp: + bytes_written = fp.write(input_data) # noqa: F841 + + with lz4.frame.open(tmp_path / f'testfile_small_{thread_id}', 'r') as fp: + data = fp.read(10) + assert len(data) == 10 + + +def test_issue_172_3(tmp_path): + input_data = 9 * os.urandom(1024) + thread_id = threading.get_native_id() + with lz4.frame.open(tmp_path / f'testfile_small_{thread_id}', 'wb') as fp: + bytes_written = fp.write(input_data) # noqa: F841 + + with lz4.frame.open(tmp_path / f'testfile_small_{thread_id}', 'rb') as fp: + data = fp.read(10) + assert len(data) == 10 + + with lz4.frame.open(tmp_path / f'testfile_small_{thread_id}', 'rb') as fp: + data = fp.read(16 * 1024 - 1) + assert len(data) == 9 * 1024 + assert data == input_data + + +def test_issue_227_1(): + q = array.array('Q', [1, 2, 3, 4, 5]) + LENGTH = len(q) * q.itemsize + + with lz4.frame.open(io.BytesIO(), 'w') as f: + assert f.write(q) == LENGTH + assert f.tell() == LENGTH + + +@pytest.mark.skipif( + sys.version_info < (3, 8), + reason="PickleBuffer only available in Python 3.8 or greater" +) +def test_issue_227_2(): + q = array.array('Q', [1, 2, 3, 4, 5]) + + c = lz4.frame.compress(q) + d = lz4.frame.LZ4FrameDecompressor().decompress(pickle.PickleBuffer(c)) + + assert memoryview(q).tobytes() == d diff --git a/tests/stream/conftest.py b/tests/stream/conftest.py new file mode 100644 index 00000000..ea23e998 --- /dev/null +++ b/tests/stream/conftest.py @@ -0,0 +1,155 @@ +import pytest +import os +import sys + +test_data = [ + (b''), + (os.urandom(8 * 1024)), + # (b'0' * 8 * 1024), + # (bytearray(b'')), + # (bytearray(os.urandom(8 * 1024))), + (bytearray(open(os.path.join(os.path.dirname(__file__), 'numpy_byte_array.bin'), 'rb').read())) +] + +if sys.version_info > (2, 7): + test_data += [ + (memoryview(b'')), + (memoryview(os.urandom(8 * 1024))) + ] + + +@pytest.fixture( + params=test_data, + ids=[ + 'data' + str(i) for i in range(len(test_data)) + ] +) +def data(request): + return request.param + + +@pytest.fixture( + params=[ + ("double_buffer"), + # ("ring_buffer"), # not implemented + ] +) +def strategy(request): + return request.param + + +test_buffer_size = sorted( + [1, + # 4, + # 8, + # 64, + # 256, + 941, + # 1 * 1024, + # 4 * 1024, + # 8 * 1024, + # 16 * 1024, + # 32 * 1024, + 64 * 1024, + # 128 * 1024 + ] +) + + +@pytest.fixture( + params=test_buffer_size, + ids=[ + 'buffer_size' + str(i) for i in range(len(test_buffer_size)) + ] +) +def buffer_size(request): + return request.param + + +@pytest.fixture( + params=[ + ( + { + 'store_comp_size': 1 + } + ), + ( + { + 'store_comp_size': 2 + } + ), + # ( + # { + # 'store_comp_size': 4 + # } + # ), + ] +) +def store_comp_size(request): + return request.param + + +@pytest.fixture( + params=[ + ( + { + 'return_bytearray': True + } + ), + ( + { + 'return_bytearray': False + } + ), + ] +) +def return_bytearray(request): + return request.param + + +@pytest.fixture +def c_return_bytearray(return_bytearray): + return return_bytearray + + +@pytest.fixture +def d_return_bytearray(return_bytearray): + return return_bytearray + + +@pytest.fixture( + params=[ + ('default', None) + ] + [ + ('fast', None) + ] + [ + ('fast', {'acceleration': 2 * s}) for s in range(5) + ] + [ + ('high_compression', None) + ] + [ + ('high_compression', {'compression_level': 2 * s}) for s in range(9) + ] + [ + (None, None) + ] +) +def mode(request): + return request.param + + +dictionary = [ + None, + (0, 0), + (100, 200), + (0, 8 * 1024), + os.urandom(8 * 1024) +] + + +@pytest.fixture( + params=dictionary, + ids=[ + 'dictionary' + str(i) for i in range(len(dictionary)) + ] +) +def dictionary(request): + return request.param diff --git a/tests/stream/numpy_byte_array.bin b/tests/stream/numpy_byte_array.bin new file mode 100644 index 00000000..49537e2d Binary files /dev/null and b/tests/stream/numpy_byte_array.bin differ diff --git a/tests/stream/test_stream_0.py b/tests/stream/test_stream_0.py new file mode 100644 index 00000000..c7db76ee --- /dev/null +++ b/tests/stream/test_stream_0.py @@ -0,0 +1,120 @@ +import lz4.stream +import sys +import pytest +if sys.version_info <= (3, 2): + import struct + + +def get_stored_size(buff, block_length_size): + if sys.version_info > (2, 7): + if isinstance(buff, memoryview): + b = buff.tobytes() + else: + b = bytes(buff) + else: + b = bytes(buff) + + if len(b) < block_length_size: + return None + + if sys.version_info > (3, 2): + return int.from_bytes(b[:block_length_size], 'little') + else: + # This would not work on a memoryview object, hence buff.tobytes call + # above + fmt = {1: 'B', 2: 'H', 4: 'I', } + return struct.unpack('<' + fmt[block_length_size], b[:block_length_size])[0] + + +def roundtrip(x, c_kwargs, d_kwargs, dictionary): + if dictionary: + if isinstance(dictionary, tuple): + dict_ = x[dictionary[0]:dictionary[1]] + else: + dict_ = dictionary + c_kwargs['dictionary'] = dict_ + d_kwargs['dictionary'] = dict_ + + c = bytes() + with lz4.stream.LZ4StreamCompressor(**c_kwargs) as proc: + for start in range(0, len(x), c_kwargs['buffer_size']): + chunk = x[start:start + c_kwargs['buffer_size']] + assert len(chunk) <= c_kwargs['buffer_size'] + block = proc.compress(chunk) + if c_kwargs.get('return_bytearray'): + assert isinstance(block, bytearray) + if start == 0: + c = block + else: + c += block + assert get_stored_size(block, c_kwargs['store_comp_size']) == \ + (len(block) - c_kwargs['store_comp_size']) + + d = bytes() + with lz4.stream.LZ4StreamDecompressor(**d_kwargs) as proc: + start = 0 + while start < len(c): + block = proc.get_block(c[start:]) + chunk = proc.decompress(block) + if d_kwargs.get('return_bytearray'): + assert isinstance(chunk, bytearray) + if start == 0: + d = chunk + else: + d += chunk + start += d_kwargs['store_comp_size'] + len(block) + + return d + + +def setup_kwargs(strategy, mode, buffer_size, store_comp_size, + c_return_bytearray=None, d_return_bytearray=None): + c_kwargs = {} + + if mode[0] is not None: + c_kwargs['mode'] = mode[0] + if mode[1] is not None: + c_kwargs.update(mode[1]) + + c_kwargs['strategy'] = strategy + c_kwargs['buffer_size'] = buffer_size + c_kwargs.update(store_comp_size) + + if c_return_bytearray: + c_kwargs.update(c_return_bytearray) + + d_kwargs = {} + + if d_return_bytearray: + d_kwargs.update(d_return_bytearray) + + d_kwargs['strategy'] = strategy + d_kwargs['buffer_size'] = buffer_size + d_kwargs.update(store_comp_size) + + return (c_kwargs, d_kwargs) + + +# Test single threaded usage with all valid variations of input +@pytest.mark.thread_unsafe( + reason=("test modifies fixtures that would be shared, see " + "https://github.com/Quansight-Labs/pytest-run-parallel/issues/14") +) +def test_1(data, strategy, mode, buffer_size, store_comp_size, + c_return_bytearray, d_return_bytearray, dictionary): + if buffer_size >= (1 << (8 * store_comp_size['store_comp_size'])): + pytest.skip("Invalid case: buffer_size too large for the block length area") + + (c_kwargs, d_kwargs) = setup_kwargs( + strategy, mode, buffer_size, store_comp_size, c_return_bytearray, d_return_bytearray) + + d = roundtrip(data, c_kwargs, d_kwargs, dictionary) + + assert d == data + + +# Test multi threaded: +# Not relevant in the lz4.stream case (the process is highly sequential, +# and reuse/share the same context from one input chunk to the next one). +def test_2(data, strategy, mode, buffer_size, store_comp_size, dictionary): # noqa + pass diff --git a/tests/stream/test_stream_1.py b/tests/stream/test_stream_1.py new file mode 100644 index 00000000..481de8aa --- /dev/null +++ b/tests/stream/test_stream_1.py @@ -0,0 +1,555 @@ +import lz4.stream +import pytest +import sys +import os + + +if sys.version_info < (3, ): + from struct import pack, unpack + + def _get_format(length, byteorder, signed): + _order = {'l': '<', 'b': '>'} + _fmt = {1: 'b', 2: 'h', 4: 'i', 8: 'q'} + _sign = {True: lambda x: x.lower(), False: lambda x: x.upper()} + return _sign[signed](_order[byteorder[0].lower()] + _fmt[length]) + + def int_to_bytes(value, length=4, byteorder='little', signed=False): + return bytearray(pack(_get_format(length, byteorder, signed), value)) + + def int_from_bytes(bytes, byteorder='little', signed=False): + return unpack(_get_format(len(bytes), byteorder, signed), bytes)[0] + +else: + def int_to_bytes(value, length=4, byteorder='little', signed=False): + return value.to_bytes(length, byteorder, signed=signed) + + def int_from_bytes(bytes, byteorder='little', signed=False): + return int.from_bytes(bytes, byteorder, signed=signed) + + +# This test requires allocating a big lump of memory. In order to +# avoid a massive memory allocation during byte compilation, we have +# to declare a variable for the size of the buffer we're going to +# create outside the scope of the function below. See: +# https://bugs.python.org/issue21074 +_4GB = 0x100000000 # 4GB + + +def compress(x, c_kwargs, return_block_offset=False, check_block_type=False): + o = [0, ] + if c_kwargs.get('return_bytearray', False): + c = bytearray() + else: + c = bytes() + with lz4.stream.LZ4StreamCompressor(**c_kwargs) as proc: + for start in range(0, len(x), c_kwargs['buffer_size']): + chunk = x[start:start + c_kwargs['buffer_size']] + block = proc.compress(chunk) + c += block + if return_block_offset: + o.append(len(c)) + if check_block_type: + assert isinstance(block, c.__class__) + if return_block_offset: + return c, o + else: + return c + + +def decompress(x, d_kwargs, check_chunk_type=False): + if d_kwargs.get('return_bytearray', False): + d = bytearray() + else: + d = bytes() + with lz4.stream.LZ4StreamDecompressor(**d_kwargs) as proc: + start = 0 + while start < len(x): + block = proc.get_block(x[start:]) + chunk = proc.decompress(block) + d += chunk + start += d_kwargs['store_comp_size'] + len(block) + if check_chunk_type: + assert isinstance(chunk, d.__class__) + return d + + +def test_invalid_config_c_1(): + c_kwargs = {} + c_kwargs['strategy'] = "ring_buffer" + c_kwargs['buffer_size'] = 1024 + + with pytest.raises(NotImplementedError): + lz4.stream.LZ4StreamCompressor(**c_kwargs) + + +def test_invalid_config_d_1(): + d_kwargs = {} + d_kwargs['strategy'] = "ring_buffer" + d_kwargs['buffer_size'] = 1024 + + with pytest.raises(NotImplementedError): + lz4.stream.LZ4StreamDecompressor(**d_kwargs) + + +def test_invalid_config_c_2(): + c_kwargs = {} + c_kwargs['strategy'] = "foo" + c_kwargs['buffer_size'] = 1024 + + with pytest.raises(ValueError): + lz4.stream.LZ4StreamCompressor(**c_kwargs) + + +def test_invalid_config_d_2(): + d_kwargs = {} + d_kwargs['strategy'] = "foo" + d_kwargs['buffer_size'] = 1024 + + with pytest.raises(ValueError): + lz4.stream.LZ4StreamDecompressor(**d_kwargs) + + +def test_invalid_config_c_3(store_comp_size): + c_kwargs = {} + c_kwargs['strategy'] = "double_buffer" + c_kwargs['buffer_size'] = 1024 + c_kwargs['store_comp_size'] = store_comp_size['store_comp_size'] + 5 + + with pytest.raises(ValueError): + lz4.stream.LZ4StreamCompressor(**c_kwargs) + + +def test_invalid_config_d_3(store_comp_size): + d_kwargs = {} + d_kwargs['strategy'] = "double_buffer" + d_kwargs['buffer_size'] = 1024 + d_kwargs['store_comp_size'] = store_comp_size['store_comp_size'] + 5 + + with pytest.raises(ValueError): + lz4.stream.LZ4StreamDecompressor(**d_kwargs) + + +def test_invalid_config_c_4(store_comp_size): + c_kwargs = {} + c_kwargs['strategy'] = "double_buffer" + c_kwargs['buffer_size'] = 1 << (8 * store_comp_size['store_comp_size']) + c_kwargs.update(store_comp_size) + + if store_comp_size['store_comp_size'] >= 4: + # No need for skipping this test case, since arguments check is + # expecting to raise an error. + + # Make sure the page size is larger than what the input bound will be, + # but still fit in 4 bytes + c_kwargs['buffer_size'] -= 1 + + if c_kwargs['buffer_size'] > lz4.stream.LZ4_MAX_INPUT_SIZE: + message = r"^Invalid buffer_size argument: \d+. Cannot define output buffer size. Must be lesser or equal to 2113929216$" # noqa + err_class = ValueError + else: + message = r"^Inconsistent buffer_size/store_comp_size values. Maximal compressed length \(\d+\) cannot fit in a \d+ byte-long integer$" # noqa + err_class = lz4.stream.LZ4StreamError + + with pytest.raises(err_class, match=message): + lz4.stream.LZ4StreamCompressor(**c_kwargs) + + +def test_invalid_config_d_4(store_comp_size): + d_kwargs = {} + d_kwargs['strategy'] = "double_buffer" + d_kwargs['buffer_size'] = 1 << (8 * store_comp_size['store_comp_size']) + d_kwargs.update(store_comp_size) + + if store_comp_size['store_comp_size'] >= 4: + + if sys.maxsize < 0xffffffff: + pytest.skip('Py_ssize_t too small for this test') + + # Make sure the page size is larger than what the input bound will be, + # but still fit in 4 bytes + d_kwargs['buffer_size'] -= 1 + + # No failure expected during instantiation/initialization + lz4.stream.LZ4StreamDecompressor(**d_kwargs) + + +def test_invalid_config_c_5(): + c_kwargs = {} + c_kwargs['strategy'] = "double_buffer" + c_kwargs['buffer_size'] = lz4.stream.LZ4_MAX_INPUT_SIZE + + if sys.maxsize < 0xffffffff: + pytest.skip('Py_ssize_t too small for this test') + + # No failure expected + lz4.stream.LZ4StreamCompressor(**c_kwargs) + + c_kwargs['buffer_size'] = lz4.stream.LZ4_MAX_INPUT_SIZE + 1 + with pytest.raises(ValueError): + lz4.stream.LZ4StreamCompressor(**c_kwargs) + + # Make sure the page size is larger than what the input bound will be, + # but still fit in 4 bytes + c_kwargs['buffer_size'] = _4GB - 1 # 4GB - 1 (to fit in 4 bytes) + with pytest.raises(ValueError): + lz4.stream.LZ4StreamCompressor(**c_kwargs) + + +def test_invalid_config_d_5(): + d_kwargs = {} + d_kwargs['strategy'] = "double_buffer" + + # No failure expected during instantiation/initialization + d_kwargs['buffer_size'] = lz4.stream.LZ4_MAX_INPUT_SIZE + + if sys.maxsize < 0xffffffff: + pytest.skip('Py_ssize_t too small for this test') + + lz4.stream.LZ4StreamDecompressor(**d_kwargs) + + # No failure expected during instantiation/initialization + d_kwargs['buffer_size'] = lz4.stream.LZ4_MAX_INPUT_SIZE + 1 + + if sys.maxsize < 0xffffffff: + pytest.skip('Py_ssize_t too small for this test') + + lz4.stream.LZ4StreamDecompressor(**d_kwargs) + + # No failure expected during instantiation/initialization + d_kwargs['buffer_size'] = _4GB - 1 # 4GB - 1 (to fit in 4 bytes) + + if sys.maxsize < 0xffffffff: + pytest.skip('Py_ssize_t too small for this test') + + lz4.stream.LZ4StreamDecompressor(**d_kwargs) + + +def test_decompress_corrupted_input_1(): + c_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + data = compress(b'A' * 512, c_kwargs) + decompress(data, d_kwargs) + + message = r"^Requested input size \(\d+\) larger than source size \(\d+\)$" + + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(data[4:], d_kwargs) + + +def test_decompress_corrupted_input_2(): + c_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + data = compress(b'A' * 512, c_kwargs) + decompress(data, d_kwargs) + + message = r"^Decompression failed. error: \d+$" + + # Block size corruption in the first block + + # Block size longer than actual: + data = int_to_bytes(int_from_bytes(data[:4], 'little') + 1, 4, 'little') + data[4:] + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(data, d_kwargs) + + # Block size shorter than actual: + data = int_to_bytes(int_from_bytes(data[:4], 'little') - 2, 4, 'little') + data[4:] + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(data, d_kwargs) + + +def test_decompress_corrupted_input_3(): + c_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + data = compress(b'A' * 512, c_kwargs) + decompress(data, d_kwargs) + + message = r"^Decompression failed. error: \d+$" + + # Block size corruption in a block in the middle of the stream + offset = 4 + int_from_bytes(data[:4], 'little') + + # Block size longer than actual: + block_len = int_from_bytes(data[offset:offset + 4], 'little') + 1 + data = data[:offset] + int_to_bytes(block_len, 4, 'little') + data[offset + 4:] + + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(data, d_kwargs) + + # Block size shorter than actual: + block_len = int_from_bytes(data[offset:offset + 4], 'little') - 2 + data = data[:offset] + int_to_bytes(block_len, 4, 'little') + data[offset + 4:] + + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(data, d_kwargs) + + +def test_decompress_corrupted_input_4(): + c_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + data = compress(b'A' * 256, c_kwargs) + decompress(data, d_kwargs) + + # Block size corruption in the last block of the stream + offset = 4 + int_from_bytes(data[:4], 'little') + + # Block size longer than actual: + block_len = int_from_bytes(data[offset:offset + 4], 'little') + 1 + data = data[:offset] + int_to_bytes(block_len, 4, 'little') + data[offset + 4:] + + message = r"^Requested input size \(\d+\) larger than source size \(\d+\)$" + + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(data, d_kwargs) + + # Block size shorter than actual: + block_len = int_from_bytes(data[offset:offset + 4], 'little') - 2 + data = data[:offset] + int_to_bytes(block_len, 4, 'little') + data[offset + 4:] + + message = r"^Decompression failed. error: \d+$" + + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(data, d_kwargs) + + +def test_decompress_truncated(): + c_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + input_data = b"2099023098234882923049823094823094898239230982349081231290381209380981203981209381238901283098908123109238098123" * 24 + compressed, block_offsets = compress(input_data, c_kwargs, return_block_offset=True) + + last_block_offset = 0 + for n in range(len(compressed)): + if n in block_offsets: + # end of input matches end of block, so decompression must succeed + last_block_offset = n + decompress(compressed[:n], d_kwargs) + + else: + # end of input does not match end of block, so decompression failure is expected + if n - last_block_offset < c_kwargs['store_comp_size']: + message = "^Invalid source, too small for holding any block$" + else: + message = r"^Requested input size \(\d+\) larger than source size \(\d+\)$" + + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(compressed[:n], d_kwargs) + + +# This next test is probably redundant given test_decompress_truncated above +# since the trailing bytes will be considered as the truncated last block, but +# we will keep them for now + + +def test_decompress_with_trailer(): + c_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + data = b'A' * 64 + comp = compress(data, c_kwargs) + + message = "^Invalid source, too small for holding any block$" + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(comp + b'A', d_kwargs) + + message = r"^Requested input size \(\d+\) larger than source size \(\d+\)$" + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(comp + b'A' * 10, d_kwargs) + + for n in range(1, 10): + if n < d_kwargs['store_comp_size']: + message = "^Invalid source, too small for holding any block$" + else: + message = r"^Decompression failed. error: \d+$" + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(comp + b'\x00' * n, d_kwargs) + + +def test_unicode(): + if sys.version_info < (3,): + return # skip + + c_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + DATA = b'x' + with pytest.raises(TypeError): + compress(DATA.decode('latin1'), c_kwargs) + decompress(compress(DATA, c_kwargs).decode('latin1'), d_kwargs) + + +# These next two are probably redundant given test_1 above but we'll keep them +# for now + + +def test_return_bytearray(): + if sys.version_info < (3,): + return # skip + + c_kwargs_r = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + c_kwargs = {'return_bytearray': True} + c_kwargs.update(c_kwargs_r) + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + data = os.urandom(128 * 1024) # Read 128kb + compressed = compress(data, c_kwargs_r, check_block_type=True) + b = compress(data, c_kwargs, check_block_type=True) + assert isinstance(b, bytearray) + assert bytes(b) == compressed + b = decompress(compressed, d_kwargs, check_chunk_type=True) + assert isinstance(b, bytearray) + assert bytes(b) == data + + +def test_memoryview(): + if sys.version_info < (2, 7): + return # skip + + c_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + d_kwargs = {} + d_kwargs.update(c_kwargs) + + data = os.urandom(128 * 1024) # Read 128kb + compressed = compress(data, c_kwargs) + assert compress(memoryview(data), c_kwargs) == compressed + assert decompress(memoryview(compressed), d_kwargs) == data + + +def test_with_dict_none(): + kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + input_data = b"2099023098234882923049823094823094898239230982349081231290381209380981203981209381238901283098908123109238098123" * 24 + for mode in ['default', 'high_compression']: + c_kwargs = {'mode': mode, 'dictionary': None} + c_kwargs.update(kwargs) + d_kwargs = {} + d_kwargs.update(kwargs) + assert decompress(compress(input_data, c_kwargs), d_kwargs) == input_data + + c_kwargs = {'mode': mode} + c_kwargs.update(kwargs) + d_kwargs = {'dictionary': None} + d_kwargs.update(kwargs) + assert decompress(compress(input_data, c_kwargs), d_kwargs) == input_data + + c_kwargs = {'mode': mode, 'dictionary': b''} + c_kwargs.update(kwargs) + d_kwargs = {} + d_kwargs.update(kwargs) + assert decompress(compress(input_data, c_kwargs), d_kwargs) == input_data + + c_kwargs = {'mode': mode} + c_kwargs.update(kwargs) + d_kwargs = {'dictionary': b''} + d_kwargs.update(kwargs) + assert decompress(compress(input_data, c_kwargs), d_kwargs) == input_data + + c_kwargs = {'mode': mode, 'dictionary': ''} + c_kwargs.update(kwargs) + d_kwargs = {} + d_kwargs.update(kwargs) + assert decompress(compress(input_data, c_kwargs), d_kwargs) == input_data + + c_kwargs = {'mode': mode} + c_kwargs.update(kwargs) + d_kwargs = {'dictionary': ''} + d_kwargs.update(kwargs) + assert decompress(compress(input_data, c_kwargs), d_kwargs) == input_data + + +def test_with_dict(): + kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + input_data = b"2099023098234882923049823094823094898239230982349081231290381209380981203981209381238901283098908123109238098123" * 24 + dict1 = input_data[10:30] + dict2 = input_data[20:40] + message = r"^Decompression failed. error: \d+$" + + for mode in ['default', 'high_compression']: + c_kwargs = {'mode': mode, 'dictionary': dict1} + c_kwargs.update(kwargs) + compressed = compress(input_data, c_kwargs) + + d_kwargs = {} + d_kwargs.update(kwargs) + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(compressed, d_kwargs) + + d_kwargs = {'dictionary': dict1[:2]} + d_kwargs.update(kwargs) + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(compressed, d_kwargs) + + d_kwargs = {'dictionary': dict2} + d_kwargs.update(kwargs) + assert decompress(compressed, d_kwargs) != input_data + + d_kwargs = {'dictionary': dict1} + d_kwargs.update(kwargs) + assert decompress(compressed, d_kwargs) == input_data + + c_kwargs = {} + c_kwargs.update(kwargs) + d_kwargs = {'dictionary': dict1} + d_kwargs.update(kwargs) + assert decompress(compress(input_data, c_kwargs), d_kwargs) == input_data + + +def test_known_decompress_1(): + d_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + output = b'' + + input = b'\x00\x00\x00\x00' + message = "^Decompression failed. error: 1$" + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + decompress(input, d_kwargs) + + input = b'\x01\x00\x00\x00\x00' + assert decompress(input, d_kwargs) == output + + +def test_known_decompress_2(): + d_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + input = b'\x02\x00\x00\x00\x10 ' + output = b' ' + assert decompress(input, d_kwargs) == output + + +def test_known_decompress_3(): + d_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + # uncompressed data size smaller than buffer_size + input = b'%\x00\x00\x00\xff\x0bLorem ipsum dolor sit amet\x1a\x006P amet' + output = b'Lorem ipsum dolor sit amet' * 4 + assert decompress(input, d_kwargs) == output + + +def test_known_decompress_4(): + d_kwargs = {'strategy': "double_buffer", 'buffer_size': 128, 'store_comp_size': 4} + + input = b'%\x00\x00\x00\xff\x0bLorem ipsum dolor sit amet\x1a\x00NPit am\n\x00\x00\x00\x0fh\x00hP sit \x05\x00\x00\x00@amet' + output = b'Lorem ipsum dolor sit amet' * 10 + assert decompress(input, d_kwargs) == output diff --git a/tests/stream/test_stream_2.py b/tests/stream/test_stream_2.py new file mode 100644 index 00000000..5578f832 --- /dev/null +++ b/tests/stream/test_stream_2.py @@ -0,0 +1,152 @@ +import pytest +import sys +import lz4.stream +import psutil +import os + + +# This test requires allocating a big lump of memory. In order to +# avoid a massive memory allocation during byte compilation, we have +# to declare a variable for the size of the buffer we're going to +# create outside the scope of the function below. See: +# https://bugs.python.org/issue21074 + +_4GB = 0xffffffff # actually 4GB - 1B, the maximum size on 4 bytes. + +# This test will be killed on Travis due to the 3GB memory limit +# there. Unfortunately psutil reports the host memory, not the memory +# available to the container, and so can't be used to detect available +# memory, so instead, as an ugly hack for detecting we're on Travis we +# check for the TRAVIS environment variable being set. This is quite +# fragile. + +if os.environ.get('TRAVIS') is not None or sys.maxsize < _4GB or \ + psutil.virtual_memory().available < _4GB: + huge = None +else: + try: + huge = b'\0' * _4GB + except (MemoryError, OverflowError): + huge = None + + +@pytest.mark.skipif( + os.environ.get('TRAVIS') is not None, + reason='Skipping test on Travis due to insufficient memory' +) +@pytest.mark.skipif( + sys.maxsize < _4GB, + reason='Py_ssize_t too small for this test' +) +@pytest.mark.skipif( + psutil.virtual_memory().available < _4GB or huge is None, + reason='Insufficient system memory for this test' +) +def test_huge_1(): + data = b'' + kwargs = { + 'strategy': "double_buffer", + 'buffer_size': lz4.stream.LZ4_MAX_INPUT_SIZE, + 'store_comp_size': 4, + 'dictionary': huge, + } + + if psutil.virtual_memory().available < 3 * kwargs['buffer_size']: + # The internal LZ4 context will request at least 3 times buffer_size + # as memory (2 buffer_size for the double-buffer, and 1.x buffer_size + # for the output buffer) + pytest.skip('Insufficient system memory for this test') + + # Triggering overflow error + message = r'^Dictionary too large for LZ4 API$' + + with pytest.raises(OverflowError, match=message): + with lz4.stream.LZ4StreamCompressor(**kwargs) as proc: + proc.compress(data) + + with pytest.raises(OverflowError, match=message): + with lz4.stream.LZ4StreamDecompressor(**kwargs) as proc: + proc.decompress(data) + + +@pytest.mark.skipif( + os.environ.get('TRAVIS') is not None, + reason='Skipping test on Travis due to insufficient memory' +) +@pytest.mark.skipif( + sys.maxsize < 0xffffffff, + reason='Py_ssize_t too small for this test' +) +@pytest.mark.skipif( + psutil.virtual_memory().available < _4GB or huge is None, + reason='Insufficient system memory for this test' +) +def test_huge_2(): + data = huge + kwargs = { + 'strategy': "double_buffer", + 'buffer_size': lz4.stream.LZ4_MAX_INPUT_SIZE, + 'store_comp_size': 4, + 'dictionary': b'', + } + + if psutil.virtual_memory().available < 3 * kwargs['buffer_size']: + # The internal LZ4 context will request at least 3 times buffer_size + # as memory (2 buffer_size for the double-buffer, and 1.x buffer_size + # for the output buffer) + pytest.skip('Insufficient system memory for this test') + + # Raising overflow error + message = r'^Input too large for LZ4 API$' + + with pytest.raises(OverflowError, match=message): + with lz4.stream.LZ4StreamCompressor(**kwargs) as proc: + proc.compress(data) + + # On decompression, too large input will raise LZ4StreamError + with pytest.raises(lz4.stream.LZ4StreamError): + with lz4.stream.LZ4StreamDecompressor(**kwargs) as proc: + proc.decompress(data) + + +@pytest.mark.skipif( + os.environ.get('TRAVIS') is not None, + reason='Skipping test on Travis due to insufficient memory' +) +@pytest.mark.skipif( + sys.maxsize < 0xffffffff, + reason='Py_ssize_t too small for this test' +) +@pytest.mark.skipif( + psutil.virtual_memory().available < _4GB or huge is None, + reason='Insufficient system memory for this test' +) +def test_huge_3(): + data = huge + kwargs = { + 'strategy': "double_buffer", + 'buffer_size': lz4.stream.LZ4_MAX_INPUT_SIZE, + 'store_comp_size': 4, + 'dictionary': huge, + } + + if psutil.virtual_memory().available < 3 * kwargs['buffer_size']: + # The internal LZ4 context will request at least 3 times buffer_size + # as memory (2 buffer_size for the double-buffer, and 1.x buffer_size + # for the output buffer) + pytest.skip('Insufficient system memory for this test') + + # Raising overflow error (during initialization because of the dictionary parameter) + message = r'^Dictionary too large for LZ4 API$' + + with pytest.raises(OverflowError, match=message): + with lz4.stream.LZ4StreamCompressor(**kwargs) as proc: + proc.compress(data) + + with pytest.raises(OverflowError, match=message): + with lz4.stream.LZ4StreamDecompressor(**kwargs) as proc: + proc.decompress(data) + + +def test_dummy(): + pass diff --git a/tests/stream/test_stream_3.py b/tests/stream/test_stream_3.py new file mode 100644 index 00000000..a18ed561 --- /dev/null +++ b/tests/stream/test_stream_3.py @@ -0,0 +1,128 @@ +import lz4.stream +import pytest +import sys + + +_1KB = 1024 +_1MB = _1KB * 1024 +_1GB = _1MB * 1024 + + +def compress(x, c_kwargs): + c = [] + with lz4.stream.LZ4StreamCompressor(**c_kwargs) as proc: + for start in range(0, len(x), c_kwargs['buffer_size']): + chunk = x[start:start + c_kwargs['buffer_size']] + block = proc.compress(chunk) + c.append(block) + if c_kwargs.get('return_bytearray', False): + return bytearray().join(c) + else: + return bytes().join(c) + + +def decompress(x, d_kwargs): + d = [] + with lz4.stream.LZ4StreamDecompressor(**d_kwargs) as proc: + start = 0 + while start < len(x): + block = proc.get_block(x[start:]) + chunk = proc.decompress(block) + d.append(chunk) + start += d_kwargs['store_comp_size'] + len(block) + if d_kwargs.get('return_bytearray', False): + return bytearray().join(d) + else: + return bytes().join(d) + + +test_buffer_size = sorted( + [256, + 1 * _1KB, + 64 * _1KB, + 1 * _1MB, + 1 * _1GB, + lz4.stream.LZ4_MAX_INPUT_SIZE] +) + + +@pytest.fixture( + params=test_buffer_size, + ids=[ + 'buffer_size' + str(i) for i in range(len(test_buffer_size)) + ] +) +def buffer_size(request): + return request.param + + +test_data = [ + (b'a' * _1MB), +] + + +@pytest.fixture( + params=test_data, + ids=[ + 'data' + str(i) for i in range(len(test_data)) + ] +) +def data(request): + return request.param + + +# coverage might allocate and cause this test to fail +@pytest.mark.no_cover +@pytest.mark.thread_unsafe( + reason="tracemalloc captures global snapshots" +) +def test_block_decompress_mem_usage(data, buffer_size): + kwargs = { + 'strategy': "double_buffer", + 'buffer_size': buffer_size, + 'store_comp_size': 4, + } + + if sys.maxsize < 0xffffffff: + pytest.skip('Py_ssize_t too small for this test') + + tracemalloc = pytest.importorskip('tracemalloc') + + # Trace memory usage on compression + tracemalloc.start() + prev_snapshot = None + + for i in range(1000): + compressed = compress(data, kwargs) + + if i % 100 == 0: + snapshot = tracemalloc.take_snapshot() + + if prev_snapshot: + # Filter on lz4.stream module'a allocations + stats = [x for x in snapshot.compare_to(prev_snapshot, 'lineno') + if lz4.stream.__file__ in x.traceback._frames[0][0]] + assert sum(map(lambda x: x.size_diff, stats)) < (1024 * 4) + + prev_snapshot = snapshot + + tracemalloc.stop() + + tracemalloc.start() + prev_snapshot = None + + for i in range(1000): + decompressed = decompress(compressed, kwargs) # noqa: F841 + + if i % 100 == 0: + snapshot = tracemalloc.take_snapshot() + + if prev_snapshot: + # Filter on lz4.stream module'a allocations + stats = [x for x in snapshot.compare_to(prev_snapshot, 'lineno') + if lz4.stream.__file__ in x.traceback._frames[0][0]] + assert sum(map(lambda x: x.size_diff, stats)) < (1024 * 4) + + prev_snapshot = snapshot + + tracemalloc.stop() diff --git a/tests/stream/test_stream_4.py b/tests/stream/test_stream_4.py new file mode 100644 index 00000000..3d139a02 --- /dev/null +++ b/tests/stream/test_stream_4.py @@ -0,0 +1,139 @@ +import lz4.stream +import pytest +import sys + + +if sys.version_info < (3, ): + from struct import pack, unpack + + def _get_format(length, byteorder, signed): + _order = {'l': '<', 'b': '>'} + _fmt = {1: 'b', 2: 'h', 4: 'i', 8: 'q'} + _sign = {True: lambda x: x.lower(), False: lambda x: x.upper()} + return _sign[signed](_order[byteorder[0].lower()] + _fmt[length]) + + def int_to_bytes(value, length=4, byteorder='little', signed=False): + return bytearray(pack(_get_format(length, byteorder, signed), value)) + + def int_from_bytes(bytes, byteorder='little', signed=False): + return unpack(_get_format(len(bytes), byteorder, signed), bytes)[0] + +else: + def int_to_bytes(value, length=4, byteorder='little', signed=False): + return value.to_bytes(length, byteorder, signed=signed) + + def int_from_bytes(bytes, byteorder='little', signed=False): + return int.from_bytes(bytes, byteorder, signed=signed) + +# Out-of-band block size record tests + + +def test_round_trip(): + data = b"2099023098234882923049823094823094898239230982349081231290381209380981203981209381238901283098908123109238098123" * 24 + kwargs = {'strategy': "double_buffer", 'buffer_size': 256, 'store_comp_size': 4} + + oob_kwargs = {} + oob_kwargs.update(kwargs) + oob_kwargs['store_comp_size'] = 0 + + ib_cstream = bytearray() + oob_cstream = bytearray() + oob_sizes = [] + + with lz4.stream.LZ4StreamCompressor(**kwargs) as ib_proc, \ + lz4.stream.LZ4StreamCompressor(**oob_kwargs) as oob_proc: + for start in range(0, len(data), kwargs['buffer_size']): + chunk = data[start:start + kwargs['buffer_size']] + ib_block = ib_proc.compress(chunk) + oob_block = oob_proc.compress(chunk) + + assert (len(ib_block) == (len(oob_block) + kwargs['store_comp_size'])), \ + "Blocks size mismatch: " \ + "{}/{}".format(len(ib_block), len(oob_block) + kwargs['store_comp_size']) + + assert (int_from_bytes(ib_block[:kwargs['store_comp_size']]) == len(oob_block)), \ + "Blocks size record mismatch: got {}, expected {}".format( + int_from_bytes(ib_block[:kwargs['store_comp_size']]), + len(oob_block)) + + assert (ib_block[kwargs['store_comp_size']:] == oob_block), "Blocks data mismatch" + + ib_cstream += ib_block + oob_cstream += oob_block + oob_sizes.append(len(oob_block)) + + ib_dstream = bytearray() + oob_dstream = bytearray() + + with lz4.stream.LZ4StreamDecompressor(**kwargs) as ib_proc, \ + lz4.stream.LZ4StreamDecompressor(**oob_kwargs) as oob_proc: + ib_offset = 0 + oob_index = 0 + oob_offset = 0 + while ib_offset < len(ib_cstream) and oob_index < len(oob_sizes): + ib_block = ib_proc.get_block(ib_cstream[ib_offset:]) + oob_block = oob_cstream[oob_offset:oob_offset + oob_sizes[oob_index]] + + assert (len(ib_block) == len(oob_block)), \ + "Blocks size mismatch: {}/{}".format(len(ib_block), len(oob_block)) + + assert (ib_block == oob_block), "Blocks data mismatch" + + ib_chunk = ib_proc.decompress(ib_block) + oob_chunk = oob_proc.decompress(oob_block) + + assert (len(ib_chunk) == len(oob_chunk)), \ + "Chunks size mismatch: {}/{}".format(len(ib_chunk), len(oob_chunk)) + + assert (ib_chunk == oob_chunk), "Chunks data mismatch" + + ib_dstream += ib_chunk + oob_dstream += oob_chunk + + ib_offset += kwargs['store_comp_size'] + len(ib_block) + oob_offset += oob_sizes[oob_index] + oob_index += 1 + + assert (len(ib_dstream) == len(oob_dstream)), "Decompressed streams length mismatch" + + assert (len(data) == len(ib_dstream)), "Decompressed streams length mismatch" + + assert (len(data) == len(oob_dstream)), "Decompressed streams length mismatch" + + assert (ib_dstream == oob_dstream), "Decompressed streams mismatch" + + assert (data == ib_dstream), "Decompressed streams mismatch" + + assert (data == oob_dstream), "Decompressed streams mismatch" + + +def test_invalid_usage(): + data = b"2099023098234882923049823094823094898239230982349081231290381209380981203981209381238901283098908123109238098123" * 24 + kwargs = {'strategy': "double_buffer", 'buffer_size': 256, 'store_comp_size': 0} + + cstream = bytearray() + oob_sizes = [] + + with lz4.stream.LZ4StreamCompressor(**kwargs) as proc: + for start in range(0, len(data), kwargs['buffer_size']): + chunk = data[start:start + kwargs['buffer_size']] + block = proc.compress(chunk) + cstream += block + oob_sizes.append(len(block)) + + message = r"^LZ4 context is configured for storing block size out-of-band$" + + with pytest.raises(lz4.stream.LZ4StreamError, match=message): + dstream = bytearray() + + with lz4.stream.LZ4StreamDecompressor(**kwargs) as proc: + offset = 0 + index = 0 + while offset < len(cstream): + block = proc.get_block(cstream[offset:]) + chunk = proc.decompress(block) + + dstream += chunk + + offset += kwargs['store_comp_size'] + len(block) + index += 1 diff --git a/tox.ini b/tox.ini index a5d50251..6c52f84a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,52 @@ [tox] -envlist = py +envlist = py, flake8, docs +minversion = 2.4.0 [testenv] -deps=pytest +extras = tests +passenv = * # setenv = PYTHONMALLOC = pymalloc # PYTHONMALLOCSTATS = 'yes' -commands= - pytest tests/block/test_block.py - pytest tests/frame/test_frame_0.py - pytest tests/frame/test_frame_1.py - pytest tests/frame/test_frame_2.py - pytest tests/frame/test_frame_3.py - pytest tests/frame/test_frame_4.py - pytest tests/frame/test_frame_5.py - pytest tests/frame/test_frame_6.py - pytest tests/frame/test_frame_7.py +usedevelop = True +commands = + pytest --cov=lz4/block --cov=lz4/frame --tb=long {posargs} tests/block tests/frame + +[pytest] +addopts = -x --tb=long --showlocals + + +[testenv:flake8] +extras = flake8 +passenv = * +commands = + flake8 lz4 setup.py tests + +[flake8] +ignore = E501 + +[testenv:docs] +package_env = .pkg-experimental +passenv = * +usedevelop = True +allowlist_externals = make +extras = docs +commands = + make -C docs doctest html + +[testenv:.pkg-experimental] +# This environment is used when building the lz4 package (wheel) that +# is subsequently installed into the tox environment for any +# environment referencing this one via package_env. +# https://tox.wiki/en/latest/config.html#package_env-env +setenv = + PYLZ4_EXPERIMENTAL = 1 + +[testenv:experimental] +extras = tests +passenv = * +package_env = .pkg-experimental +# PYTHONMALLOC = pymalloc +# PYTHONMALLOCSTATS = 'yes' +usedevelop = True +commands = + pytest --cov=lz4/stream --tb=long {posargs} tests/stream