heatshrink (empty) → 0.1.0.0
raw patch · 18 files changed
+1718/−0 lines, 18 filesdep +basedep +bytestringdep +cerealsetup-changedbinary-added
Dependencies added: base, bytestring, cereal, heatshrink, pcre-heavy, tasty, tasty-golden, tasty-hunit, text
Files
- LICENSE +30/−0
- README.md +26/−0
- Setup.hs +2/−0
- cbits/heatshrink_common.h +20/−0
- cbits/heatshrink_config.h +26/−0
- cbits/heatshrink_decoder.c +385/−0
- cbits/heatshrink_decoder.h +100/−0
- cbits/heatshrink_encoder.c +611/−0
- cbits/heatshrink_encoder.h +109/−0
- files/frame1_w12l8.golden +1/−0
- files/frame1_w12l8.hex binary
- files/frame2_w12l8.golden +1/−0
- files/frame2_w12l8.hex binary
- heatshrink.cabal +75/−0
- src/Codec/Compression/Heatshrink.hs +132/−0
- src/Codec/Compression/Heatshrink/Internal.chs +137/−0
- tests/Codec/Compression/Heatshrink/Tests.hs +48/−0
- tests/Tests.hs +15/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright FPInsight (c) 2015++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Thierry Bourrillon nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,26 @@+# Welcome to heatshrink++[](https://travis-ci.org/fpinsight/heatshrink)++heatshrink is a haskell FFI for Scott Vokes's [heatshrink](https://github.com/atomicobject/heatshrink)+, a data compression/decompression library for embedded/real-time systems.++All files under cbits are copyright by "Scott Vokes <vokes.s@gmail.com>"++haskell FFI curently only support decompression.++# Join in!++We are happy to receive bug reports, fixes, documentation enhancements,+and other improvements.++Please report bugs via the+[github issue tracker](https://github.com/fpinsight/heatshrink/issues).++Master [git repository](https://github.com/fpinsight/heatshrink):++* `git clone git@github.com:fpinsight/heatshrink.git`++# Authors++This library is written and maintained by Thierry Bourrillon, <thierry.bourrillon@fpinsight.com>.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cbits/heatshrink_common.h view
@@ -0,0 +1,20 @@+#ifndef HEATSHRINK_H+#define HEATSHRINK_H++#define HEATSHRINK_AUTHOR "Scott Vokes <vokes.s@gmail.com>"+#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
+ cbits/heatshrink_config.h view
@@ -0,0 +1,26 @@+#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
+ cbits/heatshrink_decoder.c view
@@ -0,0 +1,385 @@+#include <stdlib.h>+#include <string.h>+#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 <stdio.h>+#include <ctype.h>+#include <assert.h>+#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) {+ LOG("-- heatshrink_decoder_alloc %u %u %u\n", input_buffer_size, window_sz2, 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;+ uint8_t *buffers = HEATSHRINK_MALLOC(buffers_sz);+ if (buffers == NULL) { return NULL; }+ size_t sz = sizeof(heatshrink_decoder);+ heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz);+ if (hsd == NULL) {+ LOG("-- failed to allocate buffers\n");+ HEATSHRINK_FREE(buffers, buffers_sz);+ return NULL;+ }+ hsd->buffers = buffers;+ 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);+ LOG("-- decoder : %p, buffers: %p\n", hsd, hsd->buffers);+ return hsd;+}++void heatshrink_decoder_free(heatshrink_decoder *hsd) {+ LOG("-- heatshrink_decoder_free %p\n", hsd);+ if (hsd != NULL) {+ size_t buffers_sz = (1 << hsd->window_sz2) + hsd->input_buffer_size;+ size_t sz = sizeof(heatshrink_decoder);+ HEATSHRINK_FREE(hsd->buffers, buffer_sz);+ HEATSHRINK_FREE(hsd, sz);+ (void)buffers_sz; /* may not be used by free */+ (void)sz;+ }+}+#endif++void heatshrink_decoder_reset(heatshrink_decoder *hsd) {+ LOG("-- heatshrink_decoder_reset %p\n", 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) {+ LOG("-- heatshrink_decoder_sink %p %p %lu %p\n", hsd, in_buf, size, 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) {++ LOG("-- heatshrink_decoder_poll %p %p %lu %p\n", hsd, out_buf, out_buf_size, 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; i<count; i++) {+ uint8_t c = buf[(hsd->head_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;+}
+ cbits/heatshrink_decoder.h view
@@ -0,0 +1,100 @@+#ifndef HEATSHRINK_DECODER_H+#define HEATSHRINK_DECODER_H++#include <stdint.h>+#include <stddef.h>+#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
+ cbits/heatshrink_encoder.c view
@@ -0,0 +1,611 @@+#include <stdlib.h>+#include <string.h>+#include <stdbool.h>+#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 <stdio.h>+#include <ctype.h>+#include <assert.h>+#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; }+ uint8_t *buffer = HEATSHRINK_MALLOC(buf_sz);+ if (buffer == NULL) {+ HEATSHRINK_FREE(hse, sizeof(*hse));+ return NULL;+ }+ hse->buffer = buffer;+ 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));+ HEATSHRINK_FREE(hse->buffer, buf_sz);+#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));+ (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; i<end; i++) {+ uint8_t v = data[i];+ int16_t lv = last[v];+ index[i] = lv;+ last[v] = i;+ }+#else+ (void)hse;+#endif+}++static int is_finishing(heatshrink_encoder *hse) {+ return hse->flags & 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<maxlen; len++) {+ if (0) {+ LOG(" --> 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;+}
+ cbits/heatshrink_encoder.h view
@@ -0,0 +1,109 @@+#ifndef HEATSHRINK_ENCODER_H+#define HEATSHRINK_ENCODER_H++#include <stdint.h>+#include <stddef.h>+#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
+ files/frame1_w12l8.golden view
@@ -0,0 +1,1 @@+{"from":{"deviceId":"Spoony1","unit":"DataChunk"},"t":"2015-10-16T15:10:25.824Z","count":29,"elements":[{"n":"TEMP","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":24.99732}]},{"n":"FREQ","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":50.00000}]},{"n":"VRMSA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":219.72705}]},{"n":"VRMSB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":220.09834}]},{"n":"VRMSC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":220.04170}]},{"n":"IRMSA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":9.10000}]},{"n":"IRMSB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":9.93344}]},{"n":"IRMSC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":9.56120}]},{"n":"WATTHRA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.27771}]},{"n":"WATTHRB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.30365}]},{"n":"WATTHRC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.29220}]},{"n":"VARHRA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.00000}]},{"n":"VARHRB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.00000}]},{"n":"VARHRC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.00000}]},{"n":"VAHRA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.27771}]},{"n":"VAHRB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.30365}]},{"n":"VAHRC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.29220}]},{"n":"WATTA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":1999.51623}]},{"n":"WATTB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":2186.33593}]},{"n":"WATTC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":2103.86279}]},{"n":"VARA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.00000}]},{"n":"VARB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.00000}]},{"n":"VARC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":0.00000}]},{"n":"VAA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":1999.51623}]},{"n":"VAB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":2186.33593}]},{"n":"VAC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":2103.86279}]},{"n":"PFA","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":1.00000}]},{"n":"PFB","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":1.00000}]},{"n":"PFC","count":1,"records":[{"i":0,"t":"2015-10-16T15:10:19.999Z","q":"good","v":1.00000}]}]}
+ files/frame1_w12l8.hex view
binary file changed (absent → 455 bytes)
+ files/frame2_w12l8.golden view
@@ -0,0 +1,1 @@+{"from":{"deviceId":"Spoony1","unit":"ODMDataChunk"},"t":"2016-01-29T15:01:32.028Z","count":29,"elements":[{"n":"TEMP","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":24.96719}]},{"n":"FREQ","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":50.00000}]},{"n":"VRMSA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":220.15684}]},{"n":"VRMSB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":219.30027}]},{"n":"VRMSC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":219.67214}]},{"n":"IRMSA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":-5.37061}]},{"n":"IRMSB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":7.28793}]},{"n":"IRMSC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":4.85941}]},{"n":"WATTHRA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":0.23364}]},{"n":"WATTHRB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":7.42876}]},{"n":"WATTHRC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":5.86496}]},{"n":"VARHRA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":0.01221}]},{"n":"VARHRB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":0.14761}]},{"n":"VARHRC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":0.14548}]},{"n":"VAHRA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":0.23396}]},{"n":"VAHRB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":7.43212}]},{"n":"VAHRC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":5.86826}]},{"n":"WATTA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":-1181.78686}]},{"n":"WATTB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":1597.95654}]},{"n":"WATTC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":1066.00219}]},{"n":"VARA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":-37.34854}]},{"n":"VARB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":30.45286}]},{"n":"VARC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":56.12364}]},{"n":"VAA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":-1182.37683}]},{"n":"VAB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":1598.24670}]},{"n":"VAC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":1067.47863}]},{"n":"PFA","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":0.99950}]},{"n":"PFB","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":0.99981}]},{"n":"PFC","count":1,"records":[{"i":0,"t":"2016-01-29T15:01:31.998Z","q":"good","v":0.99861}]}]}
+ files/frame2_w12l8.hex view
binary file changed (absent → 555 bytes)
+ heatshrink.cabal view
@@ -0,0 +1,75 @@+-- This file has been generated from package.yaml by hpack version 0.17.0.+--+-- see: https://github.com/sol/hpack++name: heatshrink+version: 0.1.0.0+synopsis: Compression and decompression using heatshrink+description: Please see README.md+category: FFI+homepage: https://github.com/fpinsight/heatshrink#readme+bug-reports: https://github.com/fpinsight/heatshrink/issues+author: Thierry Bourrillon+maintainer: thierry.bourrillon@fpinsight.com+copyright: 2016 Thierry Bourrillon+license: BSD3+license-file: LICENSE+build-type: Simple+cabal-version: >= 1.10++extra-source-files:+ cbits/heatshrink_common.h+ cbits/heatshrink_config.h+ cbits/heatshrink_decoder.h+ cbits/heatshrink_encoder.h+ files/frame1_w12l8.golden+ files/frame1_w12l8.hex+ files/frame2_w12l8.golden+ files/frame2_w12l8.hex+ README.md++source-repository head+ type: git+ location: https://github.com/fpinsight/heatshrink++library+ hs-source-dirs:+ src+ ghc-options: -Wall+ cc-options: -std=c11+ include-dirs:+ cbits+ c-sources:+ cbits/heatshrink_decoder.c+ cbits/heatshrink_encoder.c+ build-depends:+ bytestring+ , cereal+ , base >=4.7 && <5+ build-tools:+ c2hs+ exposed-modules:+ Codec.Compression.Heatshrink+ other-modules:+ Codec.Compression.Heatshrink.Internal+ default-language: Haskell2010++test-suite heatshrink-test+ type: exitcode-stdio-1.0+ main-is: Tests.hs+ hs-source-dirs:+ tests+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ bytestring+ , cereal+ , base+ , heatshrink+ , pcre-heavy+ , tasty+ , tasty-golden+ , tasty-hunit+ , text+ other-modules:+ Codec.Compression.Heatshrink.Tests+ default-language: Haskell2010
+ src/Codec/Compression/Heatshrink.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE BangPatterns #-}++--------------------------------------------------------------------------------+module Codec.Compression.Heatshrink+ (+ -- * decoding+ Decoder+ , DecodeParams(..)+ , defaultDecodeParams+ , decoderWithParams+ , decode+ , decodeWith++ -- * encoding++ ) where+++--------------------------------------------------------------------------------+import Codec.Compression.Heatshrink.Internal+import qualified Data.ByteString as B+import Data.ByteString.Internal hiding (ByteString)+import qualified Data.ByteString.Lazy as BL+import Foreign.ForeignPtr+import Foreign.Ptr+++--------------------------------------------------------------------------------+data DecodeParams = DecodeParams {+ inputBufferSize :: Int+ , expansionBufferBits :: Int+ , lookAheadBits :: Int+ } deriving (Show, Eq)+++--------------------------------------------------------------------------------+defaultDecodeParams :: DecodeParams+defaultDecodeParams = DecodeParams {+ inputBufferSize = 1024+ , expansionBufferBits = 12+ , lookAheadBits = 8+ }+++--------------------------------------------------------------------------------+data Decoder = Decoder !HeatshrinkDecoderTPtr !DecodeParams+ deriving (Eq, Show)+++--------------------------------------------------------------------------------+decoderWithParams :: DecodeParams -> Decoder+decoderWithParams p = Decoder ptr p+ where+ ptr = heatshrinkDecoderAlloc+ (inputBufferSize p)+ (expansionBufferBits p)+ (lookAheadBits p)+++--------------------------------------------------------------------------------+decodeWith :: DecodeParams -> BL.ByteString -> IO BL.ByteString+decodeWith p bs = decode decoder bs+ where decoder = decoderWithParams p+++--------------------------------------------------------------------------------+decode :: Decoder -> BL.ByteString -> IO BL.ByteString+decode decoder bs = do+ let Decoder d _ = decoder+ heatshrinkDecoderReset d+ decodedChunks <- mapM (decodeChunk d) (BL.toChunks bs)+ let decoded = BL.concat decodedChunks+ f <- heatshrinkDecoderFinish d+ case f of+ HsdrFinishDone -> return decoded+ HsdrFinishMore -> do+ leftover <- pollAll d 4096+ return $ (BL.concat decodedChunks) `BL.append` leftover+ e -> fail $ show e+++--------------------------------------------------------------------------------+decodeChunk :: HeatshrinkDecoderTPtr -> B.ByteString -> IO BL.ByteString+decodeChunk d bs = do+ go d bs BL.empty+ where+ go :: HeatshrinkDecoderTPtr -> B.ByteString -> BL.ByteString -> IO BL.ByteString+ go d bs acc = if bs == B.empty then+ return acc+ else do+ res <- sink d bs+ case res of+ (HsdrSinkOk, n) -> do+ let rem = B.drop n bs+ dec <- pollAll d 4096+ go d rem (BL.append acc dec)+ (e, _) -> fail $ show e+++--------------------------------------------------------------------------------+sink :: HeatshrinkDecoderTPtr -> B.ByteString -> IO (HsdSinkRes, Int)+sink d bs = do+ let (PS p o l) = bs+ (st, sz) <- withForeignPtr p (\ptr -> heatshrinkDecoderSink d (plusPtr ptr o) (fromIntegral l))+ return (st, fromIntegral sz)+++--------------------------------------------------------------------------------+poll :: HeatshrinkDecoderTPtr -> Int -> IO (HsdPollRes, BL.ByteString)+poll d sz = do+ (bs, st) <- createAndTrim' sz (\p -> do+ (s, sz') <- heatshrinkDecoderPoll d p (fromIntegral sz)+ return (0, fromIntegral sz', s)+ )+ return (st, BL.fromStrict bs)+++--------------------------------------------------------------------------------+pollAll :: HeatshrinkDecoderTPtr -> Int -> IO (BL.ByteString)+pollAll d sz = pollAll' BL.empty d sz+ where+ pollAll' :: BL.ByteString -> HeatshrinkDecoderTPtr -> Int -> IO BL.ByteString+ pollAll' acc d sz = do+ res <- poll d sz+ case res of+ (HsdrPollMore, !bs) -> pollAll' (BL.append acc bs) d sz+ (HsdrPollEmpty, !bs) -> return $ BL.append acc bs+ (_, _) -> fail "error in pollAll"+++--------------------------------------------------------------------------------
+ src/Codec/Compression/Heatshrink/Internal.chs view
@@ -0,0 +1,137 @@+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE ForeignFunctionInterface #-}+++--------------------------------------------------------------------------------+module Codec.Compression.Heatshrink.Internal where++import Foreign.C.Types+import Foreign.Ptr+import Foreign.Storable+import Foreign.Marshal.Alloc+import Data.Word+++--------------------------------------------------------------------------------+{#pointer *size_t as CSizePtr -> CSize #}+{#pointer *uint8_t as CUCharPtr -> CUChar #}+++-- * decoding+--------------------------------------------------------------------------------+#include "heatshrink_decoder.h"++--------------------------------------------------------------------------------+{#enum HSD_sink_res as HsdSinkRes {underscoreToCase} deriving (Show, Eq) #}+{#enum HSD_poll_res as HsdPollRes {underscoreToCase} deriving (Show, Eq) #}+{#enum HSD_finish_res as HsdFinishRes {underscoreToCase} deriving (Show, Eq) #}++--------------------------------------------------------------------------------+data HeatshrinkDecoderT++--------------------------------------------------------------------------------+{#pointer *heatshrink_decoder as HeatshrinkDecoderTPtr foreign finalizer heatshrink_decoder_free #}+--{#pointer *heatshrink_decoder as HeatshrinkDecoderTPtr foreign #}++--------------------------------------------------------------------------------+++--------------------------------------------------------------------------------+{#fun pure unsafe heatshrink_decoder_alloc as ^+ { `Int'+ , `Int'+ , `Int'} -> `HeatshrinkDecoderTPtr'+#}+++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_decoder_free as ^+ { `HeatshrinkDecoderTPtr' } -> `()'+#}++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_decoder_reset as ^+ { `HeatshrinkDecoderTPtr' } -> `()'+#}+++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_decoder_sink as ^+ { `HeatshrinkDecoderTPtr'+ , castPtr `Ptr Word8'+ , cIntConv `CSize'+ , alloca- `CSize' peek* } -> `HsdSinkRes'+#}+++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_decoder_poll as ^+ { `HeatshrinkDecoderTPtr'+ , castPtr `Ptr Word8'+ , cIntConv `CSize'+ , alloca- `CSize' peek* } -> `HsdPollRes'+#}+++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_decoder_finish as ^+ { `HeatshrinkDecoderTPtr' } -> `HsdFinishRes'+#}+++--------------------------------------------------------------------------------+cIntConv :: (Integral a, Num b) => a -> b+cIntConv = fromIntegral+++--------------------------------------------------------------------------------++-- encoding+#include "heatshrink_encoder.h"++{#enum HSE_sink_res as HseSinkRes {underscoreToCase} deriving (Show, Eq) #}+{#enum HSE_poll_res as HsePollRes {underscoreToCase} deriving (Show, Eq) #}+{#enum HSE_finish_res as HseFinishRes {underscoreToCase} deriving (Show, Eq) #}++data HeatshrinkEncoderT++{#pointer *heatshrink_encoder as HeatshrinkEncoderTPtr foreign finalizer heatshrink_encoder_free #}++{#fun pure unsafe heatshrink_encoder_alloc as ^+ { `Int'+ , `Int'} -> `HeatshrinkEncoderTPtr'+#}++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_encoder_free as ^+ { `HeatshrinkEncoderTPtr' } -> `()'+#}++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_encoder_reset as ^+ { `HeatshrinkEncoderTPtr' } -> `()'+#}+++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_encoder_sink as ^+ { `HeatshrinkEncoderTPtr'+ , castPtr `Ptr Word8'+ , cIntConv `CSize'+ , alloca- `CSize' peek* } -> `HseSinkRes'+#}+++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_encoder_poll as ^+ { `HeatshrinkEncoderTPtr'+ , castPtr `Ptr Word8'+ , cIntConv `CSize'+ , alloca- `CSize' peek* } -> `HsePollRes'+#}+++--------------------------------------------------------------------------------+{#fun unsafe heatshrink_encoder_finish as ^+ { `HeatshrinkEncoderTPtr' } -> `HseFinishRes'+#}
+ tests/Codec/Compression/Heatshrink/Tests.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++--------------------------------------------------------------------------------+module Codec.Compression.Heatshrink.Tests (tests) where+++--------------------------------------------------------------------------------+import Codec.Compression.Heatshrink+import qualified Data.ByteString.Lazy as BL+import System.IO.Unsafe+import Test.Tasty+import Test.Tasty.Golden+import Text.Regex.PCRE.Heavy+++--------------------------------------------------------------------------------+tests :: TestTree+tests = testGroup "Codec.Compression.Heatshrink"+ [ testGroup "decoding files" $ unsafePerformIO goldenTests+ ]+++--------------------------------------------------------------------------------+decodeFile :: DecodeParams-> FilePath -> FilePath -> IO ()+decodeFile p s t = do+ c <- BL.readFile s+ u <- decodeWith p c+ BL.writeFile t u+++--------------------------------------------------------------------------------+goldenTests :: IO [TestTree]+goldenTests = do+ files <- findByExtension [".golden"] "files"+ let test = map (scan [re|((.*)/(\w+)_w(\d+)l(\d+))\.golden|]) files+ return $ map createTest test+++--------------------------------------------------------------------------------+createTest :: [(String, [String])] -> TestTree+createTest [(f, [noext, dir, base, sw, sl])] = goldenVsFile f f dstFile (decodeFile params srcFile dstFile)+ where+ params = DecodeParams 1024 w l+ srcFile = noext ++ ".hex"+ dstFile = noext ++ ".tmp"+ w = read sw :: Int+ l = read sl :: Int
+ tests/Tests.hs view
@@ -0,0 +1,15 @@+--------------------------------------------------------------------------------+import Test.Tasty++--------------------------------------------------------------------------------+import qualified Codec.Compression.Heatshrink.Tests++--------------------------------------------------------------------------------+main :: IO ()+main = defaultMain tests+++--------------------------------------------------------------------------------+tests :: TestTree+tests = testGroup "Tests"+ [ Codec.Compression.Heatshrink.Tests.tests ]