Repository: jibsen/tinf Branch: master Commit: 57ffa1f1d5e3 Files: 18 Total size: 153.3 KB Directory structure: gitextract_cflkwvkt/ ├── .editorconfig ├── .github/ │ └── workflows/ │ └── tinf-ci-workflow.yaml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples/ │ └── tgunzip/ │ └── tgunzip.c ├── src/ │ ├── adler32.c │ ├── crc32.c │ ├── tinf.h │ ├── tinfgzip.c │ ├── tinflate.c │ └── tinfzlib.c ├── test/ │ ├── greatest.h │ └── test_tinf.c └── tools/ ├── gengztest.py ├── genzlibtest.py └── mkzdata.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] insert_final_newline = true trim_trailing_whitespace = true [Makefile*] indent_style = tab [*.{c,h}] indent_style = tab indent_size = 8 [*.yml] indent_style = space indent_size = 2 ================================================ FILE: .github/workflows/tinf-ci-workflow.yaml ================================================ name: tinf CI on: [push, pull_request] jobs: windows: name: Windows ${{ matrix.config.name }} runs-on: windows-latest strategy: matrix: config: - name: MSVC x64 generator: Visual Studio 16 2019 cmake-flags: -A x64 steps: - uses: actions/checkout@v2 - name: Configure run: cmake -G "${{ matrix.config.generator }}" ${{ matrix.config.cmake-flags }} -B build - name: Build run: cd build && cmake --build . --config Debug - name: Test run: cd build && ctest -V --output-on-failure --interactive-debug-mode 0 -C Debug linux: name: Linux ${{ matrix.config.name }} runs-on: ubuntu-latest env: CC: ${{ matrix.config.cc }} strategy: matrix: config: - name: Clang UBSan cc: clang cmake-flags: -DCMAKE_C_FLAGS_DEBUG='-g -fsanitize=undefined' - name: Clang ASan cc: clang cmake-flags: -DCMAKE_C_FLAGS_DEBUG='-O1 -g -fsanitize=address -fno-omit-frame-pointer' steps: - uses: actions/checkout@v2 - name: Configure run: cmake ${{ matrix.config.cmake-flags }} -DCMAKE_BUILD_TYPE=Debug -B build - name: Build run: cd build && make VERBOSE=1 - name: Test run: cd build && ctest -V --output-on-failure --interactive-debug-mode 0 coverage: name: Linux Coverage runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Configure run: cmake -DCMAKE_C_FLAGS_DEBUG='-g -O0 --coverage' -DCMAKE_BUILD_TYPE=Debug -B build - name: Build run: cd build && make VERBOSE=1 - name: Test run: cd build && ctest -V --output-on-failure --interactive-debug-mode 0 - name: Generate coverage run: cd build && gcov -abcfu CMakeFiles/tinf.dir/src/*.c.gcno -o CMakeFiles/tinf.dir/src - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 with: directory: ./build/ ================================================ FILE: .gitignore ================================================ *.o *.obj *.a *.lib *.so *.so.* *.dll *.exe /build/ /doc/ ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) project(tinf C) include(CheckCCompilerFlag) include(CTest) # Check if tinf is the top-level project (standalone), or a subproject set(_tinf_standalone FALSE) if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) set(_tinf_standalone TRUE) endif() # TINF_BUILD_TESTING controls if tinf adds testing support # # When built standalone, it defaults to the value of BUILD_TESTING if set. # An optional prefix for the test names can be set with TINF_TEST_PREFIX. set(_tinf_testing_default ON) if(_tinf_standalone) if(DEFINED BUILD_TESTING) set(_tinf_testing_default ${BUILD_TESTING}) endif() else() set(_tinf_testing_default OFF) endif() option(TINF_BUILD_TESTING "Add testing support" ${_tinf_testing_default}) unset(_tinf_testing_default) mark_as_advanced(TINF_TEST_PREFIX) # Take a list of compiler flags and add those which the compiler accepts to # the COMPILE_OPTIONS directory property function(add_valid_c_compile_options) foreach(flag IN LISTS ARGN) string(REGEX REPLACE "[^a-zA-Z0-9]+" "_" flag_var "CFLAG_${flag}") check_c_compiler_flag("${flag}" ${flag_var}) if(${flag_var}) add_compile_options("${flag}") endif() endforeach() endfunction() if(MSVC) add_valid_c_compile_options(/W3) else() add_valid_c_compile_options(-Wall -Wextra -pedantic) endif() # # tinf # add_library(tinf src/adler32.c src/crc32.c src/tinfgzip.c src/tinflate.c src/tinfzlib.c src/tinf.h ) target_include_directories(tinf PUBLIC $) # # tgunzip # add_executable(tgunzip examples/tgunzip/tgunzip.c) target_link_libraries(tgunzip PRIVATE tinf) if(MSVC) target_compile_definitions(tgunzip PRIVATE _CRT_SECURE_NO_WARNINGS) endif() # # Tests # if(TINF_BUILD_TESTING) add_executable(test_tinf test/test_tinf.c) target_link_libraries(test_tinf PRIVATE tinf) if(MSVC) target_compile_definitions(test_tinf PRIVATE _CRT_SECURE_NO_WARNINGS) endif() add_test("${TINF_TEST_PREFIX}tinf" test_tinf) endif() ================================================ FILE: LICENSE ================================================ The zlib License (Zlib) Copyright (c) 2003-2019 Joergen Ibsen This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ================================================ FILE: README.md ================================================ tinf - tiny inflate library =========================== Version 1.2.1 Copyright (c) 2003-2019 Joergen Ibsen [![tinf CI](https://github.com/jibsen/tinf/actions/workflows/tinf-ci-workflow.yaml/badge.svg)](https://github.com/jibsen/tinf/actions) [![codecov](https://codecov.io/gh/jibsen/tinf/branch/master/graph/badge.svg)](https://codecov.io/gh/jibsen/tinf) About ----- tinf is a small library implementing the decompression algorithm for the [deflate][wpdeflate] compressed data format (called 'inflate'). Deflate compression is used in e.g. zlib, gzip, zip, and png. I wrote it because I needed a small in-memory zlib decompressor for a self- extracting archive, and the zlib library added 15k to my program. The tinf code added only 2k. Naturally the size difference is insignificant in most cases. Also, the zlib library has many more features, is well-tested, and mostly faster. But if you have a project that calls for a small and simple deflate decompressor, give it a try :-) [wpdeflate]: https://en.wikipedia.org/wiki/DEFLATE Usage ----- The include file `src/tinf.h` contains documentation in the form of [doxygen][] comments. Wrappers for decompressing zlib and gzip data in memory are supplied. tgunzip, an example command-line gzip decompressor in C, is included. tinf uses [CMake][] to generate build systems. To create one for the tools on your platform, and build tinf, use something along the lines of: ~~~sh mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. cmake --build . --config Release ~~~ You can also compile the source files and link them into your project. CMake just provides an easy way to build and test across various platforms and toolsets. [doxygen]: http://www.doxygen.org/ [CMake]: http://www.cmake.org/ Notes ----- tinf requires int to be at least 32-bit. The inflate algorithm and data format are from 'DEFLATE Compressed Data Format Specification version 1.3' ([RFC 1951][deflate]). The zlib data format is from 'ZLIB Compressed Data Format Specification version 3.3' ([RFC 1950][zlib]). The gzip data format is from 'GZIP file format specification version 4.3' ([RFC 1952][gzip]). The original version of tinf assumed it was given valid compressed data, and that there was sufficient space for the decompressed data. If code size is of the utmost importance, and you are absolutely sure you can trust the compressed data, you may want to check out [tinf 1.1.0][tinf110] (last release without security checks). Ideas for future versions: - Memory for the `tinf_data` object should be passed, to avoid using more than 1k of stack space - Wrappers for unpacking zip archives and png images - Blocking of some sort, so everything does not have to be in memory - Optional table-based Huffman decoder - Small compressor using fixed Huffman trees [deflate]: http://www.rfc-editor.org/rfc/rfc1951.txt [zlib]: http://www.rfc-editor.org/rfc/rfc1950.txt [gzip]: http://www.rfc-editor.org/rfc/rfc1952.txt [tinf110]: https://github.com/jibsen/tinf/releases/tag/v1.1.0 Related Projects ---------------- - [puff](https://github.com/madler/zlib) (in the contrib folder of zlib) - [tinfl](https://github.com/richgel999/miniz) (part of miniz) - [uzlib](https://github.com/pfalcon/uzlib) - [gdunzip](https://github.com/jellehermsen/gdunzip) (GDScript) - [TinyDeflate](https://github.com/bisqwit/TinyDeflate) (C++) - [tiny-inflate](https://github.com/foliojs/tiny-inflate) (JavaScript) - [tinflate](http://achurch.org/tinflate.c) (unrelated to this project) - The [Wikipedia page for deflate](https://en.wikipedia.org/wiki/DEFLATE) has a list of implementations License ------- This projected is licensed under the [zlib License](LICENSE) (Zlib). ================================================ FILE: examples/tgunzip/tgunzip.c ================================================ /* * tgunzip - gzip decompressor example * * Copyright (c) 2003-2019 Joergen Ibsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. If you use this * software in a product, an acknowledgment in the product * documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must * not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ #include #include #include #include "tinf.h" static unsigned int read_le32(const unsigned char *p) { return ((unsigned int) p[0]) | ((unsigned int) p[1] << 8) | ((unsigned int) p[2] << 16) | ((unsigned int) p[3] << 24); } static void printf_error(const char *fmt, ...) { va_list arg; fputs("tgunzip: ", stderr); va_start(arg, fmt); vfprintf(stderr, fmt, arg); va_end(arg); fputs("\n", stderr); } int main(int argc, char *argv[]) { FILE *fin = NULL; FILE *fout = NULL; unsigned char *source = NULL; unsigned char *dest = NULL; unsigned int len, dlen, outlen; int retval = EXIT_FAILURE; int res; printf("tgunzip " TINF_VER_STRING " - example from the tiny inflate library (www.ibsensoftware.com)\n\n"); if (argc != 3) { fputs("usage: tgunzip INFILE OUTFILE\n\n" "Both input and output are kept in memory, so do not use this on huge files.\n", stderr); return EXIT_FAILURE; } tinf_init(); /* -- Open files -- */ if ((fin = fopen(argv[1], "rb")) == NULL) { printf_error("unable to open input file '%s'", argv[1]); goto out; } if ((fout = fopen(argv[2], "wb")) == NULL) { printf_error("unable to create output file '%s'", argv[2]); goto out; } /* -- Read source -- */ fseek(fin, 0, SEEK_END); len = ftell(fin); fseek(fin, 0, SEEK_SET); if (len < 18) { printf_error("input too small to be gzip"); goto out; } source = (unsigned char *) malloc(len); if (source == NULL) { printf_error("not enough memory"); goto out; } if (fread(source, 1, len, fin) != len) { printf_error("error reading input file"); goto out; } /* -- Get decompressed length -- */ dlen = read_le32(&source[len - 4]); dest = (unsigned char *) malloc(dlen ? dlen : 1); if (dest == NULL) { printf_error("not enough memory"); goto out; } /* -- Decompress data -- */ outlen = dlen; res = tinf_gzip_uncompress(dest, &outlen, source, len); if ((res != TINF_OK) || (outlen != dlen)) { printf_error("decompression failed"); goto out; } printf("decompressed %u bytes\n", outlen); /* -- Write output -- */ fwrite(dest, 1, outlen, fout); retval = EXIT_SUCCESS; out: if (fin != NULL) { fclose(fin); } if (fout != NULL) { fclose(fout); } if (source != NULL) { free(source); } if (dest != NULL) { free(dest); } return retval; } ================================================ FILE: src/adler32.c ================================================ /* * Adler-32 checksum * * Copyright (c) 2003-2019 Joergen Ibsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. If you use this * software in a product, an acknowledgment in the product * documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must * not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ /* * Adler-32 algorithm taken from the zlib source, which is * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler */ #include "tinf.h" #define A32_BASE 65521 #define A32_NMAX 5552 unsigned int tinf_adler32(const void *data, unsigned int length) { const unsigned char *buf = (const unsigned char *) data; unsigned int s1 = 1; unsigned int s2 = 0; while (length > 0) { int k = length < A32_NMAX ? length : A32_NMAX; int i; for (i = k / 16; i; --i, buf += 16) { s1 += buf[0]; s2 += s1; s1 += buf[1]; s2 += s1; s1 += buf[2]; s2 += s1; s1 += buf[3]; s2 += s1; s1 += buf[4]; s2 += s1; s1 += buf[5]; s2 += s1; s1 += buf[6]; s2 += s1; s1 += buf[7]; s2 += s1; s1 += buf[8]; s2 += s1; s1 += buf[9]; s2 += s1; s1 += buf[10]; s2 += s1; s1 += buf[11]; s2 += s1; s1 += buf[12]; s2 += s1; s1 += buf[13]; s2 += s1; s1 += buf[14]; s2 += s1; s1 += buf[15]; s2 += s1; } for (i = k % 16; i; --i) { s1 += *buf++; s2 += s1; } s1 %= A32_BASE; s2 %= A32_BASE; length -= k; } return (s2 << 16) | s1; } ================================================ FILE: src/crc32.c ================================================ /* * CRC32 checksum * * Copyright (c) 1998-2019 Joergen Ibsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. If you use this * software in a product, an acknowledgment in the product * documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must * not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ /* * CRC32 algorithm taken from the zlib source, which is * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler */ #include "tinf.h" static const unsigned int tinf_crc32tab[16] = { 0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4, 0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C, 0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C }; unsigned int tinf_crc32(const void *data, unsigned int length) { const unsigned char *buf = (const unsigned char *) data; unsigned int crc = 0xFFFFFFFF; unsigned int i; if (length == 0) { return 0; } for (i = 0; i < length; ++i) { crc ^= buf[i]; crc = tinf_crc32tab[crc & 0x0F] ^ (crc >> 4); crc = tinf_crc32tab[crc & 0x0F] ^ (crc >> 4); } return crc ^ 0xFFFFFFFF; } ================================================ FILE: src/tinf.h ================================================ /* * tinf - tiny inflate library (inflate, gzip, zlib) * * Copyright (c) 2003-2019 Joergen Ibsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. If you use this * software in a product, an acknowledgment in the product * documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must * not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ #ifndef TINF_H_INCLUDED #define TINF_H_INCLUDED #ifdef __cplusplus extern "C" { #endif #define TINF_VER_MAJOR 1 /**< Major version number */ #define TINF_VER_MINOR 2 /**< Minor version number */ #define TINF_VER_PATCH 1 /**< Patch version number */ #define TINF_VER_STRING "1.2.1" /**< Version number as a string */ #ifndef TINFCC # ifdef __WATCOMC__ # define TINFCC __cdecl # else # define TINFCC # endif #endif /** * Status codes returned. * * @see tinf_uncompress, tinf_gzip_uncompress, tinf_zlib_uncompress */ typedef enum { TINF_OK = 0, /**< Success */ TINF_DATA_ERROR = -3, /**< Input error */ TINF_BUF_ERROR = -5 /**< Not enough room for output */ } tinf_error_code; /** * Initialize global data used by tinf. * * @deprecated No longer required, may be removed in a future version. */ void TINFCC tinf_init(void); /** * Decompress `sourceLen` bytes of deflate data from `source` to `dest`. * * The variable `destLen` points to must contain the size of `dest` on entry, * and will be set to the size of the decompressed data on success. * * Reads at most `sourceLen` bytes from `source`. * Writes at most `*destLen` bytes to `dest`. * * @param dest pointer to where to place decompressed data * @param destLen pointer to variable containing size of `dest` * @param source pointer to compressed data * @param sourceLen size of compressed data * @return `TINF_OK` on success, error code on error */ int TINFCC tinf_uncompress(void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen); /** * Decompress `sourceLen` bytes of gzip data from `source` to `dest`. * * The variable `destLen` points to must contain the size of `dest` on entry, * and will be set to the size of the decompressed data on success. * * Reads at most `sourceLen` bytes from `source`. * Writes at most `*destLen` bytes to `dest`. * * @param dest pointer to where to place decompressed data * @param destLen pointer to variable containing size of `dest` * @param source pointer to compressed data * @param sourceLen size of compressed data * @return `TINF_OK` on success, error code on error */ int TINFCC tinf_gzip_uncompress(void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen); /** * Decompress `sourceLen` bytes of zlib data from `source` to `dest`. * * The variable `destLen` points to must contain the size of `dest` on entry, * and will be set to the size of the decompressed data on success. * * Reads at most `sourceLen` bytes from `source`. * Writes at most `*destLen` bytes to `dest`. * * @param dest pointer to where to place decompressed data * @param destLen pointer to variable containing size of `dest` * @param source pointer to compressed data * @param sourceLen size of compressed data * @return `TINF_OK` on success, error code on error */ int TINFCC tinf_zlib_uncompress(void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen); /** * Compute Adler-32 checksum of `length` bytes starting at `data`. * * @param data pointer to data * @param length size of data * @return Adler-32 checksum */ unsigned int TINFCC tinf_adler32(const void *data, unsigned int length); /** * Compute CRC32 checksum of `length` bytes starting at `data`. * * @param data pointer to data * @param length size of data * @return CRC32 checksum */ unsigned int TINFCC tinf_crc32(const void *data, unsigned int length); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* TINF_H_INCLUDED */ ================================================ FILE: src/tinfgzip.c ================================================ /* * tinfgzip - tiny gzip decompressor * * Copyright (c) 2003-2019 Joergen Ibsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. If you use this * software in a product, an acknowledgment in the product * documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must * not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ #include "tinf.h" typedef enum { FTEXT = 1, FHCRC = 2, FEXTRA = 4, FNAME = 8, FCOMMENT = 16 } tinf_gzip_flag; static unsigned int read_le16(const unsigned char *p) { return ((unsigned int) p[0]) | ((unsigned int) p[1] << 8); } static unsigned int read_le32(const unsigned char *p) { return ((unsigned int) p[0]) | ((unsigned int) p[1] << 8) | ((unsigned int) p[2] << 16) | ((unsigned int) p[3] << 24); } int tinf_gzip_uncompress(void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen) { const unsigned char *src = (const unsigned char *) source; unsigned char *dst = (unsigned char *) dest; const unsigned char *start; unsigned int dlen, crc32; int res; unsigned char flg; /* -- Check header -- */ /* Check room for at least 10 byte header and 8 byte trailer */ if (sourceLen < 18) { return TINF_DATA_ERROR; } /* Check id bytes */ if (src[0] != 0x1F || src[1] != 0x8B) { return TINF_DATA_ERROR; } /* Check method is deflate */ if (src[2] != 8) { return TINF_DATA_ERROR; } /* Get flag byte */ flg = src[3]; /* Check that reserved bits are zero */ if (flg & 0xE0) { return TINF_DATA_ERROR; } /* -- Find start of compressed data -- */ /* Skip base header of 10 bytes */ start = src + 10; /* Skip extra data if present */ if (flg & FEXTRA) { unsigned int xlen = read_le16(start); if (xlen > sourceLen - 12) { return TINF_DATA_ERROR; } start += xlen + 2; } /* Skip file name if present */ if (flg & FNAME) { do { if (start - src >= sourceLen) { return TINF_DATA_ERROR; } } while (*start++); } /* Skip file comment if present */ if (flg & FCOMMENT) { do { if (start - src >= sourceLen) { return TINF_DATA_ERROR; } } while (*start++); } /* Check header crc if present */ if (flg & FHCRC) { unsigned int hcrc; if (start - src > sourceLen - 2) { return TINF_DATA_ERROR; } hcrc = read_le16(start); if (hcrc != (tinf_crc32(src, start - src) & 0x0000FFFF)) { return TINF_DATA_ERROR; } start += 2; } /* -- Get decompressed length -- */ dlen = read_le32(&src[sourceLen - 4]); if (dlen > *destLen) { return TINF_BUF_ERROR; } /* -- Get CRC32 checksum of original data -- */ crc32 = read_le32(&src[sourceLen - 8]); /* -- Decompress data -- */ if ((src + sourceLen) - start < 8) { return TINF_DATA_ERROR; } res = tinf_uncompress(dst, destLen, start, (src + sourceLen) - start - 8); if (res != TINF_OK) { return TINF_DATA_ERROR; } if (*destLen != dlen) { return TINF_DATA_ERROR; } /* -- Check CRC32 checksum -- */ if (crc32 != tinf_crc32(dst, dlen)) { return TINF_DATA_ERROR; } return TINF_OK; } ================================================ FILE: src/tinflate.c ================================================ /* * tinflate - tiny inflate * * Copyright (c) 2003-2019 Joergen Ibsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. If you use this * software in a product, an acknowledgment in the product * documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must * not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ #include "tinf.h" #include #include #if defined(UINT_MAX) && (UINT_MAX) < 0xFFFFFFFFUL # error "tinf requires unsigned int to be at least 32-bit" #endif /* -- Internal data structures -- */ struct tinf_tree { unsigned short counts[16]; /* Number of codes with a given length */ unsigned short symbols[288]; /* Symbols sorted by code */ int max_sym; }; struct tinf_data { const unsigned char *source; const unsigned char *source_end; unsigned int tag; int bitcount; int overflow; unsigned char *dest_start; unsigned char *dest; unsigned char *dest_end; struct tinf_tree ltree; /* Literal/length tree */ struct tinf_tree dtree; /* Distance tree */ }; /* -- Utility functions -- */ static unsigned int read_le16(const unsigned char *p) { return ((unsigned int) p[0]) | ((unsigned int) p[1] << 8); } /* Build fixed Huffman trees */ static void tinf_build_fixed_trees(struct tinf_tree *lt, struct tinf_tree *dt) { int i; /* Build fixed literal/length tree */ for (i = 0; i < 16; ++i) { lt->counts[i] = 0; } lt->counts[7] = 24; lt->counts[8] = 152; lt->counts[9] = 112; for (i = 0; i < 24; ++i) { lt->symbols[i] = 256 + i; } for (i = 0; i < 144; ++i) { lt->symbols[24 + i] = i; } for (i = 0; i < 8; ++i) { lt->symbols[24 + 144 + i] = 280 + i; } for (i = 0; i < 112; ++i) { lt->symbols[24 + 144 + 8 + i] = 144 + i; } lt->max_sym = 285; /* Build fixed distance tree */ for (i = 0; i < 16; ++i) { dt->counts[i] = 0; } dt->counts[5] = 32; for (i = 0; i < 32; ++i) { dt->symbols[i] = i; } dt->max_sym = 29; } /* Given an array of code lengths, build a tree */ static int tinf_build_tree(struct tinf_tree *t, const unsigned char *lengths, unsigned int num) { unsigned short offs[16]; unsigned int i, num_codes, available; assert(num <= 288); for (i = 0; i < 16; ++i) { t->counts[i] = 0; } t->max_sym = -1; /* Count number of codes for each non-zero length */ for (i = 0; i < num; ++i) { assert(lengths[i] <= 15); if (lengths[i]) { t->max_sym = i; t->counts[lengths[i]]++; } } /* Compute offset table for distribution sort */ for (available = 1, num_codes = 0, i = 0; i < 16; ++i) { unsigned int used = t->counts[i]; /* Check length contains no more codes than available */ if (used > available) { return TINF_DATA_ERROR; } available = 2 * (available - used); offs[i] = num_codes; num_codes += used; } /* * Check all codes were used, or for the special case of only one * code that it has length 1 */ if ((num_codes > 1 && available > 0) || (num_codes == 1 && t->counts[1] != 1)) { return TINF_DATA_ERROR; } /* Fill in symbols sorted by code */ for (i = 0; i < num; ++i) { if (lengths[i]) { t->symbols[offs[lengths[i]]++] = i; } } /* * For the special case of only one code (which will be 0) add a * code 1 which results in a symbol that is too large */ if (num_codes == 1) { t->counts[1] = 2; t->symbols[1] = t->max_sym + 1; } return TINF_OK; } /* -- Decode functions -- */ static void tinf_refill(struct tinf_data *d, int num) { assert(num >= 0 && num <= 32); /* Read bytes until at least num bits available */ while (d->bitcount < num) { if (d->source != d->source_end) { d->tag |= (unsigned int) *d->source++ << d->bitcount; } else { d->overflow = 1; } d->bitcount += 8; } assert(d->bitcount <= 32); } static unsigned int tinf_getbits_no_refill(struct tinf_data *d, int num) { unsigned int bits; assert(num >= 0 && num <= d->bitcount); /* Get bits from tag */ bits = d->tag & ((1UL << num) - 1); /* Remove bits from tag */ d->tag >>= num; d->bitcount -= num; return bits; } /* Get num bits from source stream */ static unsigned int tinf_getbits(struct tinf_data *d, int num) { tinf_refill(d, num); return tinf_getbits_no_refill(d, num); } /* Read a num bit value from stream and add base */ static unsigned int tinf_getbits_base(struct tinf_data *d, int num, int base) { return base + (num ? tinf_getbits(d, num) : 0); } /* Given a data stream and a tree, decode a symbol */ static int tinf_decode_symbol(struct tinf_data *d, const struct tinf_tree *t) { int base = 0, offs = 0; int len; /* * Get more bits while code index is above number of codes * * Rather than the actual code, we are computing the position of the * code in the sorted order of codes, which is the index of the * corresponding symbol. * * Conceptually, for each code length (level in the tree), there are * counts[len] leaves on the left and internal nodes on the right. * The index we have decoded so far is base + offs, and if that * falls within the leaves we are done. Otherwise we adjust the range * of offs and add one more bit to it. */ for (len = 1; ; ++len) { offs = 2 * offs + tinf_getbits(d, 1); assert(len <= 15); if (offs < t->counts[len]) { break; } base += t->counts[len]; offs -= t->counts[len]; } assert(base + offs >= 0 && base + offs < 288); return t->symbols[base + offs]; } /* Given a data stream, decode dynamic trees from it */ static int tinf_decode_trees(struct tinf_data *d, struct tinf_tree *lt, struct tinf_tree *dt) { unsigned char lengths[288 + 32]; /* Special ordering of code length codes */ static const unsigned char clcidx[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; unsigned int hlit, hdist, hclen; unsigned int i, num, length; int res; /* Get 5 bits HLIT (257-286) */ hlit = tinf_getbits_base(d, 5, 257); /* Get 5 bits HDIST (1-32) */ hdist = tinf_getbits_base(d, 5, 1); /* Get 4 bits HCLEN (4-19) */ hclen = tinf_getbits_base(d, 4, 4); /* * The RFC limits the range of HLIT to 286, but lists HDIST as range * 1-32, even though distance codes 30 and 31 have no meaning. While * we could allow the full range of HLIT and HDIST to make it possible * to decode the fixed trees with this function, we consider it an * error here. * * See also: https://github.com/madler/zlib/issues/82 */ if (hlit > 286 || hdist > 30) { return TINF_DATA_ERROR; } for (i = 0; i < 19; ++i) { lengths[i] = 0; } /* Read code lengths for code length alphabet */ for (i = 0; i < hclen; ++i) { /* Get 3 bits code length (0-7) */ unsigned int clen = tinf_getbits(d, 3); lengths[clcidx[i]] = clen; } /* Build code length tree (in literal/length tree to save space) */ res = tinf_build_tree(lt, lengths, 19); if (res != TINF_OK) { return res; } /* Check code length tree is not empty */ if (lt->max_sym == -1) { return TINF_DATA_ERROR; } /* Decode code lengths for the dynamic trees */ for (num = 0; num < hlit + hdist; ) { int sym = tinf_decode_symbol(d, lt); if (sym > lt->max_sym) { return TINF_DATA_ERROR; } switch (sym) { case 16: /* Copy previous code length 3-6 times (read 2 bits) */ if (num == 0) { return TINF_DATA_ERROR; } sym = lengths[num - 1]; length = tinf_getbits_base(d, 2, 3); break; case 17: /* Repeat code length 0 for 3-10 times (read 3 bits) */ sym = 0; length = tinf_getbits_base(d, 3, 3); break; case 18: /* Repeat code length 0 for 11-138 times (read 7 bits) */ sym = 0; length = tinf_getbits_base(d, 7, 11); break; default: /* Values 0-15 represent the actual code lengths */ length = 1; break; } if (length > hlit + hdist - num) { return TINF_DATA_ERROR; } while (length--) { lengths[num++] = sym; } } /* Check EOB symbol is present */ if (lengths[256] == 0) { return TINF_DATA_ERROR; } /* Build dynamic trees */ res = tinf_build_tree(lt, lengths, hlit); if (res != TINF_OK) { return res; } res = tinf_build_tree(dt, lengths + hlit, hdist); if (res != TINF_OK) { return res; } return TINF_OK; } /* -- Block inflate functions -- */ /* Given a stream and two trees, inflate a block of data */ static int tinf_inflate_block_data(struct tinf_data *d, struct tinf_tree *lt, struct tinf_tree *dt) { /* Extra bits and base tables for length codes */ static const unsigned char length_bits[30] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 127 }; static const unsigned short length_base[30] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0 }; /* Extra bits and base tables for distance codes */ static const unsigned char dist_bits[30] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; static const unsigned short dist_base[30] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 }; for (;;) { int sym = tinf_decode_symbol(d, lt); /* Check for overflow in bit reader */ if (d->overflow) { return TINF_DATA_ERROR; } if (sym < 256) { if (d->dest == d->dest_end) { return TINF_BUF_ERROR; } *d->dest++ = sym; } else { int length, dist, offs; int i; /* Check for end of block */ if (sym == 256) { return TINF_OK; } /* Check sym is within range and distance tree is not empty */ if (sym > lt->max_sym || sym - 257 > 28 || dt->max_sym == -1) { return TINF_DATA_ERROR; } sym -= 257; /* Possibly get more bits from length code */ length = tinf_getbits_base(d, length_bits[sym], length_base[sym]); dist = tinf_decode_symbol(d, dt); /* Check dist is within range */ if (dist > dt->max_sym || dist > 29) { return TINF_DATA_ERROR; } /* Possibly get more bits from distance code */ offs = tinf_getbits_base(d, dist_bits[dist], dist_base[dist]); if (offs > d->dest - d->dest_start) { return TINF_DATA_ERROR; } if (d->dest_end - d->dest < length) { return TINF_BUF_ERROR; } /* Copy match */ for (i = 0; i < length; ++i) { d->dest[i] = d->dest[i - offs]; } d->dest += length; } } } /* Inflate an uncompressed block of data */ static int tinf_inflate_uncompressed_block(struct tinf_data *d) { unsigned int length, invlength; if (d->source_end - d->source < 4) { return TINF_DATA_ERROR; } /* Get length */ length = read_le16(d->source); /* Get one's complement of length */ invlength = read_le16(d->source + 2); /* Check length */ if (length != (~invlength & 0x0000FFFF)) { return TINF_DATA_ERROR; } d->source += 4; if (d->source_end - d->source < length) { return TINF_DATA_ERROR; } if (d->dest_end - d->dest < length) { return TINF_BUF_ERROR; } /* Copy block */ while (length--) { *d->dest++ = *d->source++; } /* Make sure we start next block on a byte boundary */ d->tag = 0; d->bitcount = 0; return TINF_OK; } /* Inflate a block of data compressed with fixed Huffman trees */ static int tinf_inflate_fixed_block(struct tinf_data *d) { /* Build fixed Huffman trees */ tinf_build_fixed_trees(&d->ltree, &d->dtree); /* Decode block using fixed trees */ return tinf_inflate_block_data(d, &d->ltree, &d->dtree); } /* Inflate a block of data compressed with dynamic Huffman trees */ static int tinf_inflate_dynamic_block(struct tinf_data *d) { /* Decode trees from stream */ int res = tinf_decode_trees(d, &d->ltree, &d->dtree); if (res != TINF_OK) { return res; } /* Decode block using decoded trees */ return tinf_inflate_block_data(d, &d->ltree, &d->dtree); } /* -- Public functions -- */ /* Initialize global (static) data */ void tinf_init(void) { return; } /* Inflate stream from source to dest */ int tinf_uncompress(void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen) { struct tinf_data d; int bfinal; /* Initialise data */ d.source = (const unsigned char *) source; d.source_end = d.source + sourceLen; d.tag = 0; d.bitcount = 0; d.overflow = 0; d.dest = (unsigned char *) dest; d.dest_start = d.dest; d.dest_end = d.dest + *destLen; do { unsigned int btype; int res; /* Read final block flag */ bfinal = tinf_getbits(&d, 1); /* Read block type (2 bits) */ btype = tinf_getbits(&d, 2); /* Decompress block */ switch (btype) { case 0: /* Decompress uncompressed block */ res = tinf_inflate_uncompressed_block(&d); break; case 1: /* Decompress block with fixed Huffman trees */ res = tinf_inflate_fixed_block(&d); break; case 2: /* Decompress block with dynamic Huffman trees */ res = tinf_inflate_dynamic_block(&d); break; default: res = TINF_DATA_ERROR; break; } if (res != TINF_OK) { return res; } } while (!bfinal); /* Check for overflow in bit reader */ if (d.overflow) { return TINF_DATA_ERROR; } *destLen = d.dest - d.dest_start; return TINF_OK; } /* clang -g -O1 -fsanitize=fuzzer,address -DTINF_FUZZING tinflate.c */ #if defined(TINF_FUZZING) #include #include #include #include #include unsigned char depacked[64 * 1024]; extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { if (size > UINT_MAX / 2) { return 0; } unsigned int destLen = sizeof(depacked); tinf_uncompress(depacked, &destLen, data, size); return 0; } #endif ================================================ FILE: src/tinfzlib.c ================================================ /* * tinfzlib - tiny zlib decompressor * * Copyright (c) 2003-2019 Joergen Ibsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. If you use this * software in a product, an acknowledgment in the product * documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must * not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ #include "tinf.h" static unsigned int read_be32(const unsigned char *p) { return ((unsigned int) p[0] << 24) | ((unsigned int) p[1] << 16) | ((unsigned int) p[2] << 8) | ((unsigned int) p[3]); } int tinf_zlib_uncompress(void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen) { const unsigned char *src = (const unsigned char *) source; unsigned char *dst = (unsigned char *) dest; unsigned int a32; int res; unsigned char cmf, flg; /* -- Check header -- */ /* Check room for at least 2 byte header and 4 byte trailer */ if (sourceLen < 6) { return TINF_DATA_ERROR; } /* Get header bytes */ cmf = src[0]; flg = src[1]; /* Check checksum */ if ((256 * cmf + flg) % 31) { return TINF_DATA_ERROR; } /* Check method is deflate */ if ((cmf & 0x0F) != 8) { return TINF_DATA_ERROR; } /* Check window size is valid */ if ((cmf >> 4) > 7) { return TINF_DATA_ERROR; } /* Check there is no preset dictionary */ if (flg & 0x20) { return TINF_DATA_ERROR; } /* -- Get Adler-32 checksum of original data -- */ a32 = read_be32(&src[sourceLen - 4]); /* -- Decompress data -- */ res = tinf_uncompress(dst, destLen, src + 2, sourceLen - 6); if (res != TINF_OK) { return TINF_DATA_ERROR; } /* -- Check Adler-32 checksum -- */ if (a32 != tinf_adler32(dst, *destLen)) { return TINF_DATA_ERROR; } return TINF_OK; } ================================================ FILE: test/greatest.h ================================================ /* * Copyright (c) 2011-2021 Scott Vokes * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef GREATEST_H #define GREATEST_H #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) extern "C" { #endif /* 1.5.0 */ #define GREATEST_VERSION_MAJOR 1 #define GREATEST_VERSION_MINOR 5 #define GREATEST_VERSION_PATCH 0 /* A unit testing system for C, contained in 1 file. * It doesn't use dynamic allocation or depend on anything * beyond ANSI C89. * * An up-to-date version can be found at: * https://github.com/silentbicycle/greatest/ */ /********************************************************************* * Minimal test runner template *********************************************************************/ #if 0 #include "greatest.h" TEST foo_should_foo(void) { PASS(); } static void setup_cb(void *data) { printf("setup callback for each test case\n"); } static void teardown_cb(void *data) { printf("teardown callback for each test case\n"); } SUITE(suite) { /* Optional setup/teardown callbacks which will be run before/after * every test case. If using a test suite, they will be cleared when * the suite finishes. */ SET_SETUP(setup_cb, voidp_to_callback_data); SET_TEARDOWN(teardown_cb, voidp_to_callback_data); RUN_TEST(foo_should_foo); } /* Add definitions that need to be in the test runner's main file. */ GREATEST_MAIN_DEFS(); /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ int run_tests(void) { GREATEST_INIT(); /* init. greatest internals */ /* List of suites to run (if any). */ RUN_SUITE(suite); /* Tests can also be run directly, without using test suites. */ RUN_TEST(foo_should_foo); GREATEST_PRINT_REPORT(); /* display results */ return greatest_all_passed(); } /* main(), for a standalone command-line test runner. * This replaces run_tests above, and adds command line option * handling and exiting with a pass/fail status. */ int main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ RUN_SUITE(suite); GREATEST_MAIN_END(); /* display results */ } #endif /*********************************************************************/ #include #include #include #include /*********** * Options * ***********/ /* Default column width for non-verbose output. */ #ifndef GREATEST_DEFAULT_WIDTH #define GREATEST_DEFAULT_WIDTH 72 #endif /* FILE *, for test logging. */ #ifndef GREATEST_STDOUT #define GREATEST_STDOUT stdout #endif /* Remove GREATEST_ prefix from most commonly used symbols? */ #ifndef GREATEST_USE_ABBREVS #define GREATEST_USE_ABBREVS 1 #endif /* Set to 0 to disable all use of setjmp/longjmp. */ #ifndef GREATEST_USE_LONGJMP #define GREATEST_USE_LONGJMP 0 #endif /* Make it possible to replace fprintf with another * function with the same interface. */ #ifndef GREATEST_FPRINTF #define GREATEST_FPRINTF fprintf #endif #if GREATEST_USE_LONGJMP #include #endif /* Set to 0 to disable all use of time.h / clock(). */ #ifndef GREATEST_USE_TIME #define GREATEST_USE_TIME 1 #endif #if GREATEST_USE_TIME #include #endif /* Floating point type, for ASSERT_IN_RANGE. */ #ifndef GREATEST_FLOAT #define GREATEST_FLOAT double #define GREATEST_FLOAT_FMT "%g" #endif /* Size of buffer for test name + optional '_' separator and suffix */ #ifndef GREATEST_TESTNAME_BUF_SIZE #define GREATEST_TESTNAME_BUF_SIZE 128 #endif /********* * Types * *********/ /* Info for the current running suite. */ typedef struct greatest_suite_info { unsigned int tests_run; unsigned int passed; unsigned int failed; unsigned int skipped; #if GREATEST_USE_TIME /* timers, pre/post running suite and individual tests */ clock_t pre_suite; clock_t post_suite; clock_t pre_test; clock_t post_test; #endif } greatest_suite_info; /* Type for a suite function. */ typedef void greatest_suite_cb(void); /* Types for setup/teardown callbacks. If non-NULL, these will be run * and passed the pointer to their additional data. */ typedef void greatest_setup_cb(void *udata); typedef void greatest_teardown_cb(void *udata); /* Type for an equality comparison between two pointers of the same type. * Should return non-0 if equal, otherwise 0. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_equal_cb(const void *expd, const void *got, void *udata); /* Type for a callback that prints a value pointed to by T. * Return value has the same meaning as printf's. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_printf_cb(const void *t, void *udata); /* Callbacks for an arbitrary type; needed for type-specific * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ typedef struct greatest_type_info { greatest_equal_cb *equal; greatest_printf_cb *print; } greatest_type_info; typedef struct greatest_memory_cmp_env { const unsigned char *exp; const unsigned char *got; size_t size; } greatest_memory_cmp_env; /* Callbacks for string and raw memory types. */ extern greatest_type_info greatest_type_info_string; extern greatest_type_info greatest_type_info_memory; typedef enum { GREATEST_FLAG_FIRST_FAIL = 0x01, GREATEST_FLAG_LIST_ONLY = 0x02, GREATEST_FLAG_ABORT_ON_FAIL = 0x04 } greatest_flag_t; /* Internal state for a PRNG, used to shuffle test order. */ struct greatest_prng { unsigned char random_order; /* use random ordering? */ unsigned char initialized; /* is random ordering initialized? */ unsigned char pad_0[6]; unsigned long state; /* PRNG state */ unsigned long count; /* how many tests, this pass */ unsigned long count_ceil; /* total number of tests */ unsigned long count_run; /* total tests run */ unsigned long a; /* LCG multiplier */ unsigned long c; /* LCG increment */ unsigned long m; /* LCG modulus, based on count_ceil */ }; /* Struct containing all test runner state. */ typedef struct greatest_run_info { unsigned char flags; unsigned char verbosity; unsigned char running_test; /* guard for nested RUN_TEST calls */ unsigned char exact_name_match; unsigned int tests_run; /* total test count */ /* currently running test suite */ greatest_suite_info suite; /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; /* info to print about the most recent failure */ unsigned int fail_line; unsigned int pad_1; const char *fail_file; const char *msg; /* current setup/teardown hooks and userdata */ greatest_setup_cb *setup; void *setup_udata; greatest_teardown_cb *teardown; void *teardown_udata; /* formatting info for ".....s...F"-style output */ unsigned int col; unsigned int width; /* only run a specific suite or test */ const char *suite_filter; const char *test_filter; const char *test_exclude; const char *name_suffix; /* print suffix with test name */ char name_buf[GREATEST_TESTNAME_BUF_SIZE]; struct greatest_prng prng[2]; /* 0: suites, 1: tests */ #if GREATEST_USE_TIME /* overall timers */ clock_t begin; clock_t end; #endif #if GREATEST_USE_LONGJMP int pad_jmp_buf; unsigned char pad_2[4]; jmp_buf jump_dest; #endif } greatest_run_info; struct greatest_report_t { /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; }; /* Global var for the current testing context. * Initialized by GREATEST_MAIN_DEFS(). */ extern greatest_run_info greatest_info; /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ typedef const char *greatest_enum_str_fun(int value); /********************** * Exported functions * **********************/ /* These are used internally by greatest macros. */ int greatest_test_pre(const char *name); void greatest_test_post(int res); int greatest_do_assert_equal_t(const void *expd, const void *got, greatest_type_info *type_info, void *udata); void greatest_prng_init_first_pass(int id); int greatest_prng_init_second_pass(int id, unsigned long seed); void greatest_prng_step(int id); /* These are part of the public greatest API. */ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); void GREATEST_INIT(void); void GREATEST_PRINT_REPORT(void); int greatest_all_passed(void); void greatest_set_suite_filter(const char *filter); void greatest_set_test_filter(const char *filter); void greatest_set_test_exclude(const char *filter); void greatest_set_exact_name_match(void); void greatest_stop_at_first_fail(void); void greatest_abort_on_fail(void); void greatest_list_only(void); void greatest_get_report(struct greatest_report_t *report); unsigned int greatest_get_verbosity(void); void greatest_set_verbosity(unsigned int verbosity); void greatest_set_flag(greatest_flag_t flag); void greatest_set_test_suffix(const char *suffix); /******************** * Language Support * ********************/ /* If __VA_ARGS__ (C99) is supported, allow parametric testing * without needing to manually manage the argument struct. */ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 19901L) || \ (defined(_MSC_VER) && _MSC_VER >= 1800) #define GREATEST_VA_ARGS #endif /********** * Macros * **********/ /* Define a suite. (The duplication is intentional -- it eliminates * a warning from -Wmissing-declarations.) */ #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) /* Declare a suite, provided by another compilation unit. */ #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) /* Start defining a test function. * The arguments are not included, to allow parametric testing. */ #define GREATEST_TEST static enum greatest_test_res /* PASS/FAIL/SKIP result from a test. Used internally. */ typedef enum greatest_test_res { GREATEST_TEST_RES_PASS = 0, GREATEST_TEST_RES_FAIL = -1, GREATEST_TEST_RES_SKIP = 1 } greatest_test_res; /* Run a suite. */ #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) /* Run a test in the current suite. */ #define GREATEST_RUN_TEST(TEST) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(); \ } \ greatest_test_post(res); \ } \ } while (0) /* Ignore a test, don't warn about it being unused. */ #define GREATEST_IGNORE_TEST(TEST) (void)TEST /* Run a test in the current suite with one void * argument, * which can be a pointer to a struct with multiple arguments. */ #define GREATEST_RUN_TEST1(TEST, ENV) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(ENV); \ } \ greatest_test_post(res); \ } \ } while (0) #ifdef GREATEST_VA_ARGS #define GREATEST_RUN_TESTp(TEST, ...) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(__VA_ARGS__); \ } \ greatest_test_post(res); \ } \ } while (0) #endif /* Check if the test runner is in verbose mode. */ #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) #define GREATEST_LIST_ONLY() \ (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) #define GREATEST_FIRST_FAIL() \ (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) #define GREATEST_ABORT_ON_FAIL() \ (greatest_info.flags & GREATEST_FLAG_ABORT_ON_FAIL) #define GREATEST_FAILURE_ABORT() \ (GREATEST_FIRST_FAIL() && \ (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) /* Message-less forms of tests defined below. */ #define GREATEST_PASS() GREATEST_PASSm(NULL) #define GREATEST_FAIL() GREATEST_FAILm(NULL) #define GREATEST_SKIP() GREATEST_SKIPm(NULL) #define GREATEST_ASSERT(COND) \ GREATEST_ASSERTm(#COND, COND) #define GREATEST_ASSERT_OR_LONGJMP(COND) \ GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) #define GREATEST_ASSERT_FALSE(COND) \ GREATEST_ASSERT_FALSEm(#COND, COND) #define GREATEST_ASSERT_EQ(EXP, GOT) \ GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_NEQ(EXP, GOT) \ GREATEST_ASSERT_NEQm(#EXP " == " #GOT, EXP, GOT) #define GREATEST_ASSERT_GT(EXP, GOT) \ GREATEST_ASSERT_GTm(#EXP " <= " #GOT, EXP, GOT) #define GREATEST_ASSERT_GTE(EXP, GOT) \ GREATEST_ASSERT_GTEm(#EXP " < " #GOT, EXP, GOT) #define GREATEST_ASSERT_LT(EXP, GOT) \ GREATEST_ASSERT_LTm(#EXP " >= " #GOT, EXP, GOT) #define GREATEST_ASSERT_LTE(EXP, GOT) \ GREATEST_ASSERT_LTEm(#EXP " > " #GOT, EXP, GOT) #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) /* The following forms take an additional message argument first, * to be displayed by the test runner. */ /* Fail if a condition is not true, with message. */ #define GREATEST_ASSERTm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if a condition is not true, longjmping out of test. */ #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ } while (0) /* Fail if a condition is not false, with message. */ #define GREATEST_ASSERT_FALSEm(MSG, COND) \ do { \ greatest_info.assertions++; \ if ((COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Internal macro for relational assertions */ #define GREATEST__REL(REL, MSG, EXP, GOT) \ do { \ greatest_info.assertions++; \ if (!((EXP) REL (GOT))) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP is not ==, !=, >, <, >=, or <= to GOT. */ #define GREATEST_ASSERT_EQm(MSG,E,G) GREATEST__REL(==, MSG,E,G) #define GREATEST_ASSERT_NEQm(MSG,E,G) GREATEST__REL(!=, MSG,E,G) #define GREATEST_ASSERT_GTm(MSG,E,G) GREATEST__REL(>, MSG,E,G) #define GREATEST_ASSERT_GTEm(MSG,E,G) GREATEST__REL(>=, MSG,E,G) #define GREATEST_ASSERT_LTm(MSG,E,G) GREATEST__REL(<, MSG,E,G) #define GREATEST_ASSERT_LTEm(MSG,E,G) GREATEST__REL(<=, MSG,E,G) /* Fail if EXP != GOT (equality comparison by ==). * Warning: FMT, EXP, and GOT will be evaluated more * than once on failure. */ #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ do { \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, printing enum IDs. */ #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ do { \ int greatest_EXP = (int)(EXP); \ int greatest_GOT = (int)(GOT); \ greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ if (greatest_EXP != greatest_GOT) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ greatest_ENUM_STR(greatest_EXP)); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ greatest_ENUM_STR(greatest_GOT)); \ GREATEST_FAILm(MSG); \ } \ } while (0) \ /* Fail if GOT not in range of EXP +|- TOL. */ #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ do { \ GREATEST_FLOAT greatest_EXP = (EXP); \ GREATEST_FLOAT greatest_GOT = (GOT); \ GREATEST_FLOAT greatest_TOL = (TOL); \ greatest_info.assertions++; \ if ((greatest_EXP > greatest_GOT && \ greatest_EXP - greatest_GOT > greatest_TOL) || \ (greatest_EXP < greatest_GOT && \ greatest_GOT - greatest_EXP > greatest_TOL)) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nExpected: " GREATEST_FLOAT_FMT \ " +/- " GREATEST_FLOAT_FMT \ "\n Got: " GREATEST_FLOAT_FMT \ "\n", \ greatest_EXP, greatest_TOL, greatest_GOT); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, according to strcmp. */ #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ do { \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, NULL); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to strncmp. */ #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ do { \ size_t size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, &size); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to memcmp. */ #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ do { \ greatest_memory_cmp_env env; \ env.exp = (const unsigned char *)EXP; \ env.got = (const unsigned char *)GOT; \ env.size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ &greatest_type_info_memory, &env); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to a comparison * callback in TYPE_INFO. If they are not equal, optionally use a * print callback in TYPE_INFO to print them. */ #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ do { \ greatest_type_info *type_info = (TYPE_INFO); \ greatest_info.assertions++; \ if (!greatest_do_assert_equal_t(EXP, GOT, \ type_info, UDATA)) { \ if (type_info == NULL || type_info->equal == NULL) { \ GREATEST_FAILm("type_info->equal callback missing!"); \ } else { \ GREATEST_FAILm(MSG); \ } \ } \ } while (0) \ /* Pass. */ #define GREATEST_PASSm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_PASS; \ } while (0) /* Fail. */ #define GREATEST_FAILm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ if (GREATEST_ABORT_ON_FAIL()) { abort(); } \ return GREATEST_TEST_RES_FAIL; \ } while (0) /* Optional GREATEST_FAILm variant that longjmps. */ #if GREATEST_USE_LONGJMP #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ } while (0) #endif /* Skip the current test. */ #define GREATEST_SKIPm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_SKIP; \ } while (0) /* Check the result of a subfunction using ASSERT, etc. */ #define GREATEST_CHECK_CALL(RES) \ do { \ enum greatest_test_res greatest_RES = RES; \ if (greatest_RES != GREATEST_TEST_RES_PASS) { \ return greatest_RES; \ } \ } while (0) \ #if GREATEST_USE_TIME #define GREATEST_SET_TIME(NAME) \ NAME = clock(); \ if (NAME == (clock_t) -1) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "clock error: %s\n", #NAME); \ exit(EXIT_FAILURE); \ } #define GREATEST_CLOCK_DIFF(C1, C2) \ GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ (long unsigned int) (C2) - (long unsigned int)(C1), \ (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) #else #define GREATEST_SET_TIME(UNUSED) #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) #endif #if GREATEST_USE_LONGJMP #define GREATEST_SAVE_CONTEXT() \ /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) #else #define GREATEST_SAVE_CONTEXT() \ /*a no-op, since setjmp/longjmp aren't being used */ \ GREATEST_TEST_RES_PASS #endif /* Run every suite / test function run within BODY in pseudo-random * order, seeded by SEED. (The top 3 bits of the seed are ignored.) * * This should be called like: * GREATEST_SHUFFLE_TESTS(seed, { * GREATEST_RUN_TEST(some_test); * GREATEST_RUN_TEST(some_other_test); * GREATEST_RUN_TEST(yet_another_test); * }); * * Note that the body of the second argument will be evaluated * multiple times. */ #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) #define GREATEST_SHUFFLE(ID, SD, BODY) \ do { \ struct greatest_prng *prng = &greatest_info.prng[ID]; \ greatest_prng_init_first_pass(ID); \ do { \ prng->count = 0; \ if (prng->initialized) { greatest_prng_step(ID); } \ BODY; \ if (!prng->initialized) { \ if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ } else if (prng->count_run == prng->count_ceil) { \ break; \ } \ } while (!GREATEST_FAILURE_ABORT()); \ prng->count_run = prng->random_order = prng->initialized = 0; \ } while(0) /* Include several function definitions in the main test file. */ #define GREATEST_MAIN_DEFS() \ \ /* Is FILTER a subset of NAME? */ \ static int greatest_name_match(const char *name, const char *filter, \ int res_if_none) { \ size_t offset = 0; \ size_t filter_len = filter ? strlen(filter) : 0; \ if (filter_len == 0) { return res_if_none; } /* no filter */ \ if (greatest_info.exact_name_match && strlen(name) != filter_len) { \ return 0; /* ignore substring matches */ \ } \ while (name[offset] != '\0') { \ if (name[offset] == filter[0]) { \ if (0 == strncmp(&name[offset], filter, filter_len)) { \ return 1; \ } \ } \ offset++; \ } \ \ return 0; \ } \ \ static void greatest_buffer_test_name(const char *name) { \ struct greatest_run_info *g = &greatest_info; \ size_t len = strlen(name), size = sizeof(g->name_buf); \ memset(g->name_buf, 0x00, size); \ (void)strncat(g->name_buf, name, size - 1); \ if (g->name_suffix && (len + 1 < size)) { \ g->name_buf[len] = '_'; \ strncat(&g->name_buf[len+1], g->name_suffix, size-(len+2)); \ } \ } \ \ /* Before running a test, check the name filtering and \ * test shuffling state, if applicable, and then call setup hooks. */ \ int greatest_test_pre(const char *name) { \ struct greatest_run_info *g = &greatest_info; \ int match; \ greatest_buffer_test_name(name); \ match = greatest_name_match(g->name_buf, g->test_filter, 1) && \ !greatest_name_match(g->name_buf, g->test_exclude, 0); \ if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ if (match) { \ GREATEST_FPRINTF(GREATEST_STDOUT, " %s\n", g->name_buf); \ } \ goto clear; \ } \ if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ struct greatest_prng *p = &g->prng[1]; \ if (p->random_order) { \ p->count++; \ if (!p->initialized || ((p->count - 1) != p->state)) { \ goto clear; /* don't run this test yet */ \ } \ } \ if (g->running_test) { \ fprintf(stderr, "Error: Test run inside another test.\n"); \ return 0; \ } \ GREATEST_SET_TIME(g->suite.pre_test); \ if (g->setup) { g->setup(g->setup_udata); } \ p->count_run++; \ g->running_test = 1; \ return 1; /* test should be run */ \ } else { \ goto clear; /* skipped */ \ } \ clear: \ g->name_suffix = NULL; \ return 0; \ } \ \ static void greatest_do_pass(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ g->name_buf, g->msg ? g->msg : ""); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ } \ g->suite.passed++; \ } \ \ static void greatest_do_fail(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "FAIL %s: %s (%s:%u)", g->name_buf, \ g->msg ? g->msg : "", g->fail_file, g->fail_line); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ g->col++; /* add linebreak if in line of '.'s */ \ if (g->col != 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ g->col = 0; \ } \ GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ g->name_buf, g->msg ? g->msg : "", \ g->fail_file, g->fail_line); \ } \ g->suite.failed++; \ } \ \ static void greatest_do_skip(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ g->name_buf, g->msg ? g->msg : ""); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ } \ g->suite.skipped++; \ } \ \ void greatest_test_post(int res) { \ GREATEST_SET_TIME(greatest_info.suite.post_test); \ if (greatest_info.teardown) { \ void *udata = greatest_info.teardown_udata; \ greatest_info.teardown(udata); \ } \ \ greatest_info.running_test = 0; \ if (res <= GREATEST_TEST_RES_FAIL) { \ greatest_do_fail(); \ } else if (res >= GREATEST_TEST_RES_SKIP) { \ greatest_do_skip(); \ } else if (res == GREATEST_TEST_RES_PASS) { \ greatest_do_pass(); \ } \ greatest_info.name_suffix = NULL; \ greatest_info.suite.tests_run++; \ greatest_info.col++; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ greatest_info.suite.post_test); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } else if (greatest_info.col % greatest_info.width == 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ fflush(GREATEST_STDOUT); \ } \ \ static void report_suite(void) { \ if (greatest_info.suite.tests_run > 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\n%u test%s - %u passed, %u failed, %u skipped", \ greatest_info.suite.tests_run, \ greatest_info.suite.tests_run == 1 ? "" : "s", \ greatest_info.suite.passed, \ greatest_info.suite.failed, \ greatest_info.suite.skipped); \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ greatest_info.suite.post_suite); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } \ } \ \ static void update_counts_and_reset_suite(void) { \ greatest_info.setup = NULL; \ greatest_info.setup_udata = NULL; \ greatest_info.teardown = NULL; \ greatest_info.teardown_udata = NULL; \ greatest_info.passed += greatest_info.suite.passed; \ greatest_info.failed += greatest_info.suite.failed; \ greatest_info.skipped += greatest_info.suite.skipped; \ greatest_info.tests_run += greatest_info.suite.tests_run; \ memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ greatest_info.col = 0; \ } \ \ static int greatest_suite_pre(const char *suite_name) { \ struct greatest_prng *p = &greatest_info.prng[0]; \ if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ || (GREATEST_FAILURE_ABORT())) { return 0; } \ if (p->random_order) { \ p->count++; \ if (!p->initialized || ((p->count - 1) != p->state)) { \ return 0; /* don't run this suite yet */ \ } \ } \ p->count_run++; \ update_counts_and_reset_suite(); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ return 1; \ } \ \ static void greatest_suite_post(void) { \ GREATEST_SET_TIME(greatest_info.suite.post_suite); \ report_suite(); \ } \ \ static void greatest_run_suite(greatest_suite_cb *suite_cb, \ const char *suite_name) { \ if (greatest_suite_pre(suite_name)) { \ suite_cb(); \ greatest_suite_post(); \ } \ } \ \ int greatest_do_assert_equal_t(const void *expd, const void *got, \ greatest_type_info *type_info, void *udata) { \ int eq = 0; \ if (type_info == NULL || type_info->equal == NULL) { return 0; } \ eq = type_info->equal(expd, got, udata); \ if (!eq) { \ if (type_info->print != NULL) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ (void)type_info->print(expd, udata); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ (void)type_info->print(got, udata); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } \ } \ return eq; \ } \ \ static void greatest_usage(const char *name) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Usage: %s [-hlfavex] [-s SUITE] [-t TEST] [-x EXCLUDE]\n" \ " -h, --help print this Help\n" \ " -l List suites and tests, then exit (dry run)\n" \ " -f Stop runner after first failure\n" \ " -a Abort on first failure (implies -f)\n" \ " -v Verbose output\n" \ " -s SUITE only run suites containing substring SUITE\n" \ " -t TEST only run tests containing substring TEST\n" \ " -e only run exact name match for -s or -t\n" \ " -x EXCLUDE exclude tests containing substring EXCLUDE\n", \ name); \ } \ \ static void greatest_parse_options(int argc, char **argv) { \ int i = 0; \ for (i = 1; i < argc; i++) { \ if (argv[i][0] == '-') { \ char f = argv[i][1]; \ if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ greatest_usage(argv[0]); exit(EXIT_FAILURE); \ } \ switch (f) { \ case 's': /* suite name filter */ \ greatest_set_suite_filter(argv[i + 1]); i++; break; \ case 't': /* test name filter */ \ greatest_set_test_filter(argv[i + 1]); i++; break; \ case 'x': /* test name exclusion */ \ greatest_set_test_exclude(argv[i + 1]); i++; break; \ case 'e': /* exact name match */ \ greatest_set_exact_name_match(); break; \ case 'f': /* first fail flag */ \ greatest_stop_at_first_fail(); break; \ case 'a': /* abort() on fail flag */ \ greatest_abort_on_fail(); break; \ case 'l': /* list only (dry run) */ \ greatest_list_only(); break; \ case 'v': /* first fail flag */ \ greatest_info.verbosity++; break; \ case 'h': /* help */ \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ default: \ case '-': \ if (0 == strncmp("--help", argv[i], 6)) { \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ } else if (0 == strcmp("--", argv[i])) { \ return; /* ignore following arguments */ \ } \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Unknown argument '%s'\n", argv[i]); \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ } \ } \ } \ \ int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ \ void greatest_set_test_filter(const char *filter) { \ greatest_info.test_filter = filter; \ } \ \ void greatest_set_test_exclude(const char *filter) { \ greatest_info.test_exclude = filter; \ } \ \ void greatest_set_suite_filter(const char *filter) { \ greatest_info.suite_filter = filter; \ } \ \ void greatest_set_exact_name_match(void) { \ greatest_info.exact_name_match = 1; \ } \ \ void greatest_stop_at_first_fail(void) { \ greatest_set_flag(GREATEST_FLAG_FIRST_FAIL); \ } \ \ void greatest_abort_on_fail(void) { \ greatest_set_flag(GREATEST_FLAG_ABORT_ON_FAIL); \ } \ \ void greatest_list_only(void) { \ greatest_set_flag(GREATEST_FLAG_LIST_ONLY); \ } \ \ void greatest_get_report(struct greatest_report_t *report) { \ if (report) { \ report->passed = greatest_info.passed; \ report->failed = greatest_info.failed; \ report->skipped = greatest_info.skipped; \ report->assertions = greatest_info.assertions; \ } \ } \ \ unsigned int greatest_get_verbosity(void) { \ return greatest_info.verbosity; \ } \ \ void greatest_set_verbosity(unsigned int verbosity) { \ greatest_info.verbosity = (unsigned char)verbosity; \ } \ \ void greatest_set_flag(greatest_flag_t flag) { \ greatest_info.flags = (unsigned char)(greatest_info.flags | flag); \ } \ \ void greatest_set_test_suffix(const char *suffix) { \ greatest_info.name_suffix = suffix; \ } \ \ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ greatest_info.setup = cb; \ greatest_info.setup_udata = udata; \ } \ \ void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata) { \ greatest_info.teardown = cb; \ greatest_info.teardown_udata = udata; \ } \ \ static int greatest_string_equal_cb(const void *expd, const void *got, \ void *udata) { \ size_t *size = (size_t *)udata; \ return (size != NULL \ ? (0 == strncmp((const char *)expd, (const char *)got, *size)) \ : (0 == strcmp((const char *)expd, (const char *)got))); \ } \ \ static int greatest_string_printf_cb(const void *t, void *udata) { \ (void)udata; /* note: does not check \0 termination. */ \ return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ } \ \ greatest_type_info greatest_type_info_string = { \ greatest_string_equal_cb, greatest_string_printf_cb, \ }; \ \ static int greatest_memory_equal_cb(const void *expd, const void *got, \ void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ return (0 == memcmp(expd, got, env->size)); \ } \ \ /* Hexdump raw memory, with differences highlighted */ \ static int greatest_memory_printf_cb(const void *t, void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ const unsigned char *buf = (const unsigned char *)t; \ unsigned char diff_mark = ' '; \ FILE *out = GREATEST_STDOUT; \ size_t i, line_i, line_len = 0; \ int len = 0; /* format hexdump with differences highlighted */ \ for (i = 0; i < env->size; i+= line_len) { \ diff_mark = ' '; \ line_len = env->size - i; \ if (line_len > 16) { line_len = 16; } \ for (line_i = i; line_i < i + line_len; line_i++) { \ if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ } \ len += GREATEST_FPRINTF(out, "\n%04x %c ", \ (unsigned int)i, diff_mark); \ for (line_i = i; line_i < i + line_len; line_i++) { \ int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ len += GREATEST_FPRINTF(out, "%02x%c", \ buf[line_i], m ? ' ' : '<'); \ } \ for (line_i = 0; line_i < 16 - line_len; line_i++) { \ len += GREATEST_FPRINTF(out, " "); \ } \ GREATEST_FPRINTF(out, " "); \ for (line_i = i; line_i < i + line_len; line_i++) { \ unsigned char c = buf[line_i]; \ len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ } \ } \ len += GREATEST_FPRINTF(out, "\n"); \ return len; \ } \ \ void greatest_prng_init_first_pass(int id) { \ greatest_info.prng[id].random_order = 1; \ greatest_info.prng[id].count_run = 0; \ } \ \ int greatest_prng_init_second_pass(int id, unsigned long seed) { \ struct greatest_prng *p = &greatest_info.prng[id]; \ if (p->count == 0) { return 0; } \ p->count_ceil = p->count; \ for (p->m = 1; p->m < p->count; p->m <<= 1) {} \ p->state = seed & 0x1fffffff; /* only use lower 29 bits */ \ p->a = 4LU * p->state; /* to avoid overflow when */ \ p->a = (p->a ? p->a : 4) | 1; /* multiplied by 4 */ \ p->c = 2147483647; /* and so p->c ((2 ** 31) - 1) is */ \ p->initialized = 1; /* always relatively prime to p->a. */ \ fprintf(stderr, "init_second_pass: a %lu, c %lu, state %lu\n", \ p->a, p->c, p->state); \ return 1; \ } \ \ /* Step the pseudorandom number generator until its state reaches \ * another test ID between 0 and the test count. \ * This use a linear congruential pseudorandom number generator, \ * with the power-of-two ceiling of the test count as the modulus, the \ * masked seed as the multiplier, and a prime as the increment. For \ * each generated value < the test count, run the corresponding test. \ * This will visit all IDs 0 <= X < mod once before repeating, \ * with a starting position chosen based on the initial seed. \ * For details, see: Knuth, The Art of Computer Programming \ * Volume. 2, section 3.2.1. */ \ void greatest_prng_step(int id) { \ struct greatest_prng *p = &greatest_info.prng[id]; \ do { \ p->state = ((p->a * p->state) + p->c) & (p->m - 1); \ } while (p->state >= p->count_ceil); \ } \ \ void GREATEST_INIT(void) { \ /* Suppress unused function warning if features aren't used */ \ (void)greatest_run_suite; \ (void)greatest_parse_options; \ (void)greatest_prng_step; \ (void)greatest_prng_init_first_pass; \ (void)greatest_prng_init_second_pass; \ (void)greatest_set_test_suffix; \ \ memset(&greatest_info, 0, sizeof(greatest_info)); \ greatest_info.width = GREATEST_DEFAULT_WIDTH; \ GREATEST_SET_TIME(greatest_info.begin); \ } \ \ /* Report passes, failures, skipped tests, the number of \ * assertions, and the overall run time. */ \ void GREATEST_PRINT_REPORT(void) { \ if (!GREATEST_LIST_ONLY()) { \ update_counts_and_reset_suite(); \ GREATEST_SET_TIME(greatest_info.end); \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nTotal: %u test%s", \ greatest_info.tests_run, \ greatest_info.tests_run == 1 ? "" : "s"); \ GREATEST_CLOCK_DIFF(greatest_info.begin, \ greatest_info.end); \ GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ greatest_info.assertions, \ greatest_info.assertions == 1 ? "" : "s"); \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Pass: %u, fail: %u, skip: %u.\n", \ greatest_info.passed, \ greatest_info.failed, greatest_info.skipped); \ } \ } \ \ greatest_type_info greatest_type_info_memory = { \ greatest_memory_equal_cb, greatest_memory_printf_cb, \ }; \ \ greatest_run_info greatest_info /* Handle command-line arguments, etc. */ #define GREATEST_MAIN_BEGIN() \ do { \ GREATEST_INIT(); \ greatest_parse_options(argc, argv); \ } while (0) /* Report results, exit with exit status based on results. */ #define GREATEST_MAIN_END() \ do { \ GREATEST_PRINT_REPORT(); \ return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ } while (0) /* Make abbreviations without the GREATEST_ prefix for the * most commonly used symbols. */ #if GREATEST_USE_ABBREVS #define TEST GREATEST_TEST #define SUITE GREATEST_SUITE #define SUITE_EXTERN GREATEST_SUITE_EXTERN #define RUN_TEST GREATEST_RUN_TEST #define RUN_TEST1 GREATEST_RUN_TEST1 #define RUN_SUITE GREATEST_RUN_SUITE #define IGNORE_TEST GREATEST_IGNORE_TEST #define ASSERT GREATEST_ASSERT #define ASSERTm GREATEST_ASSERTm #define ASSERT_FALSE GREATEST_ASSERT_FALSE #define ASSERT_EQ GREATEST_ASSERT_EQ #define ASSERT_NEQ GREATEST_ASSERT_NEQ #define ASSERT_GT GREATEST_ASSERT_GT #define ASSERT_GTE GREATEST_ASSERT_GTE #define ASSERT_LT GREATEST_ASSERT_LT #define ASSERT_LTE GREATEST_ASSERT_LTE #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm #define ASSERT_EQm GREATEST_ASSERT_EQm #define ASSERT_NEQm GREATEST_ASSERT_NEQm #define ASSERT_GTm GREATEST_ASSERT_GTm #define ASSERT_GTEm GREATEST_ASSERT_GTEm #define ASSERT_LTm GREATEST_ASSERT_LTm #define ASSERT_LTEm GREATEST_ASSERT_LTEm #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm #define PASS GREATEST_PASS #define FAIL GREATEST_FAIL #define SKIP GREATEST_SKIP #define PASSm GREATEST_PASSm #define FAILm GREATEST_FAILm #define SKIPm GREATEST_SKIPm #define SET_SETUP GREATEST_SET_SETUP_CB #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB #define CHECK_CALL GREATEST_CHECK_CALL #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES #ifdef GREATEST_VA_ARGS #define RUN_TESTp GREATEST_RUN_TESTp #endif #if GREATEST_USE_LONGJMP #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm #endif #endif /* USE_ABBREVS */ #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) } #endif #endif ================================================ FILE: test/test_tinf.c ================================================ /* * tinf unit test * * Copyright (c) 2014-2022 Joergen Ibsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. If you use this * software in a product, an acknowledgment in the product * documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must * not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ #include "tinf.h" #include #include #include #include "greatest.h" #ifndef ARRAY_SIZE # define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #endif static const unsigned char robuffer[] = { 0 }; static unsigned char buffer[4096]; struct packed_data { unsigned int src_size; unsigned int depacked_size; const unsigned char data[32]; }; static const struct packed_data inflate_errors[] = { /* Unable to read first byte */ { 0, 1, { 0x42 } }, /* No next block after non-final block */ { 5, 1, { 0x00, 0x00, 0x00, 0xFF, 0xFF } }, /* Invalid block type 11 */ { 13, 1, { 0x07, 0xCA, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0x6B, 0x01, 0x00 } }, /* Uncompressed block incomplete */ { 4, 1, { 0x01, 0x00, 0x00, 0xFF } }, /* Uncompressed block inv length wrong */ { 5, 1, { 0x01, 0x00, 0x00, 0x00, 0x00 } }, /* Uncompressed block missing data */ { 5, 1, { 0x01, 0x01, 0x00, 0xFE, 0xFF } }, /* Uncompressed block writing one past end */ { 7, 1, { 0x01, 0x02, 0x00, 0xFD, 0xFF, 0x42, 0x42 } }, /* Fixed incomplete */ { 2, 1, { 0x63, 0x00 } }, /* Fixed reading one byte before start */ { 4, 4, { 0x63, 0x00, 0x42, 0x00 } }, /* Fixed literal writing one byte past end */ { 4, 1, { 0x63, 0x60, 0x00, 0x00 } }, /* Fixed match writing one byte past end */ { 4, 3, { 0x63, 0x00, 0x02, 0x00 } }, /* Fixed len > 285 */ { 4, 1024, { 0x63, 0x18, 0x03, 0x00 } }, /* Fixed dist > 29 */ { 4, 4, { 0x63, 0x00, 0x3E, 0x00 } }, /* Dynamic incomplete no HDIST */ { 1, 1, { 0x05 } }, /* Dynamic incomplete HCLEN */ { 2, 1, { 0x05, 0x00 } }, /* Dynamic incomplete code length code lengths */ { 4, 1, { 0x05, 0x40, 0x00, 0x04 } }, /* Dynamic code length code lengths all zero*/ { 6, 1, { 0x05, 0x0B, 0x00, 0x00, 0x00, 0x00 } }, /* Dynamic incomplete literal code lengths */ { 4, 1, { 0x05, 0x20, 0x00, 0x04 } }, /* Dynamic 256 has code length 0 */ { 13, 1, { 0x05, 0xCB, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xD7, 0x02, 0x00 } }, /* Dynamic only 256 available, but data contains 1 bit */ { 13, 1, { 0x05, 0xCA, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x7F, 0xEB, 0x00, 0x02 } }, /* Dynamic only one distance code, but compressed data contains 1 bit */ { 13, 4, { 0x0D, 0xC0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFC, 0xA9, 0x3F, 0x0F } }, /* Dynamic all distance codes zero, but compressed data contains match */ { 14, 4, { 0x0D, 0xCA, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFC, 0xA9, 0x1F, 0xC0, 0x02 } }, /* Dynamic only one code length code length, but compressed data contains 1 bit */ { 8, 4, { 0x05, 0x00, 0x80, 0xC0, 0xBF, 0x37, 0x00, 0x00 } }, /* Dynamic first code length code is copy prev length */ { 13, 1, { 0x05, 0xCA, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xF1, 0x87, 0x0E, 0x00 } }, /* Dynamic underfull code length in code length code (missing len 2 code) */ { 13, 1, { 0x05, 0xCA, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x7F, 0xEB, 0x00, 0x00 } }, /* Dynamic overfull code length in code length code (extra len 2 code) */ { 13, 1, { 0x05, 0xCA, 0x81, 0x00, 0x00, 0x00, 0x00, 0x82, 0x20, 0x7F, 0xEB, 0x00, 0x00 } }, /* Dynamic overfull code length in literal/length code (extra len 1 codes) */ { 15, 4, { 0x0D, 0xC3, 0x37, 0x01, 0x00, 0x00, 0x00, 0x80, 0x20, 0x46, 0xFF, 0xCE, 0xCA, 0x61, 0x01 } }, /* Dynamic underfull code length in distance code (missing len 2 code) */ { 14, 4, { 0x0D, 0xCE, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFD, 0xA9, 0xBB, 0x09, 0x1A } }, /* Dynamic overfull code length in distance code (extra len 2 code) */ { 15, 4, { 0x0D, 0xCE, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFD, 0xA9, 0xBB, 0x1F, 0xA0, 0x01 } }, /* Dynamic HLIT too large (30 = 287) */ { 15, 4, { 0xF5, 0xCB, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFC, 0xA9, 0x9F, 0x24, 0x00, 0x01 } }, /* Dynamic HDIST too large (30 = 31) */ { 15, 4, { 0xED, 0xDE, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFC, 0xA9, 0x5F, 0x24, 0x13, 0x01 } }, /* Dynamic number of literal/length codes too large (last repeat exceeds limit) */ { 15, 4, { 0x0D, 0xCB, 0x37, 0x01, 0x00, 0x00, 0x00, 0x80, 0x20, 0xFA, 0xA7, 0x56, 0x08, 0x60, 0x01 } } }; static const struct packed_data zlib_errors[] = { /* Too short (not enough room for 2 byte header and 4 byte trailer) */ { 5, 1, { 0x78, 0x9C, 0x63, 0x00, 0x00 } }, /* Too short, but last 4 bytes are valid Adler-32 */ { 8, 1, { 0x78, 0x9C, 0x63, 0x04, 0x00, 0x02, 0x00, 0x02 } }, /* Header checksum error */ { 9, 1, { 0x78, 0x9D, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01 } }, /* Method not deflate */ { 9, 1, { 0x74, 0x9D, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01 } }, /* Window size invalid */ { 9, 1, { 0x88, 0x98, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01 } }, /* Preset dictionary (not supported by tinf) */ { 13, 1, { 0x78, 0xBB, 0x00, 0x00, 0x00, 0x01, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01 } }, /* Adler-32 checksum does not match value in trailer */ { 9, 1, { 0x78, 0x9C, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, /* Decompression error (bad block type) */ { 9, 1, { 0x78, 0x9C, 0x67, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01 } } }; static const struct packed_data gzip_errors[] = { /* Too short (not enough room for 10 byte header and 8 byte trailer) */ { 17, 1, { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2 } }, /* Too short, but last 8 bytes are valid CRC32 and size */ { 19, 1, { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, /* Error in first id byte */ { 21, 1, { 0x1E, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 } }, /* Error in second id byte */ { 21, 1, { 0x1F, 0x8A, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 } }, /* Method not deflate */ { 21, 1, { 0x1F, 0x8B, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 } }, /* Reserved flag bit set */ { 21, 1, { 0x1F, 0x8B, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 } }, /* Header CRC16 error */ { 23, 1, { 0x1F, 0x8B, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x17, 0x9C, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 } }, /* Header CRC16 exceeds input size */ { 19, 1, { 0x1F, 0x8B, 0x08, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x00, 0x2E } }, /* Filename exceeds input size */ { 19, 1, { 0x1F, 0x8B, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 } }, /* Comment exceeds input size */ { 19, 1, { 0x1F, 0x8B, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 } }, /* Extra data exceeds input size */ { 19, 1, { 0x1F, 0x8B, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x08, 0x00, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37 } }, /* Not enough room for trailer after comment */ { 19, 1, { 0x1F, 0x8B, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x00 } }, /* Decompressed size does not match size in trailer */ { 21, 1, { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x02, 0x00, 0x00, 0x00 } }, /* CRC32 checksum does not match value in trailer */ { 21, 1, { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x01, 0xD2, 0x01, 0x00, 0x00, 0x00 } }, /* Decompression error (bad block type) */ { 21, 1, { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x67, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 } } }; /* tinflate */ TEST inflate_padding(void) { /* Empty buffer, fixed, 6 bits of padding in the second byte set to 1 */ static const unsigned char data[] = { 0x03, 0xFC }; unsigned int dlen = 0; int res; res = tinf_uncompress((void *) robuffer, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 0); PASS(); } TEST inflate_empty_no_literals(void) { /* Empty buffer, dynamic with 256 as only literal/length code * * You could argue that since the RFC only has an exception allowing * one symbol for the distance tree, the literal/length tree should * be complete. However gzip allows this. * * See also: https://github.com/madler/zlib/issues/75 */ static const unsigned char data[] = { 0x05, 0xCA, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0x6B, 0x01, 0x00 }; unsigned int dlen = 0; int res; res = tinf_uncompress((void *) robuffer, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 0); PASS(); } TEST inflate_huffman_only(void) { /* 256 zero bytes compressed using Huffman only (no match or distance codes) */ static const unsigned char data[] = { 0x05, 0xCA, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xD5, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; unsigned char out[256]; unsigned int dlen = ARRAY_SIZE(out); int res; size_t i; memset(out, 0xFF, ARRAY_SIZE(out)); res = tinf_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == ARRAY_SIZE(out)); for (i = 0; i < ARRAY_SIZE(out); ++i) { if (out[i]) { FAIL(); } } PASS(); } TEST inflate_rle(void) { /* 256 zero bytes compressed using RLE (only one distance code) */ static const unsigned char data[] = { 0xE5, 0xC0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFC, 0xA9, 0x07, 0x39, 0x73, 0x01 }; unsigned char out[256]; unsigned int dlen = ARRAY_SIZE(out); int res; size_t i; memset(out, 0xFF, ARRAY_SIZE(out)); res = tinf_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == ARRAY_SIZE(out)); for (i = 0; i < ARRAY_SIZE(out); ++i) { if (out[i]) { FAIL(); } } PASS(); } TEST inflate_max_matchlen(void) { /* 259 zero bytes compressed using literal/length code 285 (len 258) */ static const unsigned char data[] = { 0xED, 0xCC, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFC, 0xA9, 0x17, 0xB9, 0x00, 0x2C }; unsigned char out[259]; unsigned int dlen = ARRAY_SIZE(out); int res; size_t i; memset(out, 0xFF, ARRAY_SIZE(out)); res = tinf_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == ARRAY_SIZE(out)); for (i = 0; i < ARRAY_SIZE(out); ++i) { if (out[i]) { FAIL(); } } PASS(); } TEST inflate_max_matchlen_alt(void) { /* 259 zero bytes compressed using literal/length code 284 + 31 (len 258) * * Technically, this is outside the range specified in the RFC, but * gzip allows it. * * See also: https://github.com/madler/zlib/issues/75 */ static const unsigned char data[] = { 0xE5, 0xCC, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA0, 0xFC, 0xA9, 0x07, 0xB9, 0x00, 0xFC, 0x05 }; unsigned char out[259]; unsigned int dlen = ARRAY_SIZE(out); int res; size_t i; memset(out, 0xFF, ARRAY_SIZE(out)); res = tinf_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == ARRAY_SIZE(out)); for (i = 0; i < ARRAY_SIZE(out); ++i) { if (out[i]) { FAIL(); } } PASS(); } TEST inflate_max_matchdist(void) { /* A match of length 3 with a distance of 32768 */ static const unsigned char data[] = { 0xED, 0xDD, 0x01, 0x01, 0x00, 0x00, 0x08, 0x02, 0x20, 0xED, 0xFF, 0xE8, 0xFA, 0x11, 0x1C, 0x61, 0x9A, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFE, 0xFF, 0x05 }; unsigned char out[32771]; unsigned int dlen = ARRAY_SIZE(out); int res; size_t i; memset(out, 0xFF, ARRAY_SIZE(out)); res = tinf_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == ARRAY_SIZE(out)); ASSERT(out[0] == 2 && out[1] == 1 && out[2] == 0); for (i = 3; i < ARRAY_SIZE(out) - 3; ++i) { if (out[i]) { FAIL(); } } ASSERT(out[ARRAY_SIZE(out) - 3] == 2); ASSERT(out[ARRAY_SIZE(out) - 2] == 1); ASSERT(out[ARRAY_SIZE(out) - 1] == 0); PASS(); } TEST inflate_code_length_codes(void) { /* 4 zero bytes compressed, code length codes include codes 16, 17, and 18 */ static const unsigned char data[] = { 0x0D, 0xC3, 0x37, 0x01, 0x00, 0x00, 0x00, 0x80, 0x20, 0xFA, 0x77, 0x1E, 0xCA, 0x61, 0x01 }; unsigned char out[4]; unsigned int dlen = ARRAY_SIZE(out); int res; size_t i; memset(out, 0xFF, ARRAY_SIZE(out)); res = tinf_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == ARRAY_SIZE(out)); for (i = 0; i < ARRAY_SIZE(out); ++i) { if (out[i]) { FAIL(); } } PASS(); } TEST inflate_max_codelen(void) { /* Use all codeword lengths including 15 */ static const unsigned char data[] = { 0x05, 0xEA, 0x01, 0x82, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x12, 0x8B, 0x9A, 0x47, 0x56, 0xCF, 0xDE, 0xFF, 0x9F, 0x7B, 0x0F, 0xD0, 0xEE, 0x7D, 0xBF, 0xBF, 0x7F, 0xFF, 0xFD, 0xEF, 0xFF, 0xFE, 0xDF, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0x03 }; unsigned char out[15]; unsigned int dlen = ARRAY_SIZE(out); int res; size_t i; memset(out, 0xFF, ARRAY_SIZE(out)); res = tinf_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == ARRAY_SIZE(out)); for (i = 0; i < ARRAY_SIZE(out); ++i) { if (out[i] != i) { FAIL(); } } PASS(); } /* Test tinf_uncompress on random data */ TEST inflate_random(void) { unsigned char data[256]; unsigned int len; for (len = 1; len < ARRAY_SIZE(data); ++len) { unsigned int dlen = ARRAY_SIZE(buffer); size_t i; for (i = 0; i < len; ++i) { data[i] = (unsigned char) rand(); } /* Make sure btype is valid */ if ((data[0] & 0x06) == 0x06) { data[0] &= (rand() > RAND_MAX / 2) ? ~0x02 : ~0x04; } tinf_uncompress(buffer, &dlen, data, len); } PASS(); } /* Test tinf_uncompress on compressed data with errors */ TEST inflate_error_case(const void *closure) { const struct packed_data *pd = (const struct packed_data *) closure; int res; unsigned int size = pd->depacked_size; res = tinf_uncompress(buffer, &size, pd->data, pd->src_size); ASSERT(res != TINF_OK); PASS(); } SUITE(tinflate) { char suffix[32]; size_t i; RUN_TEST(inflate_padding); RUN_TEST(inflate_empty_no_literals); RUN_TEST(inflate_huffman_only); RUN_TEST(inflate_rle); RUN_TEST(inflate_max_matchlen); RUN_TEST(inflate_max_matchlen_alt); RUN_TEST(inflate_max_matchdist); RUN_TEST(inflate_code_length_codes); RUN_TEST(inflate_max_codelen); RUN_TEST(inflate_random); for (i = 0; i < ARRAY_SIZE(inflate_errors); ++i) { sprintf(suffix, "%d", (int) i); greatest_set_test_suffix(suffix); RUN_TEST1(inflate_error_case, &inflate_errors[i]); } } /* tinfzlib */ TEST zlib_empty_raw(void) { /* Empty buffer, uncompressed */ static const unsigned char data[] = { 0x78, 0x9C, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01 }; unsigned int dlen = 0; int res; res = tinf_zlib_uncompress((void *) robuffer, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 0); PASS(); } TEST zlib_empty_fixed(void) { /* Empty buffer, fixed Huffman */ static const unsigned char data[] = { 0x78, 0x9C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01 }; unsigned int dlen = 0; int res; res = tinf_zlib_uncompress((void *) robuffer, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 0); PASS(); } TEST zlib_empty_dynamic(void) { /* Empty buffer, dynamic Huffman */ static const unsigned char data[] = { 0x78, 0x9C, 0x05, 0xC1, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xD5, 0x08, 0x00, 0x00, 0x00, 0x01 }; unsigned int dlen = 0; int res; res = tinf_zlib_uncompress((void *) robuffer, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 0); PASS(); } TEST zlib_onebyte_raw(void) { /* One byte 00, uncompressed */ static const unsigned char data[] = { 0x78, 0x9C, 0x01, 0x01, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x01 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_zlib_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST zlib_onebyte_fixed(void) { /* One byte 00, fixed Huffman */ static const unsigned char data[] = { 0x78, 0x9C, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_zlib_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST zlib_onebyte_dynamic(void) { /* One byte 00, dynamic Huffman */ static const unsigned char data[] = { 0x78, 0x9C, 0x05, 0xC1, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xD5, 0x10, 0x00, 0x01, 0x00, 0x01 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_zlib_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST zlib_zeroes(void) { /* 256 zero bytes, to test unrolling in Adler-32 */ static const unsigned char data[] = { 0x78, 0x9C, 0x63, 0x60, 0x18, 0xD9, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01 }; unsigned char out[256]; unsigned int dlen = ARRAY_SIZE(out); int res; size_t i; memset(out, 0xFF, ARRAY_SIZE(out)); res = tinf_zlib_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == ARRAY_SIZE(out)); for (i = 0; i < ARRAY_SIZE(out); ++i) { if (out[i]) { FAIL(); } } PASS(); } /* Test tinf_zlib_uncompress on compressed data with errors */ TEST zlib_error_case(const void *closure) { const struct packed_data *pd = (const struct packed_data *) closure; int res; unsigned int size = pd->depacked_size; res = tinf_zlib_uncompress(buffer, &size, pd->data, pd->src_size); ASSERT(res != TINF_OK); PASS(); } SUITE(tinfzlib) { char suffix[32]; size_t i; RUN_TEST(zlib_empty_raw); RUN_TEST(zlib_empty_fixed); RUN_TEST(zlib_empty_dynamic); RUN_TEST(zlib_onebyte_raw); RUN_TEST(zlib_onebyte_fixed); RUN_TEST(zlib_onebyte_dynamic); RUN_TEST(zlib_zeroes); for (i = 0; i < ARRAY_SIZE(zlib_errors); ++i) { sprintf(suffix, "%d", (int) i); greatest_set_test_suffix(suffix); RUN_TEST1(zlib_error_case, &zlib_errors[i]); } } /* tinfgzip */ TEST gzip_empty_raw(void) { /* Empty buffer, uncompressed */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; unsigned int dlen = 0; int res; res = tinf_gzip_uncompress((void *) robuffer, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 0); PASS(); } TEST gzip_empty_fixed(void) { /* Empty buffer, fixed Huffman */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; unsigned int dlen = 0; int res; res = tinf_gzip_uncompress((void *) robuffer, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 0); PASS(); } TEST gzip_empty_dynamic(void) { /* Empty buffer, dynamic Huffman */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x05, 0xC1, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xD5, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; unsigned int dlen = 0; int res; res = tinf_gzip_uncompress((void *) robuffer, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 0); PASS(); } TEST gzip_onebyte_raw(void) { /* One byte 00, uncompressed */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x01, 0x01, 0x00, 0xFE, 0xFF, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_gzip_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST gzip_onebyte_fixed(void) { /* One byte 00, fixed Huffman */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x63, 0x00, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_gzip_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST gzip_onebyte_dynamic(void) { /* One byte 00, dynamic Huffman */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x05, 0xC1, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xD5, 0x10, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_gzip_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST gzip_fhcrc(void) { /* One byte 00, uncompressed, fhcrc */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x17, 0x9D, 0x01, 0x01, 0x00, 0xFE, 0xFF, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_gzip_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST gzip_fextra(void) { /* One byte 00, uncompressed, fextra */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x04, 0x00, 0x64, 0x61, 0x74, 0x61, 0x01, 0x01, 0x00, 0xFE, 0xFF, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_gzip_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST gzip_fname(void) { /* One byte 00, uncompressed, fname */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x66, 0x6F, 0x6F, 0x2E, 0x63, 0x00, 0x01, 0x01, 0x00, 0xFE, 0xFF, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_gzip_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } TEST gzip_fcomment(void) { /* One byte 00, uncompressed, fcomment */ static const unsigned char data[] = { 0x1F, 0x8B, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x00, 0x01, 0x01, 0x00, 0xFE, 0xFF, 0x00, 0x8D, 0xEF, 0x02, 0xD2, 0x01, 0x00, 0x00, 0x00 }; unsigned char out[] = { 0xFF }; unsigned int dlen = 1; int res; res = tinf_gzip_uncompress(out, &dlen, data, ARRAY_SIZE(data)); ASSERT(res == TINF_OK && dlen == 1 && out[0] == 0); PASS(); } /* Test tinf_gzip_uncompress on compressed data with errors */ TEST gzip_error_case(const void *closure) { const struct packed_data *pd = (const struct packed_data *) closure; int res; unsigned int size = pd->depacked_size; res = tinf_gzip_uncompress(buffer, &size, pd->data, pd->src_size); ASSERT(res != TINF_OK); PASS(); } SUITE(tinfgzip) { char suffix[32]; size_t i; RUN_TEST(gzip_empty_raw); RUN_TEST(gzip_empty_fixed); RUN_TEST(gzip_empty_dynamic); RUN_TEST(gzip_onebyte_raw); RUN_TEST(gzip_onebyte_fixed); RUN_TEST(gzip_onebyte_dynamic); RUN_TEST(gzip_fhcrc); RUN_TEST(gzip_fextra); RUN_TEST(gzip_fname); RUN_TEST(gzip_fcomment); for (i = 0; i < ARRAY_SIZE(gzip_errors); ++i) { sprintf(suffix, "%d", (int) i); greatest_set_test_suffix(suffix); RUN_TEST1(gzip_error_case, &gzip_errors[i]); } } GREATEST_MAIN_DEFS(); int main(int argc, char *argv[]) { GREATEST_MAIN_BEGIN(); srand(time(NULL)); tinf_init(); RUN_SUITE(tinflate); RUN_SUITE(tinfzlib); RUN_SUITE(tinfgzip); GREATEST_MAIN_END(); } ================================================ FILE: tools/gengztest.py ================================================ #!/usr/bin/env python3 # Copyright (c) 2014-2019 Joergen Ibsen # # 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. """ Gzip test file generator. Generates gzip test files with specified fields in the header. """ import argparse import struct import zlib import textwrap def write_gzip(f, args): """Write gzip data to file f.""" # gzip header data = bytearray.fromhex('1f 8b 08 00 00 00 00 00 02 0b') flags = 0 if args.s: flags |= 2 if args.extra: flags |= 4 edata = args.extra.encode('ascii') data.extend(struct.pack('I', args.preset_dict)) flg |= 31 - ((256 * cmf + flg) % 31) data[0] = cmf data[1] = flg if args.data == 'empty': if args.method == 'raw': # empty buffer, uncompressed data.extend(bytes.fromhex('01 00 00 ff ff')) elif args.method == 'fixed': # empty buffer, fixed Huffman data.extend(bytes.fromhex('03 00')) elif args.method == 'dynamic': # empty buffer, dynamic Huffman data.extend(bytes.fromhex('05 c1 81 00 00 00 00 00 10 ff d5 08')) # Adler-32 of empty buffer data.extend(struct.pack('>I', 1)) elif args.data == 'byte00': if args.method == 'raw': # one byte 00, uncompressed data.extend(bytes.fromhex('01 01 00 fe ff 00')) elif args.method == 'fixed': # one byte 00, fixed Huffman data.extend(bytes.fromhex('63 00 00')) elif args.method == 'dynamic': # one byte 00, dynamic Huffman data.extend(bytes.fromhex('05 c1 81 00 00 00 00 00 10 ff d5 10')) # Adler-32 of one byte 00 data.extend(struct.pack('>I', zlib.adler32(b'\x00'))) if args.i: s = textwrap.fill(', '.join('0x{:02X}'.format(b) for b in data)) f.write(s.encode('utf-8')) else: f.write(data) if __name__ == '__main__': parser = argparse.ArgumentParser(description='zlib test file generator.') parser.add_argument('-d', '--data', type=str, choices=['empty', 'byte00'], default='empty', help='compressed data') parser.add_argument('-m', '--method', type=str, choices=['raw', 'fixed', 'dynamic'], default='fixed', help='compression method') parser.add_argument('-l', '--level', type=int, choices=range(0, 4), default=2, help='compression level') parser.add_argument('-w', '--window_size', type=int, choices=range(0, 8), default=7, help='window size') parser.add_argument('-p', '--preset_dict', type=int, help='preset dictionary Adler-32') parser.add_argument('-i', action='store_true', help='generate include file') parser.add_argument('outfile', type=argparse.FileType('wb'), help='output file') args = parser.parse_args() write_zlib(args.outfile, args) ================================================ FILE: tools/mkzdata.c ================================================ /* * mkzdata - generate deflate compressed data testcases * * Copyright (c) 2014-2019 Joergen Ibsen * * 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. */ #include "tinf.h" #include #include #include #include struct lsb_bitwriter { unsigned char *next_out; uint32_t tag; int bitcount; }; static void lbw_init(struct lsb_bitwriter *lbw, unsigned char *dst) { lbw->next_out = dst; lbw->tag = 0; lbw->bitcount = 0; } static unsigned char* lbw_finalize(struct lsb_bitwriter *lbw) { /* Write bytes until no bits left in tag */ while (lbw->bitcount > 0) { *lbw->next_out++ = lbw->tag; lbw->tag >>= 8; lbw->bitcount -= 8; } return lbw->next_out; } static void lbw_flush(struct lsb_bitwriter *lbw, int num) { assert(num >= 0 && num <= 32); /* Write bytes until at least num bits free */ while (lbw->bitcount > 32 - num) { *lbw->next_out++ = lbw->tag; lbw->tag >>= 8; lbw->bitcount -= 8; } assert(lbw->bitcount >= 0); } static void lbw_putbits_no_flush(struct lsb_bitwriter *lbw, uint32_t bits, int num) { assert(num >= 0 && num <= 32 - lbw->bitcount); assert((bits & (~0ULL << num)) == 0); /* Add bits to tag */ lbw->tag |= bits << lbw->bitcount; lbw->bitcount += num; } static void lbw_putbits(struct lsb_bitwriter *lbw, uint32_t bits, int num) { lbw_flush(lbw, num); lbw_putbits_no_flush(lbw, bits, num); } static void lbw_putbits_rev(struct lsb_bitwriter *lbw, uint32_t bits, int num) { lbw_flush(lbw, num); while (num-- > 0) { lbw_putbits_no_flush(lbw, (bits >> num) & 1, 1); } } // 256 00 bytes compressed with Z_RLE (one distance code) void write_256_rle(struct lsb_bitwriter *lbw) { // bfinal lbw_putbits(lbw, 1, 1); // btype lbw_putbits(lbw, 2, 2); // hlit lbw_putbits(lbw, 28, 5); // hdist lbw_putbits(lbw, 0, 5); // hclen lbw_putbits(lbw, 14, 4); lbw_putbits(lbw, 0, 3); // 16 lbw_putbits(lbw, 0, 3); // 17 lbw_putbits(lbw, 1, 3); // 18 lbw_putbits(lbw, 0, 3); // 0 lbw_putbits(lbw, 0, 3); // 8 lbw_putbits(lbw, 0, 3); // 7 lbw_putbits(lbw, 0, 3); // 9 lbw_putbits(lbw, 0, 3); // 6 lbw_putbits(lbw, 0, 3); // 10 lbw_putbits(lbw, 0, 3); // 5 lbw_putbits(lbw, 0, 3); // 11 lbw_putbits(lbw, 0, 3); // 4 lbw_putbits(lbw, 0, 3); // 12 lbw_putbits(lbw, 0, 3); // 3 lbw_putbits(lbw, 0, 3); // 13 lbw_putbits(lbw, 2, 3); // 2 lbw_putbits(lbw, 0, 3); // 14 lbw_putbits(lbw, 2, 3); // 1 // code lengths for literal/length lbw_putbits_rev(lbw, 2, 2); // 0 has len 1 lbw_putbits_rev(lbw, 0, 1); // repeat len 0 for 138 times lbw_putbits(lbw, 127, 7); lbw_putbits_rev(lbw, 0, 1); // repeat len 0 for 117 times lbw_putbits(lbw, 106, 7); lbw_putbits_rev(lbw, 3, 2); // 256 has len 2 lbw_putbits_rev(lbw, 0, 1); // repeat len 0 for 27 times lbw_putbits(lbw, 16, 7); lbw_putbits_rev(lbw, 3, 2); // 284 has len 2 // code lengths for distance lbw_putbits_rev(lbw, 2, 2); // 1 has len 1 // compressed data lbw_putbits_rev(lbw, 0, 1); // 00 byte lbw_putbits_rev(lbw, 3, 2); // match len 255 lbw_putbits(lbw, 28, 5); lbw_putbits_rev(lbw, 0, 1); // distance 1 // end of block lbw_putbits_rev(lbw, 2, 2); // 256 = EOB } // 256 00 bytes compressed with Z_HUFFMAN_ONLY (no distance codes) void write_256_huffman(struct lsb_bitwriter *lbw) { // bfinal lbw_putbits(lbw, 1, 1); // btype lbw_putbits(lbw, 2, 2); // hlit lbw_putbits(lbw, 0, 5); // hdist lbw_putbits(lbw, 10, 5); // hclen lbw_putbits(lbw, 14, 4); lbw_putbits(lbw, 0, 3); // 16 lbw_putbits(lbw, 0, 3); // 17 lbw_putbits(lbw, 1, 3); // 18 lbw_putbits(lbw, 0, 3); // 0 lbw_putbits(lbw, 0, 3); // 8 lbw_putbits(lbw, 0, 3); // 7 lbw_putbits(lbw, 0, 3); // 9 lbw_putbits(lbw, 0, 3); // 6 lbw_putbits(lbw, 0, 3); // 10 lbw_putbits(lbw, 0, 3); // 5 lbw_putbits(lbw, 0, 3); // 11 lbw_putbits(lbw, 0, 3); // 4 lbw_putbits(lbw, 0, 3); // 12 lbw_putbits(lbw, 0, 3); // 3 lbw_putbits(lbw, 0, 3); // 13 lbw_putbits(lbw, 0, 3); // 2 lbw_putbits(lbw, 0, 3); // 14 lbw_putbits(lbw, 1, 3); // 1 // code lengths for literal/length lbw_putbits_rev(lbw, 0, 1); // 0 has len 1 lbw_putbits_rev(lbw, 1, 1); // repeat len 0 for 138 times lbw_putbits(lbw, 127, 7); lbw_putbits_rev(lbw, 1, 1); // repeat len 0 for 117 times lbw_putbits(lbw, 106, 7); lbw_putbits_rev(lbw, 0, 1); // 256 has len 1 // code lengths for distance lbw_putbits_rev(lbw, 1, 1); // repeat len 0 for 11 times lbw_putbits(lbw, 0, 7); // compressed data for (int i = 0; i < 256; ++i) { lbw_putbits_rev(lbw, 0, 1); // 00 byte } // end of block lbw_putbits_rev(lbw, 1, 1); // 256 = EOB } // empty with no literal symbols and no distance codes (only 256 has len 1) void write_no_lit(struct lsb_bitwriter *lbw) { // bfinal lbw_putbits(lbw, 1, 1); // btype lbw_putbits(lbw, 2, 2); // hlit lbw_putbits(lbw, 0, 5); // hdist lbw_putbits(lbw, 10, 5); // hclen lbw_putbits(lbw, 14, 4); lbw_putbits(lbw, 0, 3); // 16 lbw_putbits(lbw, 0, 3); // 17 lbw_putbits(lbw, 1, 3); // 18 lbw_putbits(lbw, 0, 3); // 0 lbw_putbits(lbw, 0, 3); // 8 lbw_putbits(lbw, 0, 3); // 7 lbw_putbits(lbw, 0, 3); // 9 lbw_putbits(lbw, 0, 3); // 6 lbw_putbits(lbw, 0, 3); // 10 lbw_putbits(lbw, 0, 3); // 5 lbw_putbits(lbw, 0, 3); // 11 lbw_putbits(lbw, 0, 3); // 4 lbw_putbits(lbw, 0, 3); // 12 lbw_putbits(lbw, 0, 3); // 3 lbw_putbits(lbw, 0, 3); // 13 lbw_putbits(lbw, 0, 3); // 2 lbw_putbits(lbw, 0, 3); // 14 lbw_putbits(lbw, 1, 3); // 1 // code lengths for literal/length lbw_putbits_rev(lbw, 1, 1); // repeat len 0 for 138 times lbw_putbits(lbw, 127, 7); lbw_putbits_rev(lbw, 1, 1); // repeat len 0 for 118 times lbw_putbits(lbw, 107, 7); lbw_putbits_rev(lbw, 0, 1); // 256 has len 1 // code lengths for distance lbw_putbits_rev(lbw, 1, 1); // repeat len 0 for 11 times lbw_putbits(lbw, 0, 7); // no compressed data // end of block lbw_putbits_rev(lbw, 0, 1); // 256 = EOB } // copy with max distance 32768 void write_max_dist(struct lsb_bitwriter *lbw) { // bfinal lbw_putbits(lbw, 1, 1); // btype lbw_putbits(lbw, 2, 2); // hlit lbw_putbits(lbw, 286 - 257, 5); // hdist lbw_putbits(lbw, 30 - 1, 5); // hclen lbw_putbits(lbw, 14, 4); lbw_putbits(lbw, 0, 3); // 16 lbw_putbits(lbw, 0, 3); // 17 lbw_putbits(lbw, 2, 3); // 18 lbw_putbits(lbw, 0, 3); // 0 lbw_putbits(lbw, 0, 3); // 8 lbw_putbits(lbw, 0, 3); // 7 lbw_putbits(lbw, 0, 3); // 9 lbw_putbits(lbw, 0, 3); // 6 lbw_putbits(lbw, 0, 3); // 10 lbw_putbits(lbw, 0, 3); // 5 lbw_putbits(lbw, 0, 3); // 11 lbw_putbits(lbw, 2, 3); // 4 lbw_putbits(lbw, 0, 3); // 12 lbw_putbits(lbw, 2, 3); // 3 lbw_putbits(lbw, 0, 3); // 13 lbw_putbits(lbw, 0, 3); // 2 lbw_putbits(lbw, 0, 3); // 14 lbw_putbits(lbw, 2, 3); // 1 // code lengths for literal/length lbw_putbits_rev(lbw, 1, 2); // 0 has len 3 lbw_putbits_rev(lbw, 1, 2); // 1 has len 3 lbw_putbits_rev(lbw, 2, 2); // 2 has len 4 lbw_putbits_rev(lbw, 3, 2); // repeat len 0 for 138 times lbw_putbits(lbw, 127, 7); lbw_putbits_rev(lbw, 3, 2); // repeat len 0 for 115 times lbw_putbits(lbw, 104, 7); lbw_putbits_rev(lbw, 2, 2); // 256 has len 4 lbw_putbits_rev(lbw, 2, 2); // 257 has len 4 lbw_putbits_rev(lbw, 3, 2); // repeat len 0 for 26 times lbw_putbits(lbw, 15, 7); lbw_putbits_rev(lbw, 2, 2); // 284 has len 4 lbw_putbits_rev(lbw, 0, 2); // 285 has len 1 // code lengths for distance lbw_putbits_rev(lbw, 0, 2); // 0 has len 1 lbw_putbits_rev(lbw, 3, 2); // repeat len 0 for 28 times lbw_putbits(lbw, 17, 7); lbw_putbits_rev(lbw, 0, 2); // 29 has len 1 // no compressed data lbw_putbits_rev(lbw, 12, 4); // literal 02 lbw_putbits_rev(lbw, 5, 3); // literal 01 lbw_putbits_rev(lbw, 4, 3); // literal 00 lbw_putbits_rev(lbw, 15, 4); // 284 = copy len 257 lbw_putbits(lbw, 30, 5); lbw_putbits_rev(lbw, 0, 1); // distance 1 for (int i = 0; i < 126; ++i) { lbw_putbits_rev(lbw, 0, 1); // 285 = copy len 258 lbw_putbits_rev(lbw, 0, 1); // distance 1 } lbw_putbits_rev(lbw, 14, 4); // 257 = copy len 3 lbw_putbits_rev(lbw, 1, 1); // distance 32768 lbw_putbits(lbw, 8191, 13); // end of block lbw_putbits_rev(lbw, 13, 4); // 256 = EOB } // Use length 15 codeword void write_max_codelen(struct lsb_bitwriter *lbw) { // bfinal lbw_putbits(lbw, 1, 1); // btype lbw_putbits(lbw, 2, 2); // hlit lbw_putbits(lbw, 0, 5); // hdist lbw_putbits(lbw, 10, 5); // hclen lbw_putbits(lbw, 15, 4); lbw_putbits(lbw, 0, 3); // 16 lbw_putbits(lbw, 0, 3); // 17 lbw_putbits(lbw, 4, 3); // 18 lbw_putbits(lbw, 0, 3); // 0 lbw_putbits(lbw, 4, 3); // 8 lbw_putbits(lbw, 4, 3); // 7 lbw_putbits(lbw, 4, 3); // 9 lbw_putbits(lbw, 4, 3); // 6 lbw_putbits(lbw, 4, 3); // 10 lbw_putbits(lbw, 4, 3); // 5 lbw_putbits(lbw, 4, 3); // 11 lbw_putbits(lbw, 4, 3); // 4 lbw_putbits(lbw, 4, 3); // 12 lbw_putbits(lbw, 4, 3); // 3 lbw_putbits(lbw, 4, 3); // 13 lbw_putbits(lbw, 4, 3); // 2 lbw_putbits(lbw, 4, 3); // 14 lbw_putbits(lbw, 4, 3); // 1 lbw_putbits(lbw, 4, 3); // 15 // code lengths for literal/length lbw_putbits_rev(lbw, 0, 4); // 0 has len 1 lbw_putbits_rev(lbw, 1, 4); // 1 has len 2 lbw_putbits_rev(lbw, 2, 4); // 2 has len 3 lbw_putbits_rev(lbw, 3, 4); // 3 has len 4 lbw_putbits_rev(lbw, 4, 4); // 4 has len 5 lbw_putbits_rev(lbw, 5, 4); // 5 has len 6 lbw_putbits_rev(lbw, 6, 4); // 6 has len 7 lbw_putbits_rev(lbw, 7, 4); // 7 has len 8 lbw_putbits_rev(lbw, 8, 4); // 8 has len 9 lbw_putbits_rev(lbw, 9, 4); // 9 has len 10 lbw_putbits_rev(lbw, 10, 4); // 10 has len 11 lbw_putbits_rev(lbw, 11, 4); // 11 has len 12 lbw_putbits_rev(lbw, 12, 4); // 12 has len 13 lbw_putbits_rev(lbw, 13, 4); // 13 has len 14 lbw_putbits_rev(lbw, 14, 4); // 14 has len 15 lbw_putbits_rev(lbw, 15, 4); // repeat len 0 for 138 times lbw_putbits(lbw, 127, 7); lbw_putbits_rev(lbw, 15, 4); // repeat len 0 for 103 times lbw_putbits(lbw, 92, 7); lbw_putbits_rev(lbw, 14, 4); // 256 has len 15 // code lengths for distance lbw_putbits_rev(lbw, 15, 4); // repeat len 0 for 11 times lbw_putbits(lbw, 0, 7); // compressed data lbw_putbits_rev(lbw, 0, 1); // literal 0 lbw_putbits_rev(lbw, 2, 2); // literal 1 lbw_putbits_rev(lbw, 6, 3); // literal 2 lbw_putbits_rev(lbw, 14, 4); // literal 3 lbw_putbits_rev(lbw, 30, 5); // literal 4 lbw_putbits_rev(lbw, 62, 6); // literal 5 lbw_putbits_rev(lbw, 126, 7); // literal 6 lbw_putbits_rev(lbw, 254, 8); // literal 7 lbw_putbits_rev(lbw, 510, 9); // literal 8 lbw_putbits_rev(lbw, 1022, 10); // literal 9 lbw_putbits_rev(lbw, 2046, 11); // literal 10 lbw_putbits_rev(lbw, 4094, 12); // literal 11 lbw_putbits_rev(lbw, 8190, 13); // literal 12 lbw_putbits_rev(lbw, 16382, 14); // literal 13 lbw_putbits_rev(lbw, 32766, 15); // literal 14 // end of block lbw_putbits_rev(lbw, 32767, 15); // 256 = EOB } unsigned char buffer[64 * 1024]; int main(int argc, char *argv[]) { if (argc > 2) { fputs("syntax: mkzdata FILE\n", stderr); return EXIT_FAILURE; } const unsigned char org_data[1024] = {0}; unsigned char data[4096]; struct lsb_bitwriter lbw; lbw_init(&lbw, &data[0]); write_max_codelen(&lbw); uint32_t size = lbw_finalize(&lbw) - &data[0]; for (int i = 0; i < size; ++i) { if (i > 0) { fputs(", ", stdout); } printf("0x%02X", data[i]); } printf("\n"); if (argc > 1) { FILE *fout = fopen(argv[1], "wb"); const unsigned char gzip_header[10] = { 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B }; fwrite(gzip_header, 1, sizeof(gzip_header), fout); fwrite(data, 1, size, fout); unsigned int dsize = sizeof(buffer); int res = tinf_uncompress(buffer, &dsize, data, size); if (res != TINF_OK) { fputs("mkzdata: decompression error\n", stderr); return EXIT_FAILURE; } // Note: only works on little-endian uint32_t crc = tinf_crc32(&buffer[0], dsize); fwrite(&crc, sizeof(crc), 1, fout); uint32_t org_size = dsize; fwrite(&org_size, sizeof(org_size), 1, fout); fclose(fout); } return 0; }