pax_global_header00006660000000000000000000000064126154325070014517gustar00rootroot0000000000000052 comment=b9ac05e912e1184b55c5d815b280486de10f331f heatshrink-0.4.1/000077500000000000000000000000001261543250700136615ustar00rootroot00000000000000heatshrink-0.4.1/.gitignore000066400000000000000000000001571261543250700156540ustar00rootroot00000000000000heatshrink test_heatshrink_dynamic test_heatshrink_static *.o *.od *.os *.a *.core *.dSYM *.exe benchmark_out/ heatshrink-0.4.1/.travis.yml000066400000000000000000000000761261543250700157750ustar00rootroot00000000000000language: c compiler: - clang - gcc script: make ci heatshrink-0.4.1/CONTRIBUTING.md000066400000000000000000000066101261543250700161150ustar00rootroot00000000000000# Contributing to heatshrink Thanks for taking time to contribute to heatshrink! Some issues may be tagged with `beginner` in the issue tracker, those should be particularly approachable. Please send patches or pull requests against the `develop` branch. Changes need to be carefully checked for reverse compatibility before merging to `master`, since heatshrink is running on devices that may not be easily recalled and updated. Sending changes via patch or pull request acknowledges that you are willing and able to contribute it under this project's license. (Please don't contribute code you aren't legally able to share.) ## Documentation Improvements to the documentation are welcome. So are requests for clarification -- if the docs are unclear or misleading, that's a potential source of bugs. ## Embedded & Portability Constraints heatshrink primarily targets embedded / real-time / memory-constrained systems, so enhancements that significantly increase memory or code space (ROM) requirements are probably out of scope. Changes that improve portability are welcome, and feedback from running on different embedded platforms is appreciated. ## Versioning & Compatibility The versioning format is MAJOR.MINOR.PATCH. Performance improvements or minor bug fixes that do not break compatibility with past releases lead to patch version increases. API changes that do not break compatibility lead to minor version increases and reset the patch version, and changes that do break compatibility lead to a major version increase. Since heatshrink's compression and decompression sides may be used and updated independently, any change to the encoder that cannot be correctly decoded by earlier releases (or vice versa) is considered a breaking change. Changes to the encoder that lead to different output that earlier decoder releases handle correctly (such as pattern detection improvements) are *not* breaking changes. Essentially, improvements to the compression process that older releases can't decode correctly will need to wait until the next major release. ## LZSS Algorithm heatshrink uses the [Lempel-Ziv-Storer-Szymanski][LZSS] algorithm for compression, with a few important implementation details: 1. The compression and decompression state machines have been designed to run incrementally - processing can work a few bytes at a time, suspending and resuming as additional data / buffer space becomes available. 2. The optional [indexing technique][index] used to speed up compression is unique to heatshrink, as far as I know. 3. In general, implementation trade-offs have favored low memory usage. [index]: http://spin.atomicobject.com/2014/01/13/lightweight-indexing-for-embedded-systems/ [LZSS]: http://en.wikipedia.org/wiki/Lempel-Ziv-Storer-Szymanski ## Testing The unit tests are based on [greatest][g], with additional property-based tests using [theft][t] (which are currently not built by default). greatest tests are preferred for specific new functionality and for regression tests, while theft tests are preferred for integration tests (e.g. "for any input, compressing and uncompressing it should match the original"). Bugs found by theft make for great regression tests. Contributors are encouraged to add tests for any new functionality, and in particular to add regression tests for any bugs found. [g]: https://github.com/silentbicycle/greatest [t]: https://github.com/silentbicycle/theft heatshrink-0.4.1/LICENSE000066400000000000000000000014251261543250700146700ustar00rootroot00000000000000Copyright (c) 2013-2015, Scott Vokes All rights reserved. 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. heatshrink-0.4.1/Makefile000066400000000000000000000063661261543250700153340ustar00rootroot00000000000000PROJECT = heatshrink OPTIMIZE = -O3 WARN = -Wall -Wextra -pedantic #-Werror WARN += -Wmissing-prototypes WARN += -Wstrict-prototypes WARN += -Wmissing-declarations # If libtheft is available, build additional property-based tests. # Uncomment these to use it in test_heatshrink_dynamic. #CFLAGS += -DHEATSHRINK_HAS_THEFT #THEFT_PATH= /usr/local/ #THEFT_INC= -I${THEFT_PATH}/include/ #LDFLAGS += -L${THEFT_PATH}/lib -ltheft CFLAGS += -std=c99 -g ${WARN} ${THEFT_INC} ${OPTIMIZE} all: heatshrink test_runners libraries libraries: libheatshrink_static.a libheatshrink_dynamic.a test_runners: test_heatshrink_static test_heatshrink_dynamic test: test_runners ./test_heatshrink_static ./test_heatshrink_dynamic ci: test clean: rm -f heatshrink test_heatshrink_{dynamic,static} \ *.o *.os *.od *.core *.a {dec,enc}_sm.png TAGS rm -rf ${BENCHMARK_OUT} TAGS: etags *.[ch] diagrams: dec_sm.png enc_sm.png dec_sm.png: dec_sm.dot dot -o $@ -Tpng $< enc_sm.png: enc_sm.dot dot -o $@ -Tpng $< # Benchmarking CORPUS_ARCHIVE= cantrbry.tar.gz CORPUS_URL= http://corpus.canterbury.ac.nz/resources/${CORPUS_ARCHIVE} BENCHMARK_OUT= benchmark_out ## Uncomment one of these. DL= curl -o ${CORPUS_ARCHIVE} #DL= wget -O ${CORPUS_ARCHIVE} bench: heatshrink corpus mkdir -p ${BENCHMARK_OUT} cd ${BENCHMARK_OUT} && tar vzxf ../${CORPUS_ARCHIVE} time ./benchmark corpus: ${CORPUS_ARCHIVE} ${CORPUS_ARCHIVE}: ${DL} ${CORPUS_URL} # Installation PREFIX ?= /usr/local INSTALL ?= install RM ?= rm install: libraries heatshrink ${INSTALL} -c heatshrink ${PREFIX}/bin/ ${INSTALL} -c libheatshrink_static.a ${PREFIX}/lib/ ${INSTALL} -c libheatshrink_dynamic.a ${PREFIX}/lib/ ${INSTALL} -c heatshrink_common.h ${PREFIX}/include/ ${INSTALL} -c heatshrink_config.h ${PREFIX}/include/ ${INSTALL} -c heatshrink_encoder.h ${PREFIX}/include/ ${INSTALL} -c heatshrink_decoder.h ${PREFIX}/include/ uninstall: ${RM} -f ${PREFIX}/lib/libheatshrink_static.a ${RM} -f ${PREFIX}/lib/libheatshrink_dynamic.a ${RM} -f ${PREFIX}/include/heatshrink_common.h ${RM} -f ${PREFIX}/include/heatshrink_config.h ${RM} -f ${PREFIX}/include/heatshrink_encoder.h ${RM} -f ${PREFIX}/include/heatshrink_decoder.h # Internal targets and rules OBJS = heatshrink_encoder.o heatshrink_decoder.o DYNAMIC_OBJS= $(OBJS:.o=.od) STATIC_OBJS= $(OBJS:.o=.os) DYNAMIC_LDFLAGS= ${LDFLAGS} -L. -lheatshrink_dynamic STATIC_LDFLAGS= ${LDFLAGS} -L. -lheatshrink_static # Libraries should be built separately for versions # with and without dynamic allocation. CFLAGS_STATIC = ${CFLAGS} -DHEATSHRINK_DYNAMIC_ALLOC=0 CFLAGS_DYNAMIC = ${CFLAGS} -DHEATSHRINK_DYNAMIC_ALLOC=1 heatshrink: heatshrink.od libheatshrink_dynamic.a ${CC} -o $@ $^ ${CFLAGS_DYNAMIC} -L. -lheatshrink_dynamic test_heatshrink_dynamic: test_heatshrink_dynamic.od test_heatshrink_dynamic_theft.od libheatshrink_dynamic.a ${CC} -o $@ $< ${CFLAGS_DYNAMIC} test_heatshrink_dynamic_theft.od ${DYNAMIC_LDFLAGS} test_heatshrink_static: test_heatshrink_static.os libheatshrink_static.a ${CC} -o $@ $< ${CFLAGS_STATIC} ${STATIC_LDFLAGS} libheatshrink_static.a: ${STATIC_OBJS} ar -rcs $@ $^ libheatshrink_dynamic.a: ${DYNAMIC_OBJS} ar -rcs $@ $^ %.od: %.c ${CC} -c -o $@ $< ${CFLAGS_DYNAMIC} %.os: %.c ${CC} -c -o $@ $< ${CFLAGS_STATIC} *.os: Makefile *.h *.od: Makefile *.h heatshrink-0.4.1/README.md000066400000000000000000000133431261543250700151440ustar00rootroot00000000000000# heatshrink A data compression/decompression library for embedded/real-time systems. ## Key Features: - **Low memory usage (as low as 50 bytes)** It is useful for some cases with less than 50 bytes, and useful for many general cases with < 300 bytes. - **Incremental, bounded CPU use** You can chew on input data in arbitrarily tiny bites. This is a useful property in hard real-time environments. - **Can use either static or dynamic memory allocation** The library doesn't impose any constraints on memory management. - **ISC license** You can use it freely, even for commercial purposes. ## Getting Started: There is a standalone command-line program, `heatshrink`, but the encoder and decoder can also be used as libraries, independent of each other. To do so, copy `heatshrink_common.h`, `heatshrink_config.h`, and either `heatshrink_encoder.c` or `heatshrink_decoder.c` (and their respective header) into your project. For projects that use both, static libraries are built that use static and dynamic allocation. Dynamic allocation is used by default, but in an embedded context, you probably want to statically allocate the encoder/decoder. Set `HEATSHRINK_DYNAMIC_ALLOC` to 0 in `heatshrink_config.h`. ### Basic Usage 1. Allocate a `heatshrink_encoder` or `heatshrink_decoder` state machine using their `alloc` function, or statically allocate one and call their `reset` function to initialize them. (See below for configuration options.) 2. Use `sink` to sink an input buffer into the state machine. The `input_size` pointer argument will be set to indicate how many bytes of the input buffer were actually consumed. (If 0 bytes were conusmed, the buffer is full.) 3. Use `poll` to move output from the state machine into an output buffer. The `output_size` pointer argument will be set to indicate how many bytes were output, and the function return value will indicate whether further output is available. (The state machine may not output any data until it has received enough input.) Repeat steps 2 and 3 to stream data through the state machine. Since it's doing data compression, the input and output sizes can vary significantly. Looping will be necessary to buffer the input and output as the data is processed. 4. When the end of the input stream is reached, call `finish` to notify the state machine that no more input is available. The return value from `finish` will indicate whether any output remains. if so, call `poll` to get more. Continue calling `finish` and `poll`ing to flush remaining output until `finish` indicates that the output has been exhausted. Sinking more data after `finish` has been called will not work without calling `reset` on the state machine. ## Configuration heatshrink has a couple configuration options, which impact its resource usage and how effectively it can compress data. These are set when dynamically allocating an encoder or decoder, or in `heatshrink_config.h` if they are statically allocated. - `window_sz2`, `-w` in the CLI: Set the window size to 2^W bytes. The window size determines how far back in the input can be searched for repeated patterns. A `window_sz2` of 8 will only use 256 bytes (2^8), while a `window_sz2` of 10 will use 1024 bytes (2^10). The latter uses more memory, but may also compress more effectively by detecting more repetition. The `window_sz2` setting currently must be between 4 and 15. - `lookahead_sz2`, `-l` in the CLI: Set the lookahead size to 2^L bytes. The lookahead size determines the max length for repeated patterns that are found. If the `lookahead_sz2` is 4, a 50-byte run of 'a' characters will be represented as several repeated 16-byte patterns (2^4 is 16), whereas a larger `lookahead_sz2` may be able to represent it all at once. The number of bits used for the lookahead size is fixed, so an overly large lookahead size can reduce compression by adding unused size bits to small patterns. The `lookahead_sz2` setting currently must be between 3 and the `window_sz2` - 1. - `input_buffer_size` - How large an input buffer to use for the decoder. This impacts how much work the decoder can do in a single step, and a larger buffer will use more memory. An extremely small buffer (say, 1 byte) will add overhead due to lots of suspend/resume function calls, but should not change how well data compresses. ### Recommended Defaults For embedded/low memory contexts, a `window_sz2` in the 8 to 10 range is probably a good default, depending on how tight memory is. Smaller or larger window sizes may make better trade-offs in specific circumstances, but should be checked with representative data. The `lookahead_sz2` should probably start near the `window_sz2`/2, e.g. -w 8 -l 4 or -w 10 -l 5. The command-line program can be used to measure how well test data works with different settings. ## More Information and Benchmarks: heatshrink is based on [LZSS], since it's particularly suitable for compression in small amounts of memory. It can use an optional, small [index] to make compression significantly faster, but otherwise can run in under 100 bytes of memory. The index currently adds 2^(window size+1) bytes to memory usage for compression, and temporarily allocates 512 bytes on the stack during index construction (if the index is enabled). For more information, see the [blog post] for an overview, and the `heatshrink_encoder.h` / `heatshrink_decoder.h` header files for API documentation. [blog post]: http://spin.atomicobject.com/2013/03/14/heatshrink-embedded-data-compression/ [index]: http://spin.atomicobject.com/2014/01/13/lightweight-indexing-for-embedded-systems/ [LZSS]: http://en.wikipedia.org/wiki/Lempel-Ziv-Storer-Szymanski ## Build Status [![Build Status](https://travis-ci.org/atomicobject/heatshrink.png)](http://travis-ci.org/atomicobject/heatshrink) heatshrink-0.4.1/benchmark000077500000000000000000000030351261543250700155420ustar00rootroot00000000000000#!/bin/sh BENCHMARK_OUT=benchmark_out HS=../heatshrink cd ${BENCHMARK_OUT} # Files in the Canterbury Corpus # http://corpus.canterbury.ac.nz/resources/cantrbry.tar.gz FILES='alice29.txt asyoulik.txt cp.html fields.c grammar.lsp kennedy.xls lcet10.txt plrabn12.txt ptt5 sum xargs.1' rm -f benchmark.output # Run several combinations of -w W -l L, # note compression ratios and check uncompressed output matches input for W in 6 7 8 9 10 11 12; do for L in 5 6 7 8; do if [ $L -lt $W ]; then for f in ${FILES} do IN_FILE="${f}" COMPRESSED_FILE="${f}.hsz.${W}_${L}" UNCOMPRESSED_FILE="${f}.orig.${W}_${L}" ${HS} -e -v -w ${W} -l ${L} ${IN_FILE} ${COMPRESSED_FILE} >> benchmark.output ${HS} -d -v -w ${W} -l ${L} ${COMPRESSED_FILE} ${UNCOMPRESSED_FILE} > /dev/null # compare file sizes if [ $(ls -l ${IN_FILE} | awk '{print($5)}') != $(ls -l ${UNCOMPRESSED_FILE} | awk '{print($5)}') ]; then printf "\n\n\nWARNING: size of %s does not match size of %s\n\n\n" \ ${IN_FILE} ${UNCOMPRESSED_FILE} else printf "pass: -w %2d -l %2d %s\n" ${W} ${L} "${f}" fi rm ${COMPRESSED_FILE} ${UNCOMPRESSED_FILE} done fi done done # Print totals and averages awk '{ t += $2; c++ }; END { printf("====\nTotal compression: %.02f%% for %d documents (avg. %0.2f%%)\n", t, c, t / c) }' benchmark.output heatshrink-0.4.1/dec_sm.dot000066400000000000000000000040651261543250700156300ustar00rootroot00000000000000digraph { graph [label="Decoder state machine", labelloc="t"] Start [style="invis", shape="point"] tag_bit yield_literal backref_index_msb backref_index_lsb backref_count_msb backref_count_lsb yield_backref done [peripheries=2] tag_bit->tag_bit [label="sink()", color="blue", weight=10] Start->tag_bit tag_bit->yield_literal [label="pop 1-bit"] tag_bit->backref_index_msb [label="pop 0-bit", weight=10] tag_bit->backref_index_lsb [label="pop 0-bit, index <8 bits", weight=10] yield_literal->yield_literal [label="sink()", color="blue"] yield_literal->yield_literal [label="poll()", color="red"] yield_literal->tag_bit [label="poll(), done", color="red"] backref_index_msb->backref_index_msb [label="sink()", color="blue"] backref_index_msb->backref_index_lsb [label="pop index, upper bits", weight=10] backref_index_msb->done [label="finish()", color="blue"] backref_index_lsb->backref_index_lsb [label="sink()", color="blue"] backref_index_lsb->backref_count_msb [label="pop index, lower bits", weight=10] backref_index_lsb->backref_count_lsb [label="pop index, count <=8 bits", weight=10] backref_index_lsb->done [label="finish()", color="blue"] backref_count_msb->backref_count_msb [label="sink()", color="blue"] backref_count_msb->backref_count_lsb [label="pop count, upper bits", weight=10] backref_count_msb->done [label="finish()", color="blue"] backref_count_lsb->backref_count_lsb [label="sink()", color="blue"] backref_count_lsb->yield_backref [label="pop count, lower bits", weight=10] backref_count_lsb->done [label="finish()", color="blue"] yield_backref->yield_backref [label="sink()", color="blue"] yield_backref->yield_backref [label="poll()", color="red"] yield_backref->tag_bit [label="poll(), done", color="red", weight=10] tag_bit->done [label="finish()", color="blue"] } heatshrink-0.4.1/enc_sm.dot000066400000000000000000000033211261543250700156340ustar00rootroot00000000000000digraph { graph [label="Encoder state machine", labelloc="t"] start [style="invis", shape="point"] not_full filled search yield_tag_bit yield_literal yield_br_length yield_br_index save_backlog flush_bits done [peripheries=2] start->not_full [label="start"] not_full->not_full [label="sink(), not full", color="blue"] not_full->filled [label="sink(), buffer is full", color="blue"] not_full->filled [label="finish(), set is_finished", color="blue"] filled->search [label="indexing (if any)"] search->yield_tag_bit [label="literal"] search->yield_tag_bit [label="match found"] search->save_backlog [label="input exhausted, not finishing"] search->flush_bits [label="input exhausted, finishing"] yield_tag_bit->yield_tag_bit [label="poll(), full buf", color="red"] yield_tag_bit->yield_literal [label="poll(), literal", color="red"] yield_tag_bit->yield_br_index [label="poll(), match", color="red"] yield_literal->yield_literal [label="poll(), full buf", color="red"] yield_literal->search [label="done"] yield_br_index->yield_br_index [label="poll(), full buf", color="red"] yield_br_index->yield_br_length [label="poll()", color="red"] yield_br_length->yield_br_length [label="poll(), full buf", color="red"] yield_br_length->search [label="done"] save_backlog->not_full [label="expect more input"] flush_bits->flush_bits [label="poll(), full buf", color="red"] flush_bits->done [label="poll(), flushed", color="red"] flush_bits->done [label="no more output"] } heatshrink-0.4.1/greatest.h000066400000000000000000001226071261543250700156600ustar00rootroot00000000000000/* * Copyright (c) 2011-2015 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 /* 1.0.0 */ #define GREATEST_VERSION_MAJOR 1 #define GREATEST_VERSION_MINOR 0 #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. */ /********************************************************************* * Minimal test runner template *********************************************************************/ #if 0 #include "greatest.h" TEST foo_should_foo() { 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 in the suite. * 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. */ RUN_SUITE(suite); GREATEST_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 /*********** * 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 1 #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 /********* * 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 *exp, 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; /* Callbacks for string type. */ extern greatest_type_info greatest_type_info_string; typedef enum { GREATEST_FLAG_VERBOSE = 0x01, GREATEST_FLAG_FIRST_FAIL = 0x02, GREATEST_FLAG_LIST_ONLY = 0x04 } GREATEST_FLAG; /* Struct containing all test runner state. */ typedef struct greatest_run_info { unsigned int flags; unsigned int tests_run; /* total test count */ /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; /* currently running test suite */ greatest_suite_info suite; /* info to print about the most recent failure */ const char *fail_file; unsigned int fail_line; 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 */ char *suite_filter; char *test_filter; #if GREATEST_USE_TIME /* overall timers */ clock_t begin; clock_t end; #endif #if GREATEST_USE_LONGJMP jmp_buf jump_dest; #endif } greatest_run_info; /* Global var for the current testing context. * Initialized by GREATEST_MAIN_DEFS(). */ extern greatest_run_info greatest_info; /********************** * Exported functions * **********************/ /* These are used internally by greatest. */ void greatest_do_pass(const char *name); void greatest_do_fail(const char *name); void greatest_do_skip(const char *name); int greatest_pre_test(const char *name); void greatest_post_test(const char *name, int res); void greatest_usage(const char *name); int greatest_do_assert_equal_t(const void *exp, const void *got, greatest_type_info *type_info, void *udata); /* 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); int greatest_all_passed(void); /******************** * Language Support * ********************/ /* If __VA_ARGS__ (C99) is supported, allow parametric testing * without needing to manually manage the argument struct. */ #if __STDC_VERSION__ >= 19901L || _MSC_VER >= 1800 #define GREATEST_VA_ARGS #endif /********** * Macros * **********/ /* Define a suite. */ #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) /* Start defining a test function. * The arguments are not included, to allow parametric testing. */ #define GREATEST_TEST static greatest_test_res /* PASS/FAIL/SKIP result from a test. Used internally. */ typedef enum { 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_pre_test(#TEST) == 1) { \ greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(); \ } \ greatest_post_test(#TEST, res); \ } else if (GREATEST_LIST_ONLY()) { \ fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ } \ } while (0) /* 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_pre_test(#TEST) == 1) { \ int res = TEST(ENV); \ greatest_post_test(#TEST, res); \ } else if (GREATEST_LIST_ONLY()) { \ fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ } \ } while (0) #ifdef GREATEST_VA_ARGS #define GREATEST_RUN_TESTp(TEST, ...) \ do { \ if (greatest_pre_test(#TEST) == 1) { \ int res = TEST(__VA_ARGS__); \ greatest_post_test(#TEST, res); \ } else if (GREATEST_LIST_ONLY()) { \ fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ } \ } while (0) #endif /* Check if the test runner is in verbose mode. */ #define GREATEST_IS_VERBOSE() (greatest_info.flags & GREATEST_FLAG_VERBOSE) #define GREATEST_LIST_ONLY() (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) #define GREATEST_FIRST_FAIL() (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) #define GREATEST_FAILURE_ABORT() (greatest_info.suite.failed > 0 && GREATEST_FIRST_FAIL()) /* 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_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) /* 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) /* Fail if EXP != GOT (equality comparison by ==). */ #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ do { \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP != GOT (equality comparison by ==). */ #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ do { \ greatest_info.assertions++; \ const char *fmt = ( FMT ); \ if ((EXP) != (GOT)) { \ fprintf(GREATEST_STDOUT, "\nExpected: "); \ fprintf(GREATEST_STDOUT, fmt, EXP); \ fprintf(GREATEST_STDOUT, "\nGot: "); \ fprintf(GREATEST_STDOUT, fmt, GOT); \ fprintf(GREATEST_STDOUT, "\n"); \ 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_info.assertions++; \ GREATEST_FLOAT exp = (EXP); \ GREATEST_FLOAT got = (GOT); \ GREATEST_FLOAT tol = (TOL); \ if ((exp > got && exp - got > tol) || \ (exp < got && got - exp > tol)) { \ fprintf(GREATEST_STDOUT, \ "\nExpected: " GREATEST_FLOAT_FMT \ " +/- " GREATEST_FLOAT_FMT "\n" \ "Got: " GREATEST_FLOAT_FMT "\n", \ exp, tol, 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 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; \ 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 { \ int _check_call_res = RES; \ if (_check_call_res != GREATEST_TEST_RES_PASS) { \ return _check_call_res; \ } \ } while (0) \ #if GREATEST_USE_TIME #define GREATEST_SET_TIME(NAME) \ NAME = clock(); \ if (NAME == (clock_t) -1) { \ fprintf(GREATEST_STDOUT, \ "clock error: %s\n", #NAME); \ exit(EXIT_FAILURE); \ } #define GREATEST_CLOCK_DIFF(C1, C2) \ 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. */ \ ((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 /* 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) { \ size_t offset = 0; \ size_t filter_len = strlen(filter); \ while (name[offset] != '\0') { \ if (name[offset] == filter[0]) { \ if (0 == strncmp(&name[offset], filter, filter_len)) { \ return 1; \ } \ } \ offset++; \ } \ \ return 0; \ } \ \ int greatest_pre_test(const char *name) { \ if (!GREATEST_LIST_ONLY() \ && (!GREATEST_FIRST_FAIL() || greatest_info.suite.failed == 0) \ && (greatest_info.test_filter == NULL || \ greatest_name_match(name, greatest_info.test_filter))) { \ GREATEST_SET_TIME(greatest_info.suite.pre_test); \ if (greatest_info.setup) { \ greatest_info.setup(greatest_info.setup_udata); \ } \ return 1; /* test should be run */ \ } else { \ return 0; /* skipped */ \ } \ } \ \ void greatest_post_test(const char *name, int res) { \ GREATEST_SET_TIME(greatest_info.suite.post_test); \ if (greatest_info.teardown) { \ void *udata = greatest_info.teardown_udata; \ greatest_info.teardown(udata); \ } \ \ if (res <= GREATEST_TEST_RES_FAIL) { \ greatest_do_fail(name); \ } else if (res >= GREATEST_TEST_RES_SKIP) { \ greatest_do_skip(name); \ } else if (res == GREATEST_TEST_RES_PASS) { \ greatest_do_pass(name); \ } \ 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); \ fprintf(GREATEST_STDOUT, "\n"); \ } else if (greatest_info.col % greatest_info.width == 0) { \ fprintf(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ if (GREATEST_STDOUT == stdout) fflush(stdout); \ } \ \ static void greatest_run_suite(greatest_suite_cb *suite_cb, \ const char *suite_name) { \ if (greatest_info.suite_filter && \ !greatest_name_match(suite_name, greatest_info.suite_filter)) { \ return; \ } \ if (GREATEST_FIRST_FAIL() && greatest_info.failed > 0) { return; } \ memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ greatest_info.col = 0; \ fprintf(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ suite_cb(); \ GREATEST_SET_TIME(greatest_info.suite.post_suite); \ if (greatest_info.suite.tests_run > 0) { \ fprintf(GREATEST_STDOUT, \ "\n%u tests - %u pass, %u fail, %u skipped", \ greatest_info.suite.tests_run, \ 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); \ fprintf(GREATEST_STDOUT, "\n"); \ } \ 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; \ } \ \ void greatest_do_pass(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ fprintf(GREATEST_STDOUT, "PASS %s: %s", \ name, greatest_info.msg ? greatest_info.msg : ""); \ } else { \ fprintf(GREATEST_STDOUT, "."); \ } \ greatest_info.suite.passed++; \ } \ \ void greatest_do_fail(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ fprintf(GREATEST_STDOUT, \ "FAIL %s: %s (%s:%u)", \ name, greatest_info.msg ? greatest_info.msg : "", \ greatest_info.fail_file, greatest_info.fail_line); \ } else { \ fprintf(GREATEST_STDOUT, "F"); \ greatest_info.col++; \ /* add linebreak if in line of '.'s */ \ if (greatest_info.col != 0) { \ fprintf(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ fprintf(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ name, \ greatest_info.msg ? greatest_info.msg : "", \ greatest_info.fail_file, greatest_info.fail_line); \ } \ greatest_info.suite.failed++; \ } \ \ void greatest_do_skip(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ fprintf(GREATEST_STDOUT, "SKIP %s: %s", \ name, \ greatest_info.msg ? \ greatest_info.msg : "" ); \ } else { \ fprintf(GREATEST_STDOUT, "s"); \ } \ greatest_info.suite.skipped++; \ } \ \ int greatest_do_assert_equal_t(const void *exp, 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(exp, got, udata); \ if (!eq) { \ if (type_info->print != NULL) { \ fprintf(GREATEST_STDOUT, "\nExpected: "); \ (void)type_info->print(exp, udata); \ fprintf(GREATEST_STDOUT, "\nGot: "); \ (void)type_info->print(got, udata); \ fprintf(GREATEST_STDOUT, "\n"); \ } else { \ fprintf(GREATEST_STDOUT, \ "GREATEST_ASSERT_EQUAL_T failure at %s:%dn", \ greatest_info.fail_file, \ greatest_info.fail_line); \ } \ } \ return eq; \ } \ \ void greatest_usage(const char *name) { \ fprintf(GREATEST_STDOUT, \ "Usage: %s [-hlfv] [-s SUITE] [-t TEST]\n" \ " -h print this Help\n" \ " -l List suites and their tests, then exit\n" \ " -f Stop runner after first failure\n" \ " -v Verbose output\n" \ " -s SUITE only run suite named SUITE\n" \ " -t TEST only run test named TEST\n", \ name); \ } \ \ int greatest_all_passed() { return (greatest_info.failed == 0); } \ \ 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 *exp, const void *got, \ void *udata) { \ (void)udata; \ return (0 == strcmp((const char *)exp, (const char *)got)); \ } \ \ static int greatest_string_printf_cb(const void *t, void *udata) { \ (void)udata; \ return fprintf(GREATEST_STDOUT, "%s", (const char *)t); \ } \ \ greatest_type_info greatest_type_info_string = { \ greatest_string_equal_cb, \ greatest_string_printf_cb, \ }; \ \ greatest_run_info greatest_info /* Init internals. */ #define GREATEST_INIT() \ do { \ memset(&greatest_info, 0, sizeof(greatest_info)); \ greatest_info.width = GREATEST_DEFAULT_WIDTH; \ GREATEST_SET_TIME(greatest_info.begin); \ } while (0) \ /* Handle command-line arguments, etc. */ #define GREATEST_MAIN_BEGIN() \ do { \ int i = 0; \ GREATEST_INIT(); \ for (i = 1; i < argc; i++) { \ if (0 == strcmp("-t", argv[i])) { \ if (argc <= i + 1) { \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ greatest_info.test_filter = argv[i+1]; \ i++; \ } else if (0 == strcmp("-s", argv[i])) { \ if (argc <= i + 1) { \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ greatest_info.suite_filter = argv[i+1]; \ i++; \ } else if (0 == strcmp("-f", argv[i])) { \ greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ } else if (0 == strcmp("-v", argv[i])) { \ greatest_info.flags |= GREATEST_FLAG_VERBOSE; \ } else if (0 == strcmp("-l", argv[i])) { \ greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; \ } else if (0 == strcmp("-h", argv[i])) { \ greatest_usage(argv[0]); \ exit(EXIT_SUCCESS); \ } else { \ fprintf(GREATEST_STDOUT, \ "Unknown argument '%s'\n", argv[i]); \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ } \ } while (0) /* Report passes, failures, skipped tests, the number of * assertions, and the overall run time. */ #define GREATEST_REPORT() \ do { \ if (!GREATEST_LIST_ONLY()) { \ GREATEST_SET_TIME(greatest_info.end); \ fprintf(GREATEST_STDOUT, \ "\nTotal: %u tests", greatest_info.tests_run); \ GREATEST_CLOCK_DIFF(greatest_info.begin, \ greatest_info.end); \ fprintf(GREATEST_STDOUT, ", %u assertions\n", \ greatest_info.assertions); \ fprintf(GREATEST_STDOUT, \ "Pass: %u, fail: %u, skip: %u.\n", \ greatest_info.passed, \ greatest_info.failed, greatest_info.skipped); \ } \ } while (0) /* Report results, exit with exit status based on results. */ #define GREATEST_MAIN_END() \ do { \ GREATEST_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 RUN_TEST GREATEST_RUN_TEST #define RUN_TEST1 GREATEST_RUN_TEST1 #define RUN_SUITE GREATEST_RUN_SUITE #define ASSERT GREATEST_ASSERT #define ASSERTm GREATEST_ASSERTm #define ASSERT_FALSE GREATEST_ASSERT_FALSE #define ASSERT_EQ GREATEST_ASSERT_EQ #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_FALSEm GREATEST_ASSERT_FALSEm #define ASSERT_EQm GREATEST_ASSERT_EQm #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 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 #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 */ #endif heatshrink-0.4.1/heatshrink.c000077500000000000000000000343451261543250700162010ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "heatshrink_encoder.h" #include "heatshrink_decoder.h" #define DEF_WINDOW_SZ2 11 #define DEF_LOOKAHEAD_SZ2 4 #define DEF_DECODER_INPUT_BUFFER_SIZE 256 #define DEF_BUFFER_SIZE (64 * 1024) #if 0 #define LOG(...) fprintf(stderr, __VA_ARGS__) #else #define LOG(...) /* NO-OP */ #endif #if _WIN32 #include #define HEATSHRINK_ERR(retval, ...) do { \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "Undefined error: %d\n", errno); \ exit(retval); \ } while(0) #else #include #define HEATSHRINK_ERR(...) err(__VA_ARGS__) #endif /* * We have to open binary files with the O_BINARY flag on Windows. Most other * platforms don't differentiate between binary and non-binary files. */ #ifndef O_BINARY #define O_BINARY 0 #endif static const int version_major = HEATSHRINK_VERSION_MAJOR; static const int version_minor = HEATSHRINK_VERSION_MINOR; static const int version_patch = HEATSHRINK_VERSION_PATCH; static const char author[] = HEATSHRINK_AUTHOR; static const char url[] = HEATSHRINK_URL; static void usage(void) { fprintf(stderr, "heatshrink version %u.%u.%u by %s\n", version_major, version_minor, version_patch, author); fprintf(stderr, "Home page: %s\n\n", url); fprintf(stderr, "Usage:\n" " heatshrink [-h] [-e|-d] [-v] [-w SIZE] [-l BITS] [IN_FILE] [OUT_FILE]\n" "\n" "heatshrink compresses or decompresses byte streams using LZSS, and is\n" "designed especially for embedded, low-memory, and/or hard real-time\n" "systems.\n" "\n" " -h print help\n" " -e encode (compress, default)\n" " -d decode (decompress)\n" " -v verbose (print input & output sizes, compression ratio, etc.)\n" "\n" " -w SIZE Base-2 log of LZSS sliding window size\n" "\n" " A larger value allows searches a larger history of the data for repeated\n" " patterns, potentially compressing more effectively, but will use\n" " more memory and processing time.\n" " Recommended default: -w 8 (embedded systems), -w 10 (elsewhere)\n" " \n" " -l BITS Number of bits used for back-reference lengths\n" "\n" " A larger value allows longer substitutions, but since all\n" " back-references must use -w + -l bits, larger -w or -l can be\n" " counterproductive if most patterns are small and/or local.\n" " Recommended default: -l 4\n" "\n" " If IN_FILE or OUT_FILE are unspecified, they will default to\n" " \"-\" for standard input and standard output, respectively.\n"); exit(1); } typedef enum { IO_READ, IO_WRITE, } IO_mode; typedef enum { OP_ENC, OP_DEC, } Operation; typedef struct { int fd; /* file descriptor */ IO_mode mode; size_t fill; /* fill index */ size_t read; /* read index */ size_t size; size_t total; uint8_t buf[]; } io_handle; typedef struct { uint8_t window_sz2; uint8_t lookahead_sz2; size_t decoder_input_buffer_size; size_t buffer_size; uint8_t verbose; Operation cmd; char *in_fname; char *out_fname; io_handle *in; io_handle *out; } config; static void die(char *msg) { fprintf(stderr, "%s\n", msg); exit(EXIT_FAILURE); } static void report(config *cfg); /* Open an IO handle. Returns NULL on error. */ static io_handle *handle_open(char *fname, IO_mode m, size_t buf_sz) { io_handle *io = NULL; io = malloc(sizeof(*io) + buf_sz); if (io == NULL) { return NULL; } memset(io, 0, sizeof(*io) + buf_sz); io->fd = -1; io->size = buf_sz; io->mode = m; if (m == IO_READ) { if (0 == strcmp("-", fname)) { io->fd = STDIN_FILENO; } else { io->fd = open(fname, O_RDONLY | O_BINARY); } } else if (m == IO_WRITE) { if (0 == strcmp("-", fname)) { io->fd = STDOUT_FILENO; } else { io->fd = open(fname, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC /*| O_EXCL*/, 0644); } } if (io->fd == -1) { /* failed to open */ free(io); HEATSHRINK_ERR(1, "open"); return NULL; } return io; } /* Read SIZE bytes from an IO handle and return a pointer to the content. * BUF contains at least size_t bytes. Returns 0 on EOF, -1 on error. */ static ssize_t handle_read(io_handle *io, size_t size, uint8_t **buf) { LOG("@ read %zd\n", size); if (buf == NULL) { return -1; } if (size > io->size) { fprintf(stderr, "size %zd, io->size %zd\n", size, io->size); return -1; } if (io->mode != IO_READ) { return -1; } size_t rem = io->fill - io->read; if (rem >= size) { *buf = &io->buf[io->read]; return size; } else { /* read and replenish */ if (io->fd == -1) { /* already closed, return what we've got */ *buf = &io->buf[io->read]; return rem; } memmove(io->buf, &io->buf[io->read], rem); io->fill -= io->read; io->read = 0; ssize_t read_sz = read(io->fd, &io->buf[io->fill], io->size - io->fill); if (read_sz < 0) { HEATSHRINK_ERR(1, "read"); } io->total += read_sz; if (read_sz == 0) { /* EOF */ if (close(io->fd) < 0) { HEATSHRINK_ERR(1, "close"); } io->fd = -1; } io->fill += read_sz; *buf = io->buf; return io->fill > size ? size : io->fill; } } /* Drop the oldest SIZE bytes from the buffer. Returns <0 on error. */ static int handle_drop(io_handle *io, size_t size) { LOG("@ drop %zd\n", size); if (io->read + size <= io->fill) { io->read += size; } else { return -1; } if (io->read == io->fill) { io->read = 0; io->fill = 0; } return 0; } /* Sink SIZE bytes from INPUT into the io handle. Returns the number of * bytes written, or -1 on error. */ static ssize_t handle_sink(io_handle *io, size_t size, uint8_t *input) { LOG("@ sink %zd\n", size); if (size > io->size) { return -1; } if (io->mode != IO_WRITE) { return -1; } if (io->fill + size > io->size) { ssize_t written = write(io->fd, io->buf, io->fill); LOG("@ flushing %zd, wrote %zd\n", io->fill, written); io->total += written; if (written == -1) { HEATSHRINK_ERR(1, "write"); } memmove(io->buf, &io->buf[written], io->fill - written); io->fill -= written; } memcpy(&io->buf[io->fill], input, size); io->fill += size; return size; } static void handle_close(io_handle *io) { if (io->fd != -1) { if (io->mode == IO_WRITE) { ssize_t written = write(io->fd, io->buf, io->fill); io->total += written; LOG("@ close: flushing %zd, wrote %zd\n", io->fill, written); if (written == -1) { HEATSHRINK_ERR(1, "write"); } } close(io->fd); io->fd = -1; } } static void close_and_report(config *cfg) { handle_close(cfg->in); handle_close(cfg->out); if (cfg->verbose) { report(cfg); } free(cfg->in); free(cfg->out); } static int encoder_sink_read(config *cfg, heatshrink_encoder *hse, uint8_t *data, size_t data_sz) { size_t out_sz = 4096; uint8_t out_buf[out_sz]; memset(out_buf, 0, out_sz); size_t sink_sz = 0; size_t poll_sz = 0; HSE_sink_res sres; HSE_poll_res pres; HSE_finish_res fres; io_handle *out = cfg->out; size_t sunk = 0; do { if (data_sz > 0) { sres = heatshrink_encoder_sink(hse, &data[sunk], data_sz - sunk, &sink_sz); if (sres < 0) { die("sink"); } sunk += sink_sz; } do { pres = heatshrink_encoder_poll(hse, out_buf, out_sz, &poll_sz); if (pres < 0) { die("poll"); } if (handle_sink(out, poll_sz, out_buf) < 0) die("handle_sink"); } while (pres == HSER_POLL_MORE); if (poll_sz == 0 && data_sz == 0) { fres = heatshrink_encoder_finish(hse); if (fres < 0) { die("finish"); } if (fres == HSER_FINISH_DONE) { return 1; } } } while (sunk < data_sz); return 0; } static int encode(config *cfg) { uint8_t window_sz2 = cfg->window_sz2; size_t window_sz = 1 << window_sz2; heatshrink_encoder *hse = heatshrink_encoder_alloc(window_sz2, cfg->lookahead_sz2); if (hse == NULL) { die("failed to init encoder: bad settings"); } ssize_t read_sz = 0; io_handle *in = cfg->in; /* Process input until end of stream */ while (1) { uint8_t *input = NULL; read_sz = handle_read(in, window_sz, &input); if (input == NULL) { fprintf(stderr, "handle read failure\n"); die("read"); } if (read_sz < 0) { die("read"); } /* Pass read to encoder and check if input is fully processed. */ if (encoder_sink_read(cfg, hse, input, read_sz)) break; if (handle_drop(in, read_sz) < 0) { die("drop"); } }; if (read_sz == -1) { HEATSHRINK_ERR(1, "read"); } heatshrink_encoder_free(hse); close_and_report(cfg); return 0; } static int decoder_sink_read(config *cfg, heatshrink_decoder *hsd, uint8_t *data, size_t data_sz) { io_handle *out = cfg->out; size_t sink_sz = 0; size_t poll_sz = 0; size_t out_sz = 4096; uint8_t out_buf[out_sz]; memset(out_buf, 0, out_sz); HSD_sink_res sres; HSD_poll_res pres; HSD_finish_res fres; size_t sunk = 0; do { if (data_sz > 0) { sres = heatshrink_decoder_sink(hsd, &data[sunk], data_sz - sunk, &sink_sz); if (sres < 0) { die("sink"); } sunk += sink_sz; } do { pres = heatshrink_decoder_poll(hsd, out_buf, out_sz, &poll_sz); if (pres < 0) { die("poll"); } if (handle_sink(out, poll_sz, out_buf) < 0) die("handle_sink"); } while (pres == HSDR_POLL_MORE); if (data_sz == 0 && poll_sz == 0) { fres = heatshrink_decoder_finish(hsd); if (fres < 0) { die("finish"); } if (fres == HSDR_FINISH_DONE) { return 1; } } } while (sunk < data_sz); return 0; } static int decode(config *cfg) { uint8_t window_sz2 = cfg->window_sz2; size_t window_sz = 1 << window_sz2; size_t ibs = cfg->decoder_input_buffer_size; heatshrink_decoder *hsd = heatshrink_decoder_alloc(ibs, window_sz2, cfg->lookahead_sz2); if (hsd == NULL) { die("failed to init decoder"); } ssize_t read_sz = 0; io_handle *in = cfg->in; HSD_finish_res fres; /* Process input until end of stream */ while (1) { uint8_t *input = NULL; read_sz = handle_read(in, window_sz, &input); if (input == NULL) { fprintf(stderr, "handle read failure\n"); die("read"); } if (read_sz == 0) { fres = heatshrink_decoder_finish(hsd); if (fres < 0) { die("finish"); } if (fres == HSDR_FINISH_DONE) break; } else if (read_sz < 0) { die("read"); } else { if (decoder_sink_read(cfg, hsd, input, read_sz)) { break; } if (handle_drop(in, read_sz) < 0) { die("drop"); } } } if (read_sz == -1) { HEATSHRINK_ERR(1, "read"); } heatshrink_decoder_free(hsd); close_and_report(cfg); return 0; } static void report(config *cfg) { size_t inb = cfg->in->total; size_t outb = cfg->out->total; fprintf(cfg->out->fd == STDOUT_FILENO ? stderr : stdout, "%s %0.2f %%\t %zd -> %zd (-w %u -l %u)\n", cfg->in_fname, 100.0 - (100.0 * outb) / inb, inb, outb, cfg->window_sz2, cfg->lookahead_sz2); } static void proc_args(config *cfg, int argc, char **argv) { cfg->window_sz2 = DEF_WINDOW_SZ2; cfg->lookahead_sz2 = DEF_LOOKAHEAD_SZ2; cfg->buffer_size = DEF_BUFFER_SIZE; cfg->decoder_input_buffer_size = DEF_DECODER_INPUT_BUFFER_SIZE; cfg->cmd = OP_ENC; cfg->verbose = 0; cfg->in_fname = "-"; cfg->out_fname = "-"; int a = 0; while ((a = getopt(argc, argv, "hedi:w:l:v")) != -1) { switch (a) { case 'h': /* help */ usage(); case 'e': /* encode */ cfg->cmd = OP_ENC; break; case 'd': /* decode */ cfg->cmd = OP_DEC; break; case 'i': /* input buffer size */ cfg->decoder_input_buffer_size = atoi(optarg); break; case 'w': /* window bits */ cfg->window_sz2 = atoi(optarg); break; case 'l': /* lookahead bits */ cfg->lookahead_sz2 = atoi(optarg); break; case 'v': /* verbosity++ */ cfg->verbose++; break; case '?': /* unknown argument */ default: usage(); } } argc -= optind; argv += optind; if (argc > 0) { cfg->in_fname = argv[0]; argc--; argv++; } if (argc > 0) { cfg->out_fname = argv[0]; } } int main(int argc, char **argv) { config cfg; memset(&cfg, 0, sizeof(cfg)); proc_args(&cfg, argc, argv); if (0 == strcmp(cfg.in_fname, cfg.out_fname) && (0 != strcmp("-", cfg.in_fname))) { fprintf(stderr, "Refusing to overwrite file '%s' with itself.\n", cfg.in_fname); exit(1); } cfg.in = handle_open(cfg.in_fname, IO_READ, cfg.buffer_size); if (cfg.in == NULL) { die("Failed to open input file for read"); } cfg.out = handle_open(cfg.out_fname, IO_WRITE, cfg.buffer_size); if (cfg.out == NULL) { die("Failed to open output file for write"); } #if _WIN32 /* * On Windows, stdin and stdout default to text mode. Switch them to * binary mode before sending data through them. */ _setmode(STDOUT_FILENO, O_BINARY); _setmode(STDIN_FILENO, O_BINARY); #endif if (cfg.cmd == OP_ENC) { return encode(&cfg); } else if (cfg.cmd == OP_DEC) { return decode(&cfg); } else { usage(); } } heatshrink-0.4.1/heatshrink_common.h000066400000000000000000000010001261543250700175310ustar00rootroot00000000000000#ifndef HEATSHRINK_H #define HEATSHRINK_H #define HEATSHRINK_AUTHOR "Scott Vokes " #define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink" /* Version 0.4.1 */ #define HEATSHRINK_VERSION_MAJOR 0 #define HEATSHRINK_VERSION_MINOR 4 #define HEATSHRINK_VERSION_PATCH 1 #define HEATSHRINK_MIN_WINDOW_BITS 4 #define HEATSHRINK_MAX_WINDOW_BITS 15 #define HEATSHRINK_MIN_LOOKAHEAD_BITS 3 #define HEATSHRINK_LITERAL_MARKER 0x01 #define HEATSHRINK_BACKREF_MARKER 0x00 #endif heatshrink-0.4.1/heatshrink_config.h000066400000000000000000000013711261543250700175210ustar00rootroot00000000000000#ifndef HEATSHRINK_CONFIG_H #define HEATSHRINK_CONFIG_H /* Should functionality assuming dynamic allocation be used? */ #ifndef HEATSHRINK_DYNAMIC_ALLOC #define HEATSHRINK_DYNAMIC_ALLOC 1 #endif #if HEATSHRINK_DYNAMIC_ALLOC /* Optional replacement of malloc/free */ #define HEATSHRINK_MALLOC(SZ) malloc(SZ) #define HEATSHRINK_FREE(P, SZ) free(P) #else /* Required parameters for static configuration */ #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 #define HEATSHRINK_STATIC_WINDOW_BITS 8 #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 #endif /* Turn on logging for debugging. */ #define HEATSHRINK_DEBUGGING_LOGS 0 /* Use indexing for faster compression. (This requires additional space.) */ #define HEATSHRINK_USE_INDEX 1 #endif heatshrink-0.4.1/heatshrink_decoder.c000066400000000000000000000320311261543250700176510ustar00rootroot00000000000000#include #include #include "heatshrink_decoder.h" /* States for the polling state machine. */ typedef enum { HSDS_TAG_BIT, /* tag bit */ HSDS_YIELD_LITERAL, /* ready to yield literal byte */ HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */ HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */ HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */ HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */ HSDS_YIELD_BACKREF, /* ready to yield back-reference */ } HSD_state; #if HEATSHRINK_DEBUGGING_LOGS #include #include #include #define LOG(...) fprintf(stderr, __VA_ARGS__) #define ASSERT(X) assert(X) static const char *state_names[] = { "tag_bit", "yield_literal", "backref_index_msb", "backref_index_lsb", "backref_count_msb", "backref_count_lsb", "yield_backref", }; #else #define LOG(...) /* no-op */ #define ASSERT(X) /* no-op */ #endif typedef struct { uint8_t *buf; /* output buffer */ size_t buf_size; /* buffer size */ size_t *output_size; /* bytes pushed to buffer, so far */ } output_info; #define NO_BITS ((uint16_t)-1) /* Forward references. */ static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count); static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte); #if HEATSHRINK_DYNAMIC_ALLOC heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, uint8_t window_sz2, uint8_t lookahead_sz2) { if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || (input_buffer_size == 0) || (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || (lookahead_sz2 >= window_sz2)) { return NULL; } size_t buffers_sz = (1 << window_sz2) + input_buffer_size; size_t sz = sizeof(heatshrink_decoder) + buffers_sz; heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz); if (hsd == NULL) { return NULL; } hsd->input_buffer_size = input_buffer_size; hsd->window_sz2 = window_sz2; hsd->lookahead_sz2 = lookahead_sz2; heatshrink_decoder_reset(hsd); LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n", sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size); return hsd; } void heatshrink_decoder_free(heatshrink_decoder *hsd) { size_t buffers_sz = (1 << hsd->window_sz2) + hsd->input_buffer_size; size_t sz = sizeof(heatshrink_decoder) + buffers_sz; HEATSHRINK_FREE(hsd, sz); (void)sz; /* may not be used by free */ } #endif void heatshrink_decoder_reset(heatshrink_decoder *hsd) { size_t buf_sz = 1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd); size_t input_sz = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd); memset(hsd->buffers, 0, buf_sz + input_sz); hsd->state = HSDS_TAG_BIT; hsd->input_size = 0; hsd->input_index = 0; hsd->bit_index = 0x00; hsd->current_byte = 0x00; hsd->output_count = 0; hsd->output_index = 0; hsd->head_index = 0; } /* Copy SIZE bytes into the decoder's input buffer, if it will fit. */ HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, uint8_t *in_buf, size_t size, size_t *input_size) { if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) { return HSDR_SINK_ERROR_NULL; } size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size; if (rem == 0) { *input_size = 0; return HSDR_SINK_FULL; } size = rem < size ? rem : size; LOG("-- sinking %zd bytes\n", size); /* copy into input buffer (at head of buffers) */ memcpy(&hsd->buffers[hsd->input_size], in_buf, size); hsd->input_size += size; *input_size = size; return HSDR_SINK_OK; } /***************** * Decompression * *****************/ #define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD)) #define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD)) // States static HSD_state st_tag_bit(heatshrink_decoder *hsd); static HSD_state st_yield_literal(heatshrink_decoder *hsd, output_info *oi); static HSD_state st_backref_index_msb(heatshrink_decoder *hsd); static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd); static HSD_state st_backref_count_msb(heatshrink_decoder *hsd); static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd); static HSD_state st_yield_backref(heatshrink_decoder *hsd, output_info *oi); HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) { return HSDR_POLL_ERROR_NULL; } *output_size = 0; output_info oi; oi.buf = out_buf; oi.buf_size = out_buf_size; oi.output_size = output_size; while (1) { LOG("-- poll, state is %d (%s), input_size %d\n", hsd->state, state_names[hsd->state], hsd->input_size); uint8_t in_state = hsd->state; switch (in_state) { case HSDS_TAG_BIT: hsd->state = st_tag_bit(hsd); break; case HSDS_YIELD_LITERAL: hsd->state = st_yield_literal(hsd, &oi); break; case HSDS_BACKREF_INDEX_MSB: hsd->state = st_backref_index_msb(hsd); break; case HSDS_BACKREF_INDEX_LSB: hsd->state = st_backref_index_lsb(hsd); break; case HSDS_BACKREF_COUNT_MSB: hsd->state = st_backref_count_msb(hsd); break; case HSDS_BACKREF_COUNT_LSB: hsd->state = st_backref_count_lsb(hsd); break; case HSDS_YIELD_BACKREF: hsd->state = st_yield_backref(hsd, &oi); break; default: return HSDR_POLL_ERROR_UNKNOWN; } /* If the current state cannot advance, check if input or output * buffer are exhausted. */ if (hsd->state == in_state) { if (*output_size == out_buf_size) { return HSDR_POLL_MORE; } return HSDR_POLL_EMPTY; } } } static HSD_state st_tag_bit(heatshrink_decoder *hsd) { uint32_t bits = get_bits(hsd, 1); // get tag bit if (bits == NO_BITS) { return HSDS_TAG_BIT; } else if (bits) { return HSDS_YIELD_LITERAL; } else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) { return HSDS_BACKREF_INDEX_MSB; } else { hsd->output_index = 0; return HSDS_BACKREF_INDEX_LSB; } } static HSD_state st_yield_literal(heatshrink_decoder *hsd, output_info *oi) { /* Emit a repeated section from the window buffer, and add it (again) * to the window buffer. (Note that the repetition can include * itself.)*/ if (*oi->output_size < oi->buf_size) { uint16_t byte = get_bits(hsd, 8); if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */ uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; uint8_t c = byte & 0xFF; LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.'); buf[hsd->head_index++ & mask] = c; push_byte(hsd, oi, c); return HSDS_TAG_BIT; } else { return HSDS_YIELD_LITERAL; } } static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) { uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); ASSERT(bit_ct > 8); uint16_t bits = get_bits(hsd, bit_ct - 8); LOG("-- backref index (msb), got 0x%04x (+1)\n", bits); if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; } hsd->output_index = bits << 8; return HSDS_BACKREF_INDEX_LSB; } static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) { uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8); LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits); if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; } hsd->output_index |= bits; hsd->output_index++; uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); hsd->output_count = 0; return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB; } static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) { uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); ASSERT(br_bit_ct > 8); uint16_t bits = get_bits(hsd, br_bit_ct - 8); LOG("-- backref count (msb), got 0x%04x (+1)\n", bits); if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; } hsd->output_count = bits << 8; return HSDS_BACKREF_COUNT_LSB; } static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) { uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8); LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits); if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; } hsd->output_count |= bits; hsd->output_count++; return HSDS_YIELD_BACKREF; } static HSD_state st_yield_backref(heatshrink_decoder *hsd, output_info *oi) { size_t count = oi->buf_size - *oi->output_size; if (count > 0) { size_t i = 0; if (hsd->output_count < count) count = hsd->output_count; uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; uint16_t neg_offset = hsd->output_index; LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset); ASSERT(neg_offset <= mask + 1); ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd))); for (i=0; ihead_index - neg_offset) & mask]; push_byte(hsd, oi, c); buf[hsd->head_index & mask] = c; hsd->head_index++; LOG(" -- ++ 0x%02x\n", c); } hsd->output_count -= count; if (hsd->output_count == 0) { return HSDS_TAG_BIT; } } return HSDS_YIELD_BACKREF; } /* Get the next COUNT bits from the input buffer, saving incremental progress. * Returns NO_BITS on end of input, or if more than 15 bits are requested. */ static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) { uint16_t accumulator = 0; int i = 0; if (count > 15) { return NO_BITS; } LOG("-- popping %u bit(s)\n", count); /* If we aren't able to get COUNT bits, suspend immediately, because we * don't track how many bits of COUNT we've accumulated before suspend. */ if (hsd->input_size == 0) { if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; } } for (i = 0; i < count; i++) { if (hsd->bit_index == 0x00) { if (hsd->input_size == 0) { LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n", accumulator, accumulator); return NO_BITS; } hsd->current_byte = hsd->buffers[hsd->input_index++]; LOG(" -- pulled byte 0x%02x\n", hsd->current_byte); if (hsd->input_index == hsd->input_size) { hsd->input_index = 0; /* input is exhausted */ hsd->input_size = 0; } hsd->bit_index = 0x80; } accumulator <<= 1; if (hsd->current_byte & hsd->bit_index) { accumulator |= 0x01; if (0) { LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n", accumulator, hsd->bit_index); } } else { if (0) { LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n", accumulator, hsd->bit_index); } } hsd->bit_index >>= 1; } if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); } return accumulator; } HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) { if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; } switch (hsd->state) { case HSDS_TAG_BIT: return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; /* If we want to finish with no input, but are in these states, it's * because the 0-bit padding to the last byte looks like a backref * marker bit followed by all 0s for index and count bits. */ case HSDS_BACKREF_INDEX_LSB: case HSDS_BACKREF_INDEX_MSB: case HSDS_BACKREF_COUNT_LSB: case HSDS_BACKREF_COUNT_MSB: return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; /* If the output stream is padded with 0xFFs (possibly due to being in * flash memory), also explicitly check the input size rather than * uselessly returning MORE but yielding 0 bytes when polling. */ case HSDS_YIELD_LITERAL: return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; default: return HSDR_FINISH_MORE; } } static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) { LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.'); oi->buf[(*oi->output_size)++] = byte; (void)hsd; } heatshrink-0.4.1/heatshrink_decoder.h000066400000000000000000000074111261543250700176620ustar00rootroot00000000000000#ifndef HEATSHRINK_DECODER_H #define HEATSHRINK_DECODER_H #include #include #include "heatshrink_common.h" #include "heatshrink_config.h" typedef enum { HSDR_SINK_OK, /* data sunk, ready to poll */ HSDR_SINK_FULL, /* out of space in internal buffer */ HSDR_SINK_ERROR_NULL=-1, /* NULL argument */ } HSD_sink_res; typedef enum { HSDR_POLL_EMPTY, /* input exhausted */ HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */ HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */ HSDR_POLL_ERROR_UNKNOWN=-2, } HSD_poll_res; typedef enum { HSDR_FINISH_DONE, /* output is done */ HSDR_FINISH_MORE, /* more output remains */ HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */ } HSD_finish_res; #if HEATSHRINK_DYNAMIC_ALLOC #define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \ ((BUF)->input_buffer_size) #define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \ ((BUF)->window_sz2) #define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ ((BUF)->lookahead_sz2) #else #define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \ HEATSHRINK_STATIC_INPUT_BUFFER_SIZE #define HEATSHRINK_DECODER_WINDOW_BITS(_) \ (HEATSHRINK_STATIC_WINDOW_BITS) #define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ (HEATSHRINK_STATIC_LOOKAHEAD_BITS) #endif typedef struct { uint16_t input_size; /* bytes in input buffer */ uint16_t input_index; /* offset to next unprocessed input byte */ uint16_t output_count; /* how many bytes to output */ uint16_t output_index; /* index for bytes to output */ uint16_t head_index; /* head of window buffer */ uint8_t state; /* current state machine node */ uint8_t current_byte; /* current byte of input */ uint8_t bit_index; /* current bit index */ #if HEATSHRINK_DYNAMIC_ALLOC /* Fields that are only used if dynamically allocated. */ uint8_t window_sz2; /* window buffer bits */ uint8_t lookahead_sz2; /* lookahead bits */ uint16_t input_buffer_size; /* input buffer size */ /* Input buffer, then expansion window buffer */ uint8_t buffers[]; #else /* Input buffer, then expansion window buffer */ uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_)) + HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)]; #endif } heatshrink_decoder; #if HEATSHRINK_DYNAMIC_ALLOC /* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes, * an expansion buffer size of 2^WINDOW_SZ2, and a lookahead * size of 2^lookahead_sz2. (The window buffer and lookahead sizes * must match the settings used when the data was compressed.) * Returns NULL on error. */ heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2); /* Free a decoder. */ void heatshrink_decoder_free(heatshrink_decoder *hsd); #endif /* Reset a decoder. */ void heatshrink_decoder_reset(heatshrink_decoder *hsd); /* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to * indicate how many bytes were actually sunk (in case a buffer was filled). */ HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, uint8_t *in_buf, size_t size, size_t *input_size); /* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, uint8_t *out_buf, size_t out_buf_size, size_t *output_size); /* Notify the dencoder that the input stream is finished. * If the return value is HSDR_FINISH_MORE, there is still more output, so * call heatshrink_decoder_poll and repeat. */ HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd); #endif heatshrink-0.4.1/heatshrink_encoder.c000066400000000000000000000500221261543250700176630ustar00rootroot00000000000000#include #include #include #include "heatshrink_encoder.h" typedef enum { HSES_NOT_FULL, /* input buffer not full enough */ HSES_FILLED, /* buffer is full */ HSES_SEARCH, /* searching for patterns */ HSES_YIELD_TAG_BIT, /* yield tag bit */ HSES_YIELD_LITERAL, /* emit literal byte */ HSES_YIELD_BR_INDEX, /* yielding backref index */ HSES_YIELD_BR_LENGTH, /* yielding backref length */ HSES_SAVE_BACKLOG, /* copying buffer to backlog */ HSES_FLUSH_BITS, /* flush bit buffer */ HSES_DONE, /* done */ } HSE_state; #if HEATSHRINK_DEBUGGING_LOGS #include #include #include #define LOG(...) fprintf(stderr, __VA_ARGS__) #define ASSERT(X) assert(X) static const char *state_names[] = { "not_full", "filled", "search", "yield_tag_bit", "yield_literal", "yield_br_index", "yield_br_length", "save_backlog", "flush_bits", "done", }; #else #define LOG(...) /* no-op */ #define ASSERT(X) /* no-op */ #endif // Encoder flags enum { FLAG_IS_FINISHING = 0x01, }; typedef struct { uint8_t *buf; /* output buffer */ size_t buf_size; /* buffer size */ size_t *output_size; /* bytes pushed to buffer, so far */ } output_info; #define MATCH_NOT_FOUND ((uint16_t)-1) static uint16_t get_input_offset(heatshrink_encoder *hse); static uint16_t get_input_buffer_size(heatshrink_encoder *hse); static uint16_t get_lookahead_size(heatshrink_encoder *hse); static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag); static int can_take_byte(output_info *oi); static int is_finishing(heatshrink_encoder *hse); static void save_backlog(heatshrink_encoder *hse); /* Push COUNT (max 8) bits to the output buffer, which has room. */ static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits, output_info *oi); static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi); static void push_literal_byte(heatshrink_encoder *hse, output_info *oi); #if HEATSHRINK_DYNAMIC_ALLOC heatshrink_encoder *heatshrink_encoder_alloc(uint8_t window_sz2, uint8_t lookahead_sz2) { if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || (lookahead_sz2 >= window_sz2)) { return NULL; } /* Note: 2 * the window size is used because the buffer needs to fit * (1 << window_sz2) bytes for the current input, and an additional * (1 << window_sz2) bytes for the previous buffer of input, which * will be scanned for useful backreferences. */ size_t buf_sz = (2 << window_sz2); heatshrink_encoder *hse = HEATSHRINK_MALLOC(sizeof(*hse) + buf_sz); if (hse == NULL) { return NULL; } hse->window_sz2 = window_sz2; hse->lookahead_sz2 = lookahead_sz2; heatshrink_encoder_reset(hse); #if HEATSHRINK_USE_INDEX size_t index_sz = buf_sz*sizeof(uint16_t); hse->search_index = HEATSHRINK_MALLOC(index_sz + sizeof(struct hs_index)); if (hse->search_index == NULL) { HEATSHRINK_FREE(hse, sizeof(*hse) + buf_sz); return NULL; } hse->search_index->size = index_sz; #endif LOG("-- allocated encoder with buffer size of %zu (%u byte input size)\n", buf_sz, get_input_buffer_size(hse)); return hse; } void heatshrink_encoder_free(heatshrink_encoder *hse) { size_t buf_sz = (2 << HEATSHRINK_ENCODER_WINDOW_BITS(hse)); #if HEATSHRINK_USE_INDEX size_t index_sz = sizeof(struct hs_index) + hse->search_index->size; HEATSHRINK_FREE(hse->search_index, index_sz); (void)index_sz; #endif HEATSHRINK_FREE(hse, sizeof(heatshrink_encoder) + buf_sz); (void)buf_sz; } #endif void heatshrink_encoder_reset(heatshrink_encoder *hse) { size_t buf_sz = (2 << HEATSHRINK_ENCODER_WINDOW_BITS(hse)); memset(hse->buffer, 0, buf_sz); hse->input_size = 0; hse->state = HSES_NOT_FULL; hse->match_scan_index = 0; hse->flags = 0; hse->bit_index = 0x80; hse->current_byte = 0x00; hse->match_length = 0; hse->outgoing_bits = 0x0000; hse->outgoing_bits_count = 0; #ifdef LOOP_DETECT hse->loop_detect = (uint32_t)-1; #endif } HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, uint8_t *in_buf, size_t size, size_t *input_size) { if ((hse == NULL) || (in_buf == NULL) || (input_size == NULL)) { return HSER_SINK_ERROR_NULL; } /* Sinking more content after saying the content is done, tsk tsk */ if (is_finishing(hse)) { return HSER_SINK_ERROR_MISUSE; } /* Sinking more content before processing is done */ if (hse->state != HSES_NOT_FULL) { return HSER_SINK_ERROR_MISUSE; } uint16_t write_offset = get_input_offset(hse) + hse->input_size; uint16_t ibs = get_input_buffer_size(hse); uint16_t rem = ibs - hse->input_size; uint16_t cp_sz = rem < size ? rem : size; memcpy(&hse->buffer[write_offset], in_buf, cp_sz); *input_size = cp_sz; hse->input_size += cp_sz; LOG("-- sunk %u bytes (of %zu) into encoder at %d, input buffer now has %u\n", cp_sz, size, write_offset, hse->input_size); if (cp_sz == rem) { LOG("-- internal buffer is now full\n"); hse->state = HSES_FILLED; } return HSER_SINK_OK; } /*************** * Compression * ***************/ static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start, uint16_t end, const uint16_t maxlen, uint16_t *match_length); static void do_indexing(heatshrink_encoder *hse); static HSE_state st_step_search(heatshrink_encoder *hse); static HSE_state st_yield_tag_bit(heatshrink_encoder *hse, output_info *oi); static HSE_state st_yield_literal(heatshrink_encoder *hse, output_info *oi); static HSE_state st_yield_br_index(heatshrink_encoder *hse, output_info *oi); static HSE_state st_yield_br_length(heatshrink_encoder *hse, output_info *oi); static HSE_state st_save_backlog(heatshrink_encoder *hse); static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse, output_info *oi); HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { if ((hse == NULL) || (out_buf == NULL) || (output_size == NULL)) { return HSER_POLL_ERROR_NULL; } if (out_buf_size == 0) { LOG("-- MISUSE: output buffer size is 0\n"); return HSER_POLL_ERROR_MISUSE; } *output_size = 0; output_info oi; oi.buf = out_buf; oi.buf_size = out_buf_size; oi.output_size = output_size; while (1) { LOG("-- polling, state %u (%s), flags 0x%02x\n", hse->state, state_names[hse->state], hse->flags); uint8_t in_state = hse->state; switch (in_state) { case HSES_NOT_FULL: return HSER_POLL_EMPTY; case HSES_FILLED: do_indexing(hse); hse->state = HSES_SEARCH; break; case HSES_SEARCH: hse->state = st_step_search(hse); break; case HSES_YIELD_TAG_BIT: hse->state = st_yield_tag_bit(hse, &oi); break; case HSES_YIELD_LITERAL: hse->state = st_yield_literal(hse, &oi); break; case HSES_YIELD_BR_INDEX: hse->state = st_yield_br_index(hse, &oi); break; case HSES_YIELD_BR_LENGTH: hse->state = st_yield_br_length(hse, &oi); break; case HSES_SAVE_BACKLOG: hse->state = st_save_backlog(hse); break; case HSES_FLUSH_BITS: hse->state = st_flush_bit_buffer(hse, &oi); case HSES_DONE: return HSER_POLL_EMPTY; default: LOG("-- bad state %s\n", state_names[hse->state]); return HSER_POLL_ERROR_MISUSE; } if (hse->state == in_state) { /* Check if output buffer is exhausted. */ if (*output_size == out_buf_size) return HSER_POLL_MORE; } } } HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse) { if (hse == NULL) { return HSER_FINISH_ERROR_NULL; } LOG("-- setting is_finishing flag\n"); hse->flags |= FLAG_IS_FINISHING; if (hse->state == HSES_NOT_FULL) { hse->state = HSES_FILLED; } return hse->state == HSES_DONE ? HSER_FINISH_DONE : HSER_FINISH_MORE; } static HSE_state st_step_search(heatshrink_encoder *hse) { uint16_t window_length = get_input_buffer_size(hse); uint16_t lookahead_sz = get_lookahead_size(hse); uint16_t msi = hse->match_scan_index; LOG("## step_search, scan @ +%d (%d/%d), input size %d\n", msi, hse->input_size + msi, 2*window_length, hse->input_size); bool fin = is_finishing(hse); if (msi > hse->input_size - (fin ? 1 : lookahead_sz)) { /* Current search buffer is exhausted, copy it into the * backlog and await more input. */ LOG("-- end of search @ %d\n", msi); return fin ? HSES_FLUSH_BITS : HSES_SAVE_BACKLOG; } uint16_t input_offset = get_input_offset(hse); uint16_t end = input_offset + msi; uint16_t start = end - window_length; uint16_t max_possible = lookahead_sz; if (hse->input_size - msi < lookahead_sz) { max_possible = hse->input_size - msi; } uint16_t match_length = 0; uint16_t match_pos = find_longest_match(hse, start, end, max_possible, &match_length); if (match_pos == MATCH_NOT_FOUND) { LOG("ss Match not found\n"); hse->match_scan_index++; hse->match_length = 0; return HSES_YIELD_TAG_BIT; } else { LOG("ss Found match of %d bytes at %d\n", match_length, match_pos); hse->match_pos = match_pos; hse->match_length = match_length; ASSERT(match_pos <= 1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse) /*window_length*/); return HSES_YIELD_TAG_BIT; } } static HSE_state st_yield_tag_bit(heatshrink_encoder *hse, output_info *oi) { if (can_take_byte(oi)) { if (hse->match_length == 0) { add_tag_bit(hse, oi, HEATSHRINK_LITERAL_MARKER); return HSES_YIELD_LITERAL; } else { add_tag_bit(hse, oi, HEATSHRINK_BACKREF_MARKER); hse->outgoing_bits = hse->match_pos - 1; hse->outgoing_bits_count = HEATSHRINK_ENCODER_WINDOW_BITS(hse); return HSES_YIELD_BR_INDEX; } } else { return HSES_YIELD_TAG_BIT; /* output is full, continue */ } } static HSE_state st_yield_literal(heatshrink_encoder *hse, output_info *oi) { if (can_take_byte(oi)) { push_literal_byte(hse, oi); return HSES_SEARCH; } else { return HSES_YIELD_LITERAL; } } static HSE_state st_yield_br_index(heatshrink_encoder *hse, output_info *oi) { if (can_take_byte(oi)) { LOG("-- yielding backref index %u\n", hse->match_pos); if (push_outgoing_bits(hse, oi) > 0) { return HSES_YIELD_BR_INDEX; /* continue */ } else { hse->outgoing_bits = hse->match_length - 1; hse->outgoing_bits_count = HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse); return HSES_YIELD_BR_LENGTH; /* done */ } } else { return HSES_YIELD_BR_INDEX; /* continue */ } } static HSE_state st_yield_br_length(heatshrink_encoder *hse, output_info *oi) { if (can_take_byte(oi)) { LOG("-- yielding backref length %u\n", hse->match_length); if (push_outgoing_bits(hse, oi) > 0) { return HSES_YIELD_BR_LENGTH; } else { hse->match_scan_index += hse->match_length; hse->match_length = 0; return HSES_SEARCH; } } else { return HSES_YIELD_BR_LENGTH; } } static HSE_state st_save_backlog(heatshrink_encoder *hse) { LOG("-- saving backlog\n"); save_backlog(hse); return HSES_NOT_FULL; } static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse, output_info *oi) { if (hse->bit_index == 0x80) { LOG("-- done!\n"); return HSES_DONE; } else if (can_take_byte(oi)) { LOG("-- flushing remaining byte (bit_index == 0x%02x)\n", hse->bit_index); oi->buf[(*oi->output_size)++] = hse->current_byte; LOG("-- done!\n"); return HSES_DONE; } else { return HSES_FLUSH_BITS; } } static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag) { LOG("-- adding tag bit: %d\n", tag); push_bits(hse, 1, tag, oi); } static uint16_t get_input_offset(heatshrink_encoder *hse) { return get_input_buffer_size(hse); } static uint16_t get_input_buffer_size(heatshrink_encoder *hse) { return (1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse)); (void)hse; } static uint16_t get_lookahead_size(heatshrink_encoder *hse) { return (1 << HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse)); (void)hse; } static void do_indexing(heatshrink_encoder *hse) { #if HEATSHRINK_USE_INDEX /* Build an index array I that contains flattened linked lists * for the previous instances of every byte in the buffer. * * For example, if buf[200] == 'x', then index[200] will either * be an offset i such that buf[i] == 'x', or a negative offset * to indicate end-of-list. This significantly speeds up matching, * while only using sizeof(uint16_t)*sizeof(buffer) bytes of RAM. * * Future optimization options: * 1. Since any negative value represents end-of-list, the other * 15 bits could be used to improve the index dynamically. * * 2. Likewise, the last lookahead_sz bytes of the index will * not be usable, so temporary data could be stored there to * dynamically improve the index. * */ struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse); int16_t last[256]; memset(last, 0xFF, sizeof(last)); uint8_t * const data = hse->buffer; int16_t * const index = hsi->index; const uint16_t input_offset = get_input_offset(hse); const uint16_t end = input_offset + hse->input_size; for (uint16_t i=0; iflags & FLAG_IS_FINISHING; } static int can_take_byte(output_info *oi) { return *oi->output_size < oi->buf_size; } /* Return the longest match for the bytes at buf[end:end+maxlen] between * buf[start] and buf[end-1]. If no match is found, return -1. */ static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start, uint16_t end, const uint16_t maxlen, uint16_t *match_length) { LOG("-- scanning for match of buf[%u:%u] between buf[%u:%u] (max %u bytes)\n", end, end + maxlen, start, end + maxlen - 1, maxlen); uint8_t *buf = hse->buffer; uint16_t match_maxlen = 0; uint16_t match_index = MATCH_NOT_FOUND; uint16_t len = 0; uint8_t * const needlepoint = &buf[end]; #if HEATSHRINK_USE_INDEX struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse); int16_t pos = hsi->index[end]; while (pos - (int16_t)start >= 0) { uint8_t * const pospoint = &buf[pos]; len = 0; /* Only check matches that will potentially beat the current maxlen. * This is redundant with the index if match_maxlen is 0, but the * added branch overhead to check if it == 0 seems to be worse. */ if (pospoint[match_maxlen] != needlepoint[match_maxlen]) { pos = hsi->index[pos]; continue; } for (len = 1; len < maxlen; len++) { if (pospoint[len] != needlepoint[len]) break; } if (len > match_maxlen) { match_maxlen = len; match_index = pos; if (len == maxlen) { break; } /* won't find better */ } pos = hsi->index[pos]; } #else for (int16_t pos=end - 1; pos - (int16_t)start >= 0; pos--) { uint8_t * const pospoint = &buf[pos]; if ((pospoint[match_maxlen] == needlepoint[match_maxlen]) && (*pospoint == *needlepoint)) { for (len=1; len cmp buf[%d] == 0x%02x against %02x (start %u)\n", pos + len, pospoint[len], needlepoint[len], start); } if (pospoint[len] != needlepoint[len]) { break; } } if (len > match_maxlen) { match_maxlen = len; match_index = pos; if (len == maxlen) { break; } /* don't keep searching */ } } } #endif const size_t break_even_point = (1 + HEATSHRINK_ENCODER_WINDOW_BITS(hse) + HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse)); /* Instead of comparing break_even_point against 8*match_maxlen, * compare match_maxlen against break_even_point/8 to avoid * overflow. Since MIN_WINDOW_BITS and MIN_LOOKAHEAD_BITS are 4 and * 3, respectively, break_even_point/8 will always be at least 1. */ if (match_maxlen > (break_even_point / 8)) { LOG("-- best match: %u bytes at -%u\n", match_maxlen, end - match_index); *match_length = match_maxlen; return end - match_index; } LOG("-- none found\n"); return MATCH_NOT_FOUND; } static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi) { uint8_t count = 0; uint8_t bits = 0; if (hse->outgoing_bits_count > 8) { count = 8; bits = hse->outgoing_bits >> (hse->outgoing_bits_count - 8); } else { count = hse->outgoing_bits_count; bits = hse->outgoing_bits; } if (count > 0) { LOG("-- pushing %d outgoing bits: 0x%02x\n", count, bits); push_bits(hse, count, bits, oi); hse->outgoing_bits_count -= count; } return count; } /* Push COUNT (max 8) bits to the output buffer, which has room. * Bytes are set from the lowest bits, up. */ static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits, output_info *oi) { ASSERT(count <= 8); LOG("++ push_bits: %d bits, input of 0x%02x\n", count, bits); /* If adding a whole byte and at the start of a new output byte, * just push it through whole and skip the bit IO loop. */ if (count == 8 && hse->bit_index == 0x80) { oi->buf[(*oi->output_size)++] = bits; } else { for (int i=count - 1; i>=0; i--) { bool bit = bits & (1 << i); if (bit) { hse->current_byte |= hse->bit_index; } if (0) { LOG(" -- setting bit %d at bit index 0x%02x, byte => 0x%02x\n", bit ? 1 : 0, hse->bit_index, hse->current_byte); } hse->bit_index >>= 1; if (hse->bit_index == 0x00) { hse->bit_index = 0x80; LOG(" > pushing byte 0x%02x\n", hse->current_byte); oi->buf[(*oi->output_size)++] = hse->current_byte; hse->current_byte = 0x00; } } } } static void push_literal_byte(heatshrink_encoder *hse, output_info *oi) { uint16_t processed_offset = hse->match_scan_index - 1; uint16_t input_offset = get_input_offset(hse) + processed_offset; uint8_t c = hse->buffer[input_offset]; LOG("-- yielded literal byte 0x%02x ('%c') from +%d\n", c, isprint(c) ? c : '.', input_offset); push_bits(hse, 8, c, oi); } static void save_backlog(heatshrink_encoder *hse) { size_t input_buf_sz = get_input_buffer_size(hse); uint16_t msi = hse->match_scan_index; /* Copy processed data to beginning of buffer, so it can be * used for future matches. Don't bother checking whether the * input is less than the maximum size, because if it isn't, * we're done anyway. */ uint16_t rem = input_buf_sz - msi; // unprocessed bytes uint16_t shift_sz = input_buf_sz + rem; memmove(&hse->buffer[0], &hse->buffer[input_buf_sz - rem], shift_sz); hse->match_scan_index = 0; hse->input_size -= input_buf_sz - rem; } heatshrink-0.4.1/heatshrink_encoder.h000066400000000000000000000070371261543250700177000ustar00rootroot00000000000000#ifndef HEATSHRINK_ENCODER_H #define HEATSHRINK_ENCODER_H #include #include #include "heatshrink_common.h" #include "heatshrink_config.h" typedef enum { HSER_SINK_OK, /* data sunk into input buffer */ HSER_SINK_ERROR_NULL=-1, /* NULL argument */ HSER_SINK_ERROR_MISUSE=-2, /* API misuse */ } HSE_sink_res; typedef enum { HSER_POLL_EMPTY, /* input exhausted */ HSER_POLL_MORE, /* poll again for more output */ HSER_POLL_ERROR_NULL=-1, /* NULL argument */ HSER_POLL_ERROR_MISUSE=-2, /* API misuse */ } HSE_poll_res; typedef enum { HSER_FINISH_DONE, /* encoding is complete */ HSER_FINISH_MORE, /* more output remaining; use poll */ HSER_FINISH_ERROR_NULL=-1, /* NULL argument */ } HSE_finish_res; #if HEATSHRINK_DYNAMIC_ALLOC #define HEATSHRINK_ENCODER_WINDOW_BITS(HSE) \ ((HSE)->window_sz2) #define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(HSE) \ ((HSE)->lookahead_sz2) #define HEATSHRINK_ENCODER_INDEX(HSE) \ ((HSE)->search_index) struct hs_index { uint16_t size; int16_t index[]; }; #else #define HEATSHRINK_ENCODER_WINDOW_BITS(_) \ (HEATSHRINK_STATIC_WINDOW_BITS) #define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(_) \ (HEATSHRINK_STATIC_LOOKAHEAD_BITS) #define HEATSHRINK_ENCODER_INDEX(HSE) \ (&(HSE)->search_index) struct hs_index { uint16_t size; int16_t index[2 << HEATSHRINK_STATIC_WINDOW_BITS]; }; #endif typedef struct { uint16_t input_size; /* bytes in input buffer */ uint16_t match_scan_index; uint16_t match_length; uint16_t match_pos; uint16_t outgoing_bits; /* enqueued outgoing bits */ uint8_t outgoing_bits_count; uint8_t flags; uint8_t state; /* current state machine node */ uint8_t current_byte; /* current byte of output */ uint8_t bit_index; /* current bit index */ #if HEATSHRINK_DYNAMIC_ALLOC uint8_t window_sz2; /* 2^n size of window */ uint8_t lookahead_sz2; /* 2^n size of lookahead */ #if HEATSHRINK_USE_INDEX struct hs_index *search_index; #endif /* input buffer and / sliding window for expansion */ uint8_t buffer[]; #else #if HEATSHRINK_USE_INDEX struct hs_index search_index; #endif /* input buffer and / sliding window for expansion */ uint8_t buffer[2 << HEATSHRINK_ENCODER_WINDOW_BITS(_)]; #endif } heatshrink_encoder; #if HEATSHRINK_DYNAMIC_ALLOC /* Allocate a new encoder struct and its buffers. * Returns NULL on error. */ heatshrink_encoder *heatshrink_encoder_alloc(uint8_t window_sz2, uint8_t lookahead_sz2); /* Free an encoder. */ void heatshrink_encoder_free(heatshrink_encoder *hse); #endif /* Reset an encoder. */ void heatshrink_encoder_reset(heatshrink_encoder *hse); /* Sink up to SIZE bytes from IN_BUF into the encoder. * INPUT_SIZE is set to the number of bytes actually sunk (in case a * buffer was filled.). */ HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, uint8_t *in_buf, size_t size, size_t *input_size); /* Poll for output from the encoder, copying at most OUT_BUF_SIZE bytes into * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, uint8_t *out_buf, size_t out_buf_size, size_t *output_size); /* Notify the encoder that the input stream is finished. * If the return value is HSER_FINISH_MORE, there is still more output, so * call heatshrink_encoder_poll and repeat. */ HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse); #endif heatshrink-0.4.1/test_heatshrink_dynamic.c000066400000000000000000001064041261543250700207350ustar00rootroot00000000000000#include #include #include #include "heatshrink_encoder.h" #include "heatshrink_decoder.h" #include "greatest.h" #if !HEATSHRINK_DYNAMIC_ALLOC #error Must set HEATSHRINK_DYNAMIC_ALLOC to 1 for dynamic allocation test suite. #endif SUITE(encoding); SUITE(decoding); SUITE(regression); SUITE(integration); #ifdef HEATSHRINK_HAS_THEFT SUITE(properties); #endif static void dump_buf(char *name, uint8_t *buf, uint16_t count) { for (int i=0; iinput_size, 6); ASSERT_EQ(hsd->input_index, 0); heatshrink_decoder_free(hsd); PASS(); } TEST decoder_poll_should_return_empty_if_empty(void) { uint8_t output[256]; size_t out_sz = 0; heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, HEATSHRINK_MIN_WINDOW_BITS, HEATSHRINK_MIN_WINDOW_BITS - 1); HSD_poll_res res = heatshrink_decoder_poll(hsd, output, 256, &out_sz); ASSERT_EQ(HSDR_POLL_EMPTY, res); heatshrink_decoder_free(hsd); PASS(); } TEST decoder_poll_should_reject_null_hsd(void) { uint8_t output[256]; size_t out_sz = 0; HSD_poll_res res = heatshrink_decoder_poll(NULL, output, 256, &out_sz); ASSERT_EQ(HSDR_POLL_ERROR_NULL, res); PASS(); } TEST decoder_poll_should_reject_null_output_buffer(void) { size_t out_sz = 0; heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, HEATSHRINK_MIN_WINDOW_BITS, HEATSHRINK_MIN_WINDOW_BITS - 1); HSD_poll_res res = heatshrink_decoder_poll(hsd, NULL, 256, &out_sz); ASSERT_EQ(HSDR_POLL_ERROR_NULL, res); heatshrink_decoder_free(hsd); PASS(); } TEST decoder_poll_should_reject_null_output_size_pointer(void) { uint8_t output[256]; heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, HEATSHRINK_MIN_WINDOW_BITS, 4); HSD_poll_res res = heatshrink_decoder_poll(hsd, output, 256, NULL); ASSERT_EQ(HSDR_POLL_ERROR_NULL, res); heatshrink_decoder_free(hsd); PASS(); } TEST decoder_poll_should_expand_short_literal(void) { uint8_t input[] = {0xb3, 0x5b, 0xed, 0xe0 }; //"foo" uint8_t output[4]; heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 3); size_t count = 0; HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, sizeof(input), &count); ASSERT_EQ(HSDR_SINK_OK, sres); size_t out_sz = 0; HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, 4, &out_sz); ASSERT_EQ(HSDR_POLL_EMPTY, pres); ASSERT_EQ(3, out_sz); ASSERT_EQ('f', output[0]); ASSERT_EQ('o', output[1]); ASSERT_EQ('o', output[2]); heatshrink_decoder_free(hsd); PASS(); } TEST decoder_poll_should_expand_short_literal_and_backref(void) { uint8_t input[] = {0xb3, 0x5b, 0xed, 0xe0, 0x41, 0x00}; //"foofoo" uint8_t output[6]; heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 6); memset(output, 0, sizeof(*output)); size_t count = 0; HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, sizeof(input), &count); ASSERT_EQ(HSDR_SINK_OK, sres); size_t out_sz = 0; (void)heatshrink_decoder_poll(hsd, output, 6, &out_sz); if (0) dump_buf("output", output, out_sz); ASSERT_EQ_FMT(6, out_sz, "%d"); ASSERT_EQ('f', output[0]); ASSERT_EQ('o', output[1]); ASSERT_EQ('o', output[2]); ASSERT_EQ('f', output[3]); ASSERT_EQ('o', output[4]); ASSERT_EQ('o', output[5]); heatshrink_decoder_free(hsd); PASS(); } TEST decoder_poll_should_expand_short_self_overlapping_backref(void) { /* "aaaaa" == (literal, 1), ('a'), (backref, 1 back, 4 bytes) */ uint8_t input[] = {0xb0, 0x80, 0x01, 0x80}; uint8_t output[6]; uint8_t expected[] = {'a', 'a', 'a', 'a', 'a'}; heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 8, 7); size_t count = 0; HSD_sink_res sres = heatshrink_decoder_sink(hsd, input, sizeof(input), &count); ASSERT_EQ(HSDR_SINK_OK, sres); size_t out_sz = 0; (void)heatshrink_decoder_poll(hsd, output, sizeof(output), &out_sz); if (0) dump_buf("output", output, out_sz); ASSERT_EQ(sizeof(expected), out_sz); for (size_t i=0; iwindow_sz2, cfg->lookahead_sz2); ASSERT(hse); heatshrink_decoder *hsd = heatshrink_decoder_alloc(cfg->decoder_input_buffer_size, cfg->window_sz2, cfg->lookahead_sz2); ASSERT(hsd); size_t comp_sz = input_size + (input_size/2) + 4; size_t decomp_sz = input_size + (input_size/2) + 4; uint8_t *comp = malloc(comp_sz); uint8_t *decomp = malloc(decomp_sz); if (comp == NULL) FAILm("malloc fail"); if (decomp == NULL) FAILm("malloc fail"); memset(comp, 0, comp_sz); memset(decomp, 0, decomp_sz); size_t count = 0; if (cfg->log_lvl > 1) { printf("\n^^ COMPRESSING\n"); dump_buf("input", input, input_size); } size_t sunk = 0; size_t polled = 0; while (sunk < input_size) { HSE_sink_res esres = heatshrink_encoder_sink(hse, &input[sunk], input_size - sunk, &count); ASSERT(esres >= 0); sunk += count; if (cfg->log_lvl > 1) printf("^^ sunk %zd\n", count); if (sunk == input_size) { ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(hse)); } HSE_poll_res pres; do { /* "turn the crank" */ pres = heatshrink_encoder_poll(hse, &comp[polled], comp_sz - polled, &count); ASSERT(pres >= 0); polled += count; if (cfg->log_lvl > 1) printf("^^ polled %zd\n", count); } while (pres == HSER_POLL_MORE); ASSERT_EQ(HSER_POLL_EMPTY, pres); if (polled >= comp_sz) FAILm("compression should never expand that much"); if (sunk == input_size) { ASSERT_EQ(HSER_FINISH_DONE, heatshrink_encoder_finish(hse)); } } if (cfg->log_lvl > 0) printf("in: %u compressed: %zu ", input_size, polled); size_t compressed_size = polled; sunk = 0; polled = 0; if (cfg->log_lvl > 1) { printf("\n^^ DECOMPRESSING\n"); dump_buf("comp", comp, compressed_size); } while (sunk < compressed_size) { ASSERT(heatshrink_decoder_sink(hsd, &comp[sunk], compressed_size - sunk, &count) >= 0); sunk += count; if (cfg->log_lvl > 1) printf("^^ sunk %zd\n", count); if (sunk == compressed_size) { ASSERT_EQ(HSDR_FINISH_MORE, heatshrink_decoder_finish(hsd)); } HSD_poll_res pres; do { pres = heatshrink_decoder_poll(hsd, &decomp[polled], decomp_sz - polled, &count); ASSERT(pres >= 0); ASSERT(count > 0); polled += count; if (cfg->log_lvl > 1) printf("^^ polled %zd\n", count); } while (pres == HSDR_POLL_MORE); ASSERT_EQ(HSDR_POLL_EMPTY, pres); if (sunk == compressed_size) { HSD_finish_res fres = heatshrink_decoder_finish(hsd); ASSERT_EQ(HSDR_FINISH_DONE, fres); } if (polled > input_size) { printf("\nExpected %zd, got %zu\n", (size_t)input_size, polled); FAILm("Decompressed data is larger than original input"); } } if (cfg->log_lvl > 0) printf("decompressed: %zu\n", polled); if (polled != input_size) { FAILm("Decompressed length does not match original input length"); } if (cfg->log_lvl > 1) dump_buf("decomp", decomp, polled); for (uint32_t i=0; i out[%d] == 0x%02x ('%c') %c\n", j, input[j], isprint(input[j]) ? input[j] : '.', j, decomp[j], isprint(decomp[j]) ? decomp[j] : '.', input[j] == decomp[j] ? ' ' : 'X'); } } } ASSERT_EQ(input[i], decomp[i]); } free(comp); free(decomp); heatshrink_encoder_free(hse); heatshrink_decoder_free(hsd); PASS(); } TEST data_without_duplication_should_match(void) { uint8_t input[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; cfg_info cfg; cfg.log_lvl = 0; cfg.window_sz2 = 8; cfg.lookahead_sz2 = 3; cfg.decoder_input_buffer_size = 256; return compress_and_expand_and_check(input, sizeof(input), &cfg); } TEST data_with_simple_repetition_should_compress_and_decompress_properly(void) { uint8_t input[] = {'a', 'b', 'c', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; cfg_info cfg; cfg.log_lvl = 0; cfg.window_sz2 = 8; cfg.lookahead_sz2 = 3; cfg.decoder_input_buffer_size = 256; return compress_and_expand_and_check(input, sizeof(input), &cfg); } TEST data_without_duplication_should_match_with_absurdly_tiny_buffers(void) { heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 3); heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 8, 3); uint8_t input[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; uint8_t comp[60]; uint8_t decomp[60]; size_t count = 0; int log = 0; if (log) dump_buf("input", input, sizeof(input)); for (uint32_t i=0; i= 0); } ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(hse)); size_t packed_count = 0; do { ASSERT(heatshrink_encoder_poll(hse, &comp[packed_count], 1, &count) >= 0); packed_count += count; } while (heatshrink_encoder_finish(hse) == HSER_FINISH_MORE); if (log) dump_buf("comp", comp, packed_count); for (uint32_t i=0; i= 0); } for (uint32_t i=0; i= 0); } if (log) dump_buf("decomp", decomp, sizeof(input)); for (uint32_t i=0; i= 0); } ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(hse)); size_t packed_count = 0; do { ASSERT(heatshrink_encoder_poll(hse, &comp[packed_count], 1, &count) >= 0); packed_count += count; } while (heatshrink_encoder_finish(hse) == HSER_FINISH_MORE); if (log) dump_buf("comp", comp, packed_count); for (uint32_t i=0; i= 0); } for (uint32_t i=0; i= 0); } if (log) dump_buf("decomp", decomp, sizeof(input)); for (uint32_t i=0; ilog_lvl > 0) { printf("\n-- size %u, seed %u, input buf %zu\n", size, seed, cfg->decoder_input_buffer_size); } fill_with_pseudorandom_letters(input, size, seed); return compress_and_expand_and_check(input, size, cfg); } TEST small_input_buffer_should_not_impact_decoder_correctness(void) { int size = 5; uint8_t input[size]; cfg_info cfg; cfg.log_lvl = 0; cfg.window_sz2 = 8; cfg.lookahead_sz2 = 3; cfg.decoder_input_buffer_size = 5; for (uint16_t i=0; i= 19901L printf("\n\nFuzzing (single-byte sizes):\n"); for (uint8_t lsize=3; lsize < 8; lsize++) { for (uint32_t size=1; size < 128*1024L; size <<= 1) { if (GREATEST_IS_VERBOSE()) printf(" -- size %u\n", size); for (uint16_t ibs=32; ibs<=8192; ibs <<= 1) { /* input buffer size */ if (GREATEST_IS_VERBOSE()) printf(" -- input buffer %u\n", ibs); for (uint32_t seed=1; seed<=10; seed++) { if (GREATEST_IS_VERBOSE()) printf(" -- seed %u\n", seed); cfg_info cfg; cfg.log_lvl = 0; cfg.window_sz2 = 8; cfg.lookahead_sz2 = lsize; cfg.decoder_input_buffer_size = ibs; RUN_TESTp(pseudorandom_data_should_match, size, seed, &cfg); } } } } printf("\nFuzzing (multi-byte sizes):\n"); for (uint8_t lsize=6; lsize < 9; lsize++) { for (uint32_t size=1; size < 128*1024L; size <<= 1) { if (GREATEST_IS_VERBOSE()) printf(" -- size %u\n", size); for (uint16_t ibs=32; ibs<=8192; ibs <<= 1) { /* input buffer size */ if (GREATEST_IS_VERBOSE()) printf(" -- input buffer %u\n", ibs); for (uint32_t seed=1; seed<=10; seed++) { if (GREATEST_IS_VERBOSE()) printf(" -- seed %u\n", seed); cfg_info cfg; cfg.log_lvl = 0; cfg.window_sz2 = 11; cfg.lookahead_sz2 = lsize; cfg.decoder_input_buffer_size = ibs; RUN_TESTp(pseudorandom_data_should_match, size, seed, &cfg); } } } } #endif } /* Add all the definitions that need to be in the test runner's main file. */ GREATEST_MAIN_DEFS(); int main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */ RUN_SUITE(encoding); RUN_SUITE(decoding); RUN_SUITE(regression); RUN_SUITE(integration); #ifdef HEATSHRINK_HAS_THEFT RUN_SUITE(properties); #endif GREATEST_MAIN_END(); /* display results */ } heatshrink-0.4.1/test_heatshrink_dynamic_theft.c000066400000000000000000000526431261543250700221340ustar00rootroot00000000000000#include "heatshrink_config.h" #ifdef HEATSHRINK_HAS_THEFT #include #include #include #include #include #include "heatshrink_encoder.h" #include "heatshrink_decoder.h" #include "greatest.h" #include "theft.h" #if !HEATSHRINK_DYNAMIC_ALLOC #error Must set HEATSHRINK_DYNAMIC_ALLOC to 1 for this test suite. #endif SUITE(properties); // Buffers, 16 MB each #define BUF_SIZE (16 * 1024L * 1024) /* static uint8_t *input; */ static uint8_t *output; static uint8_t *output2; typedef struct { int limit; int fails; int dots; uint16_t decoder_buffer_size; } test_env; typedef struct { size_t size; uint8_t buf[]; } rbuf; static void *rbuf_alloc_cb(struct theft *t, theft_hash seed, void *env) { test_env *te = (test_env *)env; //printf("seed is 0x%016llx\n", seed); size_t sz = (size_t)(seed % te->limit) + 1; rbuf *r = malloc(sizeof(rbuf) + sz); if (r == NULL) { return THEFT_ERROR; } r->size = sz; for (size_t i = 0; i < sz; i += sizeof(theft_hash)) { theft_hash s = theft_random(t); for (uint8_t b = 0; b < sizeof(theft_hash); b++) { if (i + b >= sz) { break; } r->buf[i + b] = (uint8_t) (s >> (8*b)) & 0xff; } } return r; } static void rbuf_free_cb(void *instance, void *env) { free(instance); (void)env; } static uint64_t rbuf_hash_cb(void *instance, void *env) { rbuf *r = (rbuf *)instance; (void)env; return theft_hash_onepass(r->buf, r->size); } /* Make a copy of a buffer, keeping NEW_SZ bytes starting at OFFSET. */ static void *copy_rbuf_subset(rbuf *cur, size_t new_sz, size_t byte_offset) { if (new_sz == 0) { return THEFT_DEAD_END; } rbuf *nr = malloc(sizeof(rbuf) + new_sz); if (nr == NULL) { return THEFT_ERROR; } nr->size = new_sz; memcpy(nr->buf, &cur->buf[byte_offset], new_sz); /* printf("%zu -> %zu\n", cur->size, new_sz); */ return nr; } /* Make a copy of a buffer, but only PORTION, starting OFFSET in * (e.g. the third quarter is (0.25 at +0.75). Rounds to ints. */ static void *copy_rbuf_percent(rbuf *cur, float portion, float offset) { size_t new_sz = cur->size * portion; size_t byte_offset = (size_t)(cur->size * offset); return copy_rbuf_subset(cur, new_sz, byte_offset); } /* How to shrink a random buffer to a simpler one. */ static void *rbuf_shrink_cb(void *instance, uint32_t tactic, void *env) { rbuf *cur = (rbuf *)instance; if (tactic == 0) { /* first half */ return copy_rbuf_percent(cur, 0.5, 0); } else if (tactic == 1) { /* second half */ return copy_rbuf_percent(cur, 0.5, 0.5); } else if (tactic <= 18) { /* drop 1-16 bytes at start */ const int last_tactic = 1; const size_t drop = tactic - last_tactic; if (cur->size < drop) { return THEFT_DEAD_END; } return copy_rbuf_subset(cur, cur->size - drop, drop); } else if (tactic <= 34) { /* drop 1-16 bytes at end */ const int last_tactic = 18; const size_t drop = tactic - last_tactic; if (cur->size < drop) { return THEFT_DEAD_END; } return copy_rbuf_subset(cur, cur->size - drop, 0); } else if (tactic == 35) { /* Divide every byte by 2, saturating at 0 */ rbuf *cp = copy_rbuf_percent(cur, 1, 0); if (cp == NULL) { return THEFT_ERROR; } for (size_t i = 0; i < cp->size; i++) { cp->buf[i] /= 2; } return cp; } else if (tactic == 36) { /* subtract 1 from every byte, saturating at 0 */ rbuf *cp = copy_rbuf_percent(cur, 1, 0); if (cp == NULL) { return THEFT_ERROR; } for (size_t i = 0; i < cp->size; i++) { if (cp->buf[i] > 0) { cp->buf[i]--; } } return cp; } else { (void)env; return THEFT_NO_MORE_TACTICS; } return THEFT_NO_MORE_TACTICS; } static void rbuf_print_cb(FILE *f, void *instance, void *env) { rbuf *r = (rbuf *)instance; (void)env; fprintf(f, "buf[%zd]:\n ", r->size); uint8_t bytes = 0; for (size_t i = 0; i < r->size; i++) { fprintf(f, "%02x", r->buf[i]); bytes++; if (bytes == 16) { fprintf(f, "\n "); bytes = 0; } } fprintf(f, "\n"); } static struct theft_type_info rbuf_info = { .alloc = rbuf_alloc_cb, .free = rbuf_free_cb, .hash = rbuf_hash_cb, .shrink = rbuf_shrink_cb, .print = rbuf_print_cb, }; static void *window_alloc_cb(struct theft *t, theft_seed seed, void *env) { uint8_t *window = malloc(sizeof(uint8_t)); if (window == NULL) { return THEFT_ERROR; } *window = (seed % (HEATSHRINK_MAX_WINDOW_BITS - HEATSHRINK_MIN_WINDOW_BITS)) + HEATSHRINK_MIN_WINDOW_BITS; (void)t; (void)env; return window; } static void window_free_cb(void *instance, void *env) { free(instance); (void)env; } static theft_hash window_hash_cb(void *instance, void *env) { (void)env; return *(uint8_t *)instance; } static void window_print_cb(FILE *f, void *instance, void *env) { fprintf(f, "%u", (*(uint8_t *)instance)); (void)env; } static struct theft_type_info window_info = { .alloc = window_alloc_cb, .free = window_free_cb, .hash = window_hash_cb, .print = window_print_cb, }; static void *lookahead_alloc_cb(struct theft *t, theft_seed seed, void *env) { uint8_t *window = malloc(sizeof(uint8_t)); if (window == NULL) { return THEFT_ERROR; } *window = (seed % (HEATSHRINK_MAX_WINDOW_BITS - HEATSHRINK_MIN_LOOKAHEAD_BITS)) + HEATSHRINK_MIN_LOOKAHEAD_BITS; (void)t; (void)env; return window; } static void lookahead_free_cb(void *instance, void *env) { free(instance); (void)env; } static theft_hash lookahead_hash_cb(void *instance, void *env) { (void)env; return *(uint8_t *)instance; } static void lookahead_print_cb(FILE *f, void *instance, void *env) { fprintf(f, "%u", (*(uint8_t *)instance)); (void)env; } static struct theft_type_info lookahead_info = { .alloc = lookahead_alloc_cb, .free = lookahead_free_cb, .hash = lookahead_hash_cb, .print = lookahead_print_cb, }; static void *decoder_buf_alloc_cb(struct theft *t, theft_seed seed, void *env) { uint16_t *size = malloc(sizeof(uint16_t)); if (size == NULL) { return THEFT_ERROR; } /* Get a random uint16_t, and only keep bottom 0-15 bits at random, * to bias towards smaller buffers. */ *size = seed & 0xFFFF; *size &= (1 << (theft_random(t) & 0xF)) - 1; if (*size == 0) { *size = 1; } // round up to 1 (void)t; (void)env; return size; } static void decoder_buf_free_cb(void *instance, void *env) { free(instance); (void)env; } static theft_hash decoder_buf_hash_cb(void *instance, void *env) { (void)env; return *(uint16_t *)instance; } static void decoder_buf_print_cb(FILE *f, void *instance, void *env) { fprintf(f, "%u", (*(uint16_t *)instance)); (void)env; } static struct theft_type_info decoder_buf_info = { .alloc = decoder_buf_alloc_cb, .free = decoder_buf_free_cb, .hash = decoder_buf_hash_cb, .print = decoder_buf_print_cb, }; static theft_progress_callback_res progress_cb(struct theft_trial_info *info, void *env) { test_env *te = (test_env *)env; if ((info->trial & 0xff) == 0) { printf("."); fflush(stdout); te->dots++; if (te->dots == 64) { printf("\n"); te->dots = 0; } } if (info->status == THEFT_TRIAL_FAIL) { te->fails++; rbuf *cur = info->args[0]; if (cur->size < 5) { return THEFT_PROGRESS_HALT; } } if (te->fails > 10) { return THEFT_PROGRESS_HALT; } return THEFT_PROGRESS_CONTINUE; } /* For an arbitrary input buffer, it should never get stuck in a * state where the data has been sunk but no data can be polled. */ static theft_trial_res prop_should_not_get_stuck(void *input, void *window, void *lookahead) { assert(window); uint8_t window_sz2 = *(uint8_t *)window; assert(lookahead); uint8_t lookahead_sz2 = *(uint8_t *)lookahead; if (lookahead_sz2 >= window_sz2) { return THEFT_TRIAL_SKIP; } heatshrink_decoder *hsd = heatshrink_decoder_alloc((64 * 1024L) - 1, window_sz2, lookahead_sz2); if (hsd == NULL) { return THEFT_TRIAL_ERROR; } rbuf *r = (rbuf *)input; size_t count = 0; HSD_sink_res sres = heatshrink_decoder_sink(hsd, r->buf, r->size, &count); if (sres != HSDR_SINK_OK) { return THEFT_TRIAL_ERROR; } size_t out_sz = 0; HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, BUF_SIZE, &out_sz); if (pres != HSDR_POLL_EMPTY) { return THEFT_TRIAL_FAIL; } HSD_finish_res fres = heatshrink_decoder_finish(hsd); heatshrink_decoder_free(hsd); if (fres != HSDR_FINISH_DONE) { return THEFT_TRIAL_FAIL; } return THEFT_TRIAL_PASS; } static bool get_time_seed(theft_seed *seed) { struct timeval tv; if (-1 == gettimeofday(&tv, NULL)) { return false; } *seed = (theft_seed)((tv.tv_sec << 32) | tv.tv_usec); /* printf("seed is 0x%016llx\n", *seed); */ return true; } TEST decoder_fuzzing_should_not_detect_stuck_state(void) { // Get a random number seed based on the time theft_seed seed; if (!get_time_seed(&seed)) { FAIL(); } /* Pass the max buffer size for this property (4 KB) in a closure */ test_env env = { .limit = 1 << 12 }; theft_seed always_seeds = { 0xe87bb1f61032a061 }; struct theft *t = theft_init(0); struct theft_cfg cfg = { .name = __func__, .fun = prop_should_not_get_stuck, .type_info = { &rbuf_info, &window_info, &lookahead_info }, .seed = seed, .trials = 100000, .progress_cb = progress_cb, .env = &env, .always_seeds = &always_seeds, .always_seed_count = 1, }; theft_run_res res = theft_run(t, &cfg); theft_free(t); printf("\n"); GREATEST_ASSERT_EQm("should_not_get_stuck", THEFT_RUN_PASS, res); PASS(); } static bool do_compress(heatshrink_encoder *hse, uint8_t *input, size_t input_size, uint8_t *output, size_t output_buf_size, size_t *output_used_size) { size_t sunk = 0; size_t polled = 0; while (sunk < input_size) { size_t sunk_size = 0; HSE_sink_res esres = heatshrink_encoder_sink(hse, &input[sunk], input_size - sunk, &sunk_size); if (esres != HSER_SINK_OK) { return false; } sunk += sunk_size; HSE_poll_res epres = HSER_POLL_ERROR_NULL; do { size_t poll_size = 0; epres = heatshrink_encoder_poll(hse, &output[polled], output_buf_size - polled, &poll_size); if (epres < 0) { return false; } polled += poll_size; } while (epres == HSER_POLL_MORE); } HSE_finish_res efres = heatshrink_encoder_finish(hse); while (efres == HSER_FINISH_MORE) { size_t poll_size = 0; HSE_poll_res epres = heatshrink_encoder_poll(hse, &output[polled], output_buf_size - polled, &poll_size); if (epres < 0) { return false; } polled += poll_size; efres = heatshrink_encoder_finish(hse); } *output_used_size = polled; return efres == HSER_FINISH_DONE; } static bool do_decompress(heatshrink_decoder *hsd, uint8_t *input, size_t input_size, uint8_t *output, size_t output_buf_size, size_t *output_used_size) { size_t sunk = 0; size_t polled = 0; while (sunk < input_size) { size_t sunk_size = 0; HSD_sink_res dsres = heatshrink_decoder_sink(hsd, &input[sunk], input_size - sunk, &sunk_size); if (dsres != HSDR_SINK_OK) { return false; } sunk += sunk_size; HSD_poll_res dpres = HSDR_POLL_ERROR_NULL; do { size_t poll_size = 0; dpres = heatshrink_decoder_poll(hsd, &output[polled], output_buf_size - polled, &poll_size); if (dpres < 0) { return false; } polled += poll_size; } while (dpres == HSDR_POLL_MORE); } HSD_finish_res dfres = heatshrink_decoder_finish(hsd); while (dfres == HSDR_FINISH_MORE) { size_t poll_size = 0; HSD_poll_res dpres = heatshrink_decoder_poll(hsd, &output[polled], output_buf_size - polled, &poll_size); if (dpres < 0) { return false; } polled += poll_size; dfres = heatshrink_decoder_finish(hsd); } *output_used_size = polled; return dfres == HSDR_FINISH_DONE; } static theft_trial_res prop_encoded_and_decoded_data_should_match(void *input, void *window, void *lookahead, void *decoder_buffer_size) { assert(window); uint8_t window_sz2 = *(uint8_t *)window; assert(lookahead); uint8_t lookahead_sz2 = *(uint8_t *)lookahead; if (lookahead_sz2 >= window_sz2) { return THEFT_TRIAL_SKIP; } heatshrink_encoder *hse = heatshrink_encoder_alloc(window_sz2, lookahead_sz2); if (hse == NULL) { return THEFT_TRIAL_ERROR; } assert(decoder_buffer_size); uint16_t buf_size = *(uint16_t *)decoder_buffer_size; heatshrink_decoder *hsd = heatshrink_decoder_alloc(buf_size, window_sz2, lookahead_sz2); if (hsd == NULL) { return THEFT_TRIAL_ERROR; } rbuf *r = (rbuf *)input; size_t compressed_size = 0; if (!do_compress(hse, r->buf, r->size, output, BUF_SIZE, &compressed_size)) { return THEFT_TRIAL_ERROR; } size_t decompressed_size = 0; if (!do_decompress(hsd, output, compressed_size, output2, BUF_SIZE, &decompressed_size)) { return THEFT_TRIAL_ERROR; } // verify decompressed output matches original input if (r->size != decompressed_size) { return THEFT_TRIAL_FAIL; } if (0 != memcmp(output2, r->buf, decompressed_size)) { return THEFT_TRIAL_FAIL; } heatshrink_encoder_free(hse); heatshrink_decoder_free(hsd); return THEFT_TRIAL_PASS; } TEST encoded_and_decoded_data_should_match(void) { test_env env = { .limit = 1 << 11 }; theft_seed seed; if (!get_time_seed(&seed)) { FAIL(); } struct theft *t = theft_init(0); struct theft_cfg cfg = { .name = __func__, .fun = prop_encoded_and_decoded_data_should_match, .type_info = { &rbuf_info, &window_info, &lookahead_info, &decoder_buf_info, }, .seed = seed, .trials = 1000000, .env = &env, .progress_cb = progress_cb, }; theft_run_res res = theft_run(t, &cfg); theft_free(t); printf("\n"); ASSERT_EQ(THEFT_RUN_PASS, res); PASS(); } static size_t ceil_nine_eighths(size_t sz) { return sz + sz/8 + (sz & 0x07 ? 1 : 0); } static theft_trial_res prop_encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst(void *input, void *window, void *lookahead) { assert(window); uint8_t window_sz2 = *(uint8_t *)window; assert(lookahead); uint8_t lookahead_sz2 = *(uint8_t *)lookahead; if (lookahead_sz2 >= window_sz2) { return THEFT_TRIAL_SKIP; } heatshrink_encoder *hse = heatshrink_encoder_alloc(window_sz2, lookahead_sz2); if (hse == NULL) { return THEFT_TRIAL_ERROR; } rbuf *r = (rbuf *)input; size_t compressed_size = 0; if (!do_compress(hse, r->buf, r->size, output, BUF_SIZE, &compressed_size)) { return THEFT_TRIAL_ERROR; } size_t ceil_9_8s = ceil_nine_eighths(r->size); if (compressed_size > ceil_9_8s) { return THEFT_TRIAL_FAIL; } heatshrink_encoder_free(hse); return THEFT_TRIAL_PASS; } TEST encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst(void) { test_env env = { .limit = 1 << 11 }; theft_seed seed; if (!get_time_seed(&seed)) { FAIL(); } struct theft *t = theft_init(0); struct theft_cfg cfg = { .name = __func__, .fun = prop_encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst, .type_info = { &rbuf_info, &window_info, &lookahead_info }, .seed = seed, .trials = 10000, .env = &env, .progress_cb = progress_cb, }; theft_run_res res = theft_run(t, &cfg); theft_free(t); printf("\n"); ASSERT_EQ(THEFT_RUN_PASS, res); PASS(); } static theft_trial_res prop_encoder_should_always_make_progress(void *instance, void *window, void *lookahead) { assert(window); uint8_t window_sz2 = *(uint8_t *)window; assert(lookahead); uint8_t lookahead_sz2 = *(uint8_t *)lookahead; if (lookahead_sz2 >= window_sz2) { return THEFT_TRIAL_SKIP; } heatshrink_encoder *hse = heatshrink_encoder_alloc(window_sz2, lookahead_sz2); if (hse == NULL) { return THEFT_TRIAL_ERROR; } rbuf *r = (rbuf *)instance; size_t sunk = 0; int no_progress = 0; while (1) { if (sunk < r->size) { size_t input_size = 0; HSE_sink_res esres = heatshrink_encoder_sink(hse, &r->buf[sunk], r->size - sunk, &input_size); if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; } sunk += input_size; } else { HSE_finish_res efres = heatshrink_encoder_finish(hse); if (efres == HSER_FINISH_DONE) { break; } else if (efres != HSER_FINISH_MORE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } } size_t output_size = 0; HSE_poll_res epres = heatshrink_encoder_poll(hse, output, BUF_SIZE, &output_size); if (epres < 0) { return THEFT_TRIAL_ERROR; } if (output_size == 0 && sunk == r->size) { no_progress++; if (no_progress > 2) { return THEFT_TRIAL_FAIL; } } else { no_progress = 0; } } heatshrink_encoder_free(hse); return THEFT_TRIAL_PASS; } TEST encoder_should_always_make_progress(void) { test_env env = { .limit = 1 << 15 }; theft_seed seed; if (!get_time_seed(&seed)) { FAIL(); } struct theft *t = theft_init(0); struct theft_cfg cfg = { .name = __func__, .fun = prop_encoder_should_always_make_progress, .type_info = { &rbuf_info, &window_info, &lookahead_info }, .seed = seed, .trials = 10000, .env = &env, .progress_cb = progress_cb, }; theft_run_res res = theft_run(t, &cfg); theft_free(t); printf("\n"); ASSERT_EQ(THEFT_RUN_PASS, res); PASS(); } static theft_trial_res prop_decoder_should_always_make_progress(void *instance, void *window, void *lookahead) { assert(window); uint8_t window_sz2 = *(uint8_t *)window; assert(lookahead); uint8_t lookahead_sz2 = *(uint8_t *)lookahead; if (lookahead_sz2 >= window_sz2) { return THEFT_TRIAL_SKIP; } heatshrink_decoder *hsd = heatshrink_decoder_alloc(512, window_sz2, lookahead_sz2); if (hsd == NULL) { fprintf(stderr, "Failed to alloc decoder\n"); return THEFT_TRIAL_ERROR; } rbuf *r = (rbuf *)instance; size_t sunk = 0; int no_progress = 0; while (1) { if (sunk < r->size) { size_t input_size = 0; HSD_sink_res sres = heatshrink_decoder_sink(hsd, &r->buf[sunk], r->size - sunk, &input_size); if (sres < 0) { fprintf(stderr, "Sink error %d\n", sres); return THEFT_TRIAL_ERROR; } sunk += input_size; } else { HSD_finish_res fres = heatshrink_decoder_finish(hsd); if (fres == HSDR_FINISH_DONE) { break; } else if (fres != HSDR_FINISH_MORE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; } } size_t output_size = 0; HSD_poll_res pres = heatshrink_decoder_poll(hsd, output, sizeof(output), &output_size); if (pres < 0) { fprintf(stderr, "poll error: %d\n", pres); return THEFT_TRIAL_ERROR; } if (output_size == 0 && sunk == r->size) { no_progress++; if (no_progress > 2) { return THEFT_TRIAL_FAIL; } } else { no_progress = 0; } } heatshrink_decoder_free(hsd); return THEFT_TRIAL_PASS; } TEST decoder_should_always_make_progress(void) { test_env env = { .limit = 1 << 15 }; theft_seed seed; if (!get_time_seed(&seed)) { FAIL(); } struct theft *t = theft_init(0); struct theft_cfg cfg = { .name = __func__, .fun = prop_decoder_should_always_make_progress, .type_info = { &rbuf_info, &window_info, &lookahead_info }, .seed = seed, .trials = 10000, .env = &env, .progress_cb = progress_cb, }; theft_run_res res = theft_run(t, &cfg); theft_free(t); printf("\n"); ASSERT_EQ(THEFT_RUN_PASS, res); PASS(); } static void setup_cb(void *udata) { (void)udata; memset(output, 0, BUF_SIZE); memset(output2, 0, BUF_SIZE); } SUITE(properties) { output = malloc(BUF_SIZE); assert(output); output2 = malloc(BUF_SIZE); assert(output2); GREATEST_SET_SETUP_CB(setup_cb, NULL); RUN_TEST(decoder_fuzzing_should_not_detect_stuck_state); RUN_TEST(encoded_and_decoded_data_should_match); RUN_TEST(encoding_data_should_never_increase_it_by_more_than_an_eighth_at_worst); RUN_TEST(encoder_should_always_make_progress); RUN_TEST(decoder_should_always_make_progress); free(output); free(output2); } #else struct because_iso_c_requires_at_least_one_declaration; #endif heatshrink-0.4.1/test_heatshrink_static.c000066400000000000000000000134151261543250700205770ustar00rootroot00000000000000#include #include #include "heatshrink_encoder.h" #include "heatshrink_decoder.h" #include "greatest.h" #if HEATSHRINK_DYNAMIC_ALLOC #error HEATSHRINK_DYNAMIC_ALLOC must be false for static allocation test suite. #endif SUITE(integration); /* The majority of the tests are in test_heatshrink_dynamic, because that allows * instantiating encoders/decoders with different settings at run-time. */ static heatshrink_encoder hse; static heatshrink_decoder hsd; static void fill_with_pseudorandom_letters(uint8_t *buf, uint16_t size, uint32_t seed) { uint64_t rn = 9223372036854775783; /* prime under 2^64 */ for (int i=0; i 1) { printf("\n^^ COMPRESSING\n"); dump_buf("input", input, input_size); } uint32_t sunk = 0; uint32_t polled = 0; while (sunk < input_size) { ASSERT(heatshrink_encoder_sink(&hse, &input[sunk], input_size - sunk, &count) >= 0); sunk += count; if (log_lvl > 1) printf("^^ sunk %zd\n", count); if (sunk == input_size) { ASSERT_EQ(HSER_FINISH_MORE, heatshrink_encoder_finish(&hse)); } HSE_poll_res pres; do { /* "turn the crank" */ pres = heatshrink_encoder_poll(&hse, &comp[polled], comp_sz - polled, &count); ASSERT(pres >= 0); polled += count; if (log_lvl > 1) printf("^^ polled %zd\n", count); } while (pres == HSER_POLL_MORE); ASSERT_EQ(HSER_POLL_EMPTY, pres); if (polled >= comp_sz) FAILm("compression should never expand that much"); if (sunk == input_size) { ASSERT_EQ(HSER_FINISH_DONE, heatshrink_encoder_finish(&hse)); } } if (log_lvl > 0) printf("in: %u compressed: %u ", input_size, polled); uint32_t compressed_size = polled; sunk = 0; polled = 0; if (log_lvl > 1) { printf("\n^^ DECOMPRESSING\n"); dump_buf("comp", comp, compressed_size); } while (sunk < compressed_size) { ASSERT(heatshrink_decoder_sink(&hsd, &comp[sunk], compressed_size - sunk, &count) >= 0); sunk += count; if (log_lvl > 1) printf("^^ sunk %zd\n", count); if (sunk == compressed_size) { ASSERT_EQ(HSDR_FINISH_MORE, heatshrink_decoder_finish(&hsd)); } HSD_poll_res pres; do { pres = heatshrink_decoder_poll(&hsd, &decomp[polled], decomp_sz - polled, &count); ASSERT(pres >= 0); polled += count; if (log_lvl > 1) printf("^^ polled %zd\n", count); } while (pres == HSDR_POLL_MORE); ASSERT_EQ(HSDR_POLL_EMPTY, pres); if (sunk == compressed_size) { HSD_finish_res fres = heatshrink_decoder_finish(&hsd); ASSERT_EQ(HSDR_FINISH_DONE, fres); } if (polled > input_size) { FAILm("Decompressed data is larger than original input"); } } if (log_lvl > 0) printf("decompressed: %u\n", polled); if (polled != input_size) { FAILm("Decompressed length does not match original input length"); } if (log_lvl > 1) dump_buf("decomp", decomp, polled); for (size_t i=0; i out[%zd] == 0x%02x ('%c')\n", j, input[j], isprint(input[j]) ? input[j] : '.', j, decomp[j], isprint(decomp[j]) ? decomp[j] : '.'); } } } ASSERT_EQ(input[i], decomp[i]); } free(comp); free(decomp); PASS(); } TEST pseudorandom_data_should_match(uint32_t size, uint32_t seed) { uint8_t input[size]; fill_with_pseudorandom_letters(input, size, seed); return compress_and_expand_and_check(input, size, 0); } SUITE(integration) { #if __STDC_VERSION__ >= 19901L for (uint32_t size=1; size < 64*1024; size <<= 1) { if (GREATEST_IS_VERBOSE()) printf(" -- size %u\n", size); for (uint32_t seed=1; seed<=100; seed++) { if (GREATEST_IS_VERBOSE()) printf(" -- seed %u\n", seed); RUN_TESTp(pseudorandom_data_should_match, size, seed); } } #endif } /* Add all the definitions that need to be in the test runner's main file. */ GREATEST_MAIN_DEFS(); int main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */ printf("INPUT_BUFFER_SIZE: %u\n", HEATSHRINK_STATIC_INPUT_BUFFER_SIZE); printf("WINDOW_BITS: %u\n", HEATSHRINK_STATIC_WINDOW_BITS); printf("LOOKAHEAD_BITS: %u\n", HEATSHRINK_STATIC_LOOKAHEAD_BITS); printf("sizeof(heatshrink_encoder): %zd\n", sizeof(heatshrink_encoder)); printf("sizeof(heatshrink_decoder): %zd\n", sizeof(heatshrink_decoder)); RUN_SUITE(integration); GREATEST_MAIN_END(); /* display results */ }