diff --git a/.gitignore b/.gitignore
index 7b7ee90..899b7c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,8 @@ html_compressed/
esp-link.tgz
tve-patch/
yui
+espfs/mkespfsimage/mman-win32/mman.o
+esp-link.opensdf
+esp-link.sdf
+espfs/mkespfsimage/mman-win32/libmman.a
+.localhistory/
diff --git a/Makefile b/Makefile
index 709a0a1..297e4fe 100644
--- a/Makefile
+++ b/Makefile
@@ -289,6 +289,9 @@ $(BUILD_DIR):
wiflash: all
./wiflash $(ESP_HOSTNAME) $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin
+baseflash: all
+ $(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash 0x01000 $(FW_BASE)/user1.bin
+
flash: all
$(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) -fs $(ET_FS) -ff $(ET_FF) write_flash \
0x00000 "$(SDK_BASE)/bin/boot_v1.4(b1).bin" 0x01000 $(FW_BASE)/user1.bin \
@@ -296,7 +299,7 @@ flash: all
yui/$(YUI-COMPRESSOR):
$(Q) mkdir -p yui
- cd yui; wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI-COMPRESSOR)
+ cd yui; wget --no-check-certificate https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI-COMPRESSOR) -O $(YUI-COMPRESSOR)
ifeq ("$(COMPRESS_W_YUI)","yes")
$(BUILD_BASE)/espfs_img.o: yui/$(YUI-COMPRESSOR)
diff --git a/esp-link.sln b/esp-link.sln
new file mode 100644
index 0000000..fb7b468
--- /dev/null
+++ b/esp-link.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.31101.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "esp-link", "esp-link.vcxproj", "{A92F0CAA-F89B-4F78-AD2A-A042429BD87F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|ARM = Debug|ARM
+ Release|ARM = Release|ARM
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Debug|ARM.ActiveCfg = Debug|ARM
+ {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Debug|ARM.Build.0 = Debug|ARM
+ {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Release|ARM.ActiveCfg = Release|ARM
+ {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Release|ARM.Build.0 = Release|ARM
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/esp-link.vcxproj b/esp-link.vcxproj
new file mode 100644
index 0000000..45dd2ae
--- /dev/null
+++ b/esp-link.vcxproj
@@ -0,0 +1,175 @@
+
+
+
+
+ Debug
+ ARM
+
+
+ Release
+ ARM
+
+
+
+ {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}
+ MakeFileProj
+
+
+
+ Makefile
+
+
+
+
+
+
+
+
+
+
+ __ets__;_STDINT_H;ICACHE_FLASH;__MINGW32__;__WIN32__
+ .\mqtt\include;.\serial;.\user;.\espfs;.\httpd;.\include;c:\tools\mingw64\x86_64-w64-mingw32\include\c++\x86_64-w64-mingw32;c:\tools\mingw64\x86_64-w64-mingw32\include\c++\backward;c:\tools\mingw64\x86_64-w64-mingw32\include\c++;c:\tools\mingw64\x86_64-w64-mingw32\include;c:\tools\mingw64\lib\gcc\x86_64-w64-mingw32\4.8.3\include-fixed;c:\tools\mingw64\lib\gcc\x86_64-w64-mingw32\4.8.3\include;c:\Espressif\sdk\include
+
+
+
+
+
+
+ bin
+ build
+
+
+ espmake flash
+ espmake clean all
+ espmake clean
+
+
+ espmake baseflash
+ espmake clean all
+ espmake clean
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/espfs/espfs.c b/espfs/espfs.c
index 3da6692..1f503d5 100644
--- a/espfs/espfs.c
+++ b/espfs/espfs.c
@@ -7,9 +7,9 @@ It's written for use with httpd, but doesn't need to be used as such.
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
- * Jeroen Domburg wrote this file. As long as you retain
- * this notice you can do whatever you want with this stuff. If we meet some day,
- * and you think this stuff is worth it, you can buy me a beer in return.
+ * Jeroen Domburg wrote this file. As long as you retain
+ * this notice you can do whatever you want with this stuff. If we meet some day,
+ * and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
@@ -42,11 +42,12 @@ It's written for use with httpd, but doesn't need to be used as such.
#ifdef ESPFS_HEATSHRINK
#include "heatshrink_config_custom.h"
-#include "heatshrink_decoder.h"
+#include "heatshrink/heatshrink_decoder.h"
#endif
static char* espFsData = NULL;
+
struct EspFsFile {
EspFsHeader *header;
char decompressor;
@@ -146,19 +147,19 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) {
return NULL;
}
if (h.flags&FLAG_LASTFILE) {
- //os_printf("End of image.\n");
+ os_printf("End of image.\n");
return NULL;
}
//Grab the name of the file.
- p+=sizeof(EspFsHeader);
+ p+=sizeof(EspFsHeader);
os_memcpy(namebuf, p, sizeof(namebuf));
-// os_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n",
+// os_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n",
// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags);
if (os_strcmp(namebuf, fileName)==0) {
//Yay, this is the file we need!
p+=h.nameLen; //Skip to content.
r=(EspFsFile *)os_malloc(sizeof(EspFsFile)); //Alloc file desc mem
- //os_printf("Alloc %p[%d]\n", r, sizeof(EspFsFile));
+// os_printf("Alloc %p\n", r);
if (r==NULL) return NULL;
r->header=(EspFsHeader *)hpos;
r->decompressor=h.compression;
@@ -175,7 +176,7 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) {
//Decoder params are stored in 1st byte.
memcpyAligned(&parm, r->posComp, 1);
r->posComp++;
- //os_printf("Heatshrink compressed file; decode parms = %x\n", parm);
+ os_printf("Heatshrink compressed file; decode parms = %x\n", parm);
dec=heatshrink_decoder_alloc(16, (parm>>4)&0xf, parm&0xf);
r->decompData=dec;
#endif
@@ -265,7 +266,7 @@ void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) {
// os_printf("Freed %p\n", dec);
}
#endif
- //os_printf("Freed %p\n", fh);
+// os_printf("Freed %p\n", fh);
os_free(fh);
}
diff --git a/espfs/heatshrink/LICENSE b/espfs/heatshrink/LICENSE
new file mode 100644
index 0000000..31ec3df
--- /dev/null
+++ b/espfs/heatshrink/LICENSE
@@ -0,0 +1,14 @@
+Copyright (c) 2013, 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.
diff --git a/espfs/heatshrink/Makefile b/espfs/heatshrink/Makefile
new file mode 100644
index 0000000..d8e6875
--- /dev/null
+++ b/espfs/heatshrink/Makefile
@@ -0,0 +1,49 @@
+PROJECT = heatshrink
+#OPTIMIZE = -O0
+#OPTIMIZE = -Os
+OPTIMIZE = -O3
+WARN = -Wall -Wextra -pedantic #-Werror
+CFLAGS += -std=c99 -g ${WARN} ${OPTIMIZE}
+CFLAGS += -Wmissing-prototypes
+CFLAGS += -Wstrict-prototypes
+CFLAGS += -Wmissing-declarations
+
+# If libtheft is available, build additional property-based tests.
+# Uncomment these to use it in test_heatshrink_dynamic.
+#CFLAGS += -DHEATSHRINK_HAS_THEFT
+#LDFLAGS += -ltheft
+
+all:
+ @echo "For tests, make test_heatshrink_dynamic (default) or change the"
+ @echo "config.h to disable static memory and build test_heatshrink_static."
+ @echo "For the standalone command-line tool, make heatshrink."
+
+${PROJECT}: heatshrink.c
+
+OBJS= heatshrink_encoder.o \
+ heatshrink_decoder.o \
+
+heatshrink: ${OBJS}
+test_heatshrink_dynamic: ${OBJS} test_heatshrink_dynamic_theft.o
+test_heatshrink_static: ${OBJS}
+
+*.o: Makefile heatshrink_config.h
+
+heatshrink_decoder.o: heatshrink_decoder.h heatshrink_common.h
+heatshrink_encoder.o: heatshrink_encoder.h heatshrink_common.h
+
+tags: TAGS
+
+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 $<
+
+clean:
+ rm -f ${PROJECT} test_heatshrink_{dynamic,static} *.o *.core {dec,enc}_sm.png TAGS
diff --git a/espfs/heatshrink/README.md b/espfs/heatshrink/README.md
new file mode 100644
index 0000000..ab150ee
--- /dev/null
+++ b/espfs/heatshrink/README.md
@@ -0,0 +1,49 @@
+# 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.
+
+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`.
+
+## 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.
+
+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)
diff --git a/espfs/heatshrink/dec_sm.dot b/espfs/heatshrink/dec_sm.dot
new file mode 100644
index 0000000..470012f
--- /dev/null
+++ b/espfs/heatshrink/dec_sm.dot
@@ -0,0 +1,52 @@
+digraph {
+ graph [label="Decoder state machine", labelloc="t"]
+ Start [style="invis", shape="point"]
+ empty
+ input_available
+ yield_literal
+ backref_index_msb
+ backref_index_lsb
+ backref_count_msb
+ backref_count_lsb
+ yield_backref
+ check_for_more_input
+ done [peripheries=2]
+
+ empty->input_available [label="sink()", color="blue", weight=10]
+ Start->empty
+
+ input_available->yield_literal [label="pop 1-bit"]
+ input_available->backref_index_msb [label="pop 0-bit", weight=10]
+ input_available->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->check_for_more_input [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->check_for_more_input [label="poll(), done",
+ color="red", weight=10]
+
+ check_for_more_input->empty [label="no"]
+ check_for_more_input->input_available [label="yes"]
+
+ empty->done [label="finish()", color="blue"]
+}
diff --git a/espfs/heatshrink/enc_sm.dot b/espfs/heatshrink/enc_sm.dot
new file mode 100644
index 0000000..6d3030f
--- /dev/null
+++ b/espfs/heatshrink/enc_sm.dot
@@ -0,0 +1,51 @@
+digraph {
+ 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->search [label="step"]
+ search->yield_tag_bit [label="literal"]
+ search->yield_tag_bit [label="match found"]
+ search->save_backlog [label="input exhausted"]
+
+ 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(), no literal", color="red"]
+ yield_tag_bit->flush_bits [label="finishing, no literal"]
+
+ yield_literal->yield_literal [label="poll(), full buf", color="red"]
+ yield_literal->search [label="poll(), no match", color="red"]
+ yield_literal->yield_tag_bit [label="poll(), match", color="red"]
+ yield_literal->flush_bits [label="poll(), final literal", color="red"]
+
+ 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->flush_bits [label="finishing, no literal"]
+ save_backlog->yield_tag_bit [label="finishing, literal"]
+ 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"]
+}
diff --git a/espfs/heatshrink/greatest.h b/espfs/heatshrink/greatest.h
new file mode 100644
index 0000000..a92c642
--- /dev/null
+++ b/espfs/heatshrink/greatest.h
@@ -0,0 +1,591 @@
+/*
+ * Copyright (c) 2011 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
+
+#define GREATEST_VERSION_MAJOR 0
+#define GREATEST_VERSION_MINOR 9
+#define GREATEST_VERSION_PATCH 3
+
+/* 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 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(suite);
+ GREATEST_MAIN_END(); /* display results */
+}
+
+#endif
+/*********************************************************************/
+
+
+#include
+#include
+#include
+#include
+
+
+/***********
+ * Options *
+ ***********/
+
+/* Default column width for non-verbose output. */
+#ifndef GREATEST_DEFAULT_WIDTH
+#define GREATEST_DEFAULT_WIDTH 72
+#endif
+
+/* FILE *, for test logging. */
+#ifndef GREATEST_STDOUT
+#define GREATEST_STDOUT stdout
+#endif
+
+/* Remove GREATEST_ prefix from most commonly used symbols? */
+#ifndef GREATEST_USE_ABBREVS
+#define GREATEST_USE_ABBREVS 1
+#endif
+
+
+/*********
+ * 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;
+
+ /* timers, pre/post running suite and individual tests */
+ clock_t pre_suite;
+ clock_t post_suite;
+ clock_t pre_test;
+ clock_t post_test;
+} 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);
+
+typedef enum {
+ GREATEST_FLAG_VERBOSE = 0x01,
+ GREATEST_FLAG_FIRST_FAIL = 0x02,
+ GREATEST_FLAG_LIST_ONLY = 0x04
+} GREATEST_FLAG;
+
+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;
+
+ /* 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;
+
+ /* overall timers */
+ clock_t begin;
+ clock_t end;
+} greatest_run_info;
+
+/* Global var for the current testing context.
+ * Initialized by GREATEST_MAIN_DEFS(). */
+extern greatest_run_info greatest_info;
+
+
+/**********************
+ * Exported functions *
+ **********************/
+
+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);
+void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata);
+void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata);
+
+
+/**********
+ * Macros *
+ **********/
+
+/* Define a suite. */
+#define GREATEST_SUITE(NAME) void NAME(void)
+
+/* Start defining a test function.
+ * The arguments are not included, to allow parametric testing. */
+#define GREATEST_TEST static int
+
+/* 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) { \
+ int 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)
+
+/* If __VA_ARGS__ (C99) is supported, allow parametric testing
+ * without needing to manually manage the argument struct. */
+#if __STDC_VERSION__ >= 19901L
+#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. */
+#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_FALSE(COND) GREATEST_ASSERT_FALSEm(#COND, COND)
+#define GREATEST_ASSERT_EQ(EXP, GOT) GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT)
+#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.msg = MSG; \
+ greatest_info.fail_file = __FILE__; \
+ greatest_info.fail_line = __LINE__; \
+ if (!(COND)) return -1; \
+ greatest_info.msg = NULL; \
+ } while (0)
+
+#define GREATEST_ASSERT_FALSEm(MSG, COND) \
+ do { \
+ greatest_info.msg = MSG; \
+ greatest_info.fail_file = __FILE__; \
+ greatest_info.fail_line = __LINE__; \
+ if ((COND)) return -1; \
+ greatest_info.msg = NULL; \
+ } while (0)
+
+#define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \
+ do { \
+ greatest_info.msg = MSG; \
+ greatest_info.fail_file = __FILE__; \
+ greatest_info.fail_line = __LINE__; \
+ if ((EXP) != (GOT)) return -1; \
+ greatest_info.msg = NULL; \
+ } while (0)
+
+#define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \
+ do { \
+ const char *exp_s = (EXP); \
+ const char *got_s = (GOT); \
+ greatest_info.msg = MSG; \
+ greatest_info.fail_file = __FILE__; \
+ greatest_info.fail_line = __LINE__; \
+ if (0 != strcmp(exp_s, got_s)) { \
+ fprintf(GREATEST_STDOUT, \
+ "Expected:\n####\n%s\n####\n", exp_s); \
+ fprintf(GREATEST_STDOUT, \
+ "Got:\n####\n%s\n####\n", got_s); \
+ return -1; \
+ } \
+ greatest_info.msg = NULL; \
+ } while (0)
+
+#define GREATEST_PASSm(MSG) \
+ do { \
+ greatest_info.msg = MSG; \
+ return 0; \
+ } while (0)
+
+#define GREATEST_FAILm(MSG) \
+ do { \
+ greatest_info.fail_file = __FILE__; \
+ greatest_info.fail_line = __LINE__; \
+ greatest_info.msg = MSG; \
+ return -1; \
+ } while (0)
+
+#define GREATEST_SKIPm(MSG) \
+ do { \
+ greatest_info.msg = MSG; \
+ return 1; \
+ } while (0)
+
+#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) - (C1), \
+ (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) \
+
+/* 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 < 0) { \
+ greatest_do_fail(name); \
+ } else if (res > 0) { \
+ greatest_do_skip(name); \
+ } else if (res == 0) { \
+ 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; \
+ greatest_info.suite.tests_run = 0; \
+ greatest_info.suite.failed = 0; \
+ greatest_info.suite.passed = 0; \
+ greatest_info.suite.skipped = 0; \
+ greatest_info.suite.pre_suite = 0; \
+ greatest_info.suite.post_suite = 0; \
+ greatest_info.suite.pre_test = 0; \
+ greatest_info.suite.post_test = 0; \
+ 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"); \
+ /* add linebreak if in line of '.'s */ \
+ if (greatest_info.col % greatest_info.width != 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++; \
+} \
+ \
+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); \
+} \
+ \
+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; \
+} \
+ \
+greatest_run_info greatest_info
+
+/* Handle command-line arguments, etc. */
+#define GREATEST_MAIN_BEGIN() \
+ do { \
+ int i = 0; \
+ memset(&greatest_info, 0, sizeof(greatest_info)); \
+ if (greatest_info.width == 0) { \
+ greatest_info.width = GREATEST_DEFAULT_WIDTH; \
+ } \
+ 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); \
+ GREATEST_SET_TIME(greatest_info.begin)
+
+#define GREATEST_MAIN_END() \
+ 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, "\n"); \
+ fprintf(GREATEST_STDOUT, \
+ "Pass: %u, fail: %u, skip: %u.\n", \
+ greatest_info.passed, \
+ greatest_info.failed, greatest_info.skipped); \
+ } \
+ return (greatest_info.failed > 0 \
+ ? EXIT_FAILURE : EXIT_SUCCESS); \
+ } 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_STR_EQ GREATEST_ASSERT_STR_EQ
+#define ASSERT_FALSEm GREATEST_ASSERT_FALSEm
+#define ASSERT_EQm GREATEST_ASSERT_EQm
+#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
+
+#if __STDC_VERSION__ >= 19901L
+#endif /* C99 */
+#define RUN_TESTp GREATEST_RUN_TESTp
+#endif /* USE_ABBREVS */
+
+#endif
diff --git a/espfs/heatshrink/heatshrink.c b/espfs/heatshrink/heatshrink.c
new file mode 100644
index 0000000..9de553f
--- /dev/null
+++ b/espfs/heatshrink/heatshrink.c
@@ -0,0 +1,446 @@
+#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
+
+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 uncompresses 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 (uncompress)\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);
+ }
+ } else if (m == IO_WRITE) {
+ if (0 == strcmp("-", fname)) {
+ io->fd = STDOUT_FILENO;
+ } else {
+ io->fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC /*| O_EXCL*/, 0644);
+ }
+ }
+
+ if (io->fd == -1) { /* failed to open */
+ free(io);
+ 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) {
+ printf("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) { err(1, "read"); }
+ io->total += read_sz;
+ if (read_sz == 0) { /* EOF */
+ if (close(io->fd) < 0) { 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) { 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) { 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) {
+ printf("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) { 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) {
+ printf("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) { 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))) {
+ printf("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 (cfg.cmd == OP_ENC) {
+ return encode(&cfg);
+ } else if (cfg.cmd == OP_DEC) {
+ return decode(&cfg);
+ } else {
+ usage();
+ }
+}
diff --git a/espfs/heatshrink/heatshrink_common.h b/espfs/heatshrink/heatshrink_common.h
new file mode 100644
index 0000000..0d396b9
--- /dev/null
+++ b/espfs/heatshrink/heatshrink_common.h
@@ -0,0 +1,20 @@
+#ifndef HEATSHRINK_H
+#define HEATSHRINK_H
+
+#define HEATSHRINK_AUTHOR "Scott Vokes "
+#define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink"
+
+/* Version 0.3.1 */
+#define HEATSHRINK_VERSION_MAJOR 0
+#define HEATSHRINK_VERSION_MINOR 3
+#define HEATSHRINK_VERSION_PATCH 1
+
+#define HEATSHRINK_MIN_WINDOW_BITS 4
+#define HEATSHRINK_MAX_WINDOW_BITS 15
+
+#define HEATSHRINK_MIN_LOOKAHEAD_BITS 2
+
+#define HEATSHRINK_LITERAL_MARKER 0x01
+#define HEATSHRINK_BACKREF_MARKER 0x00
+
+#endif
diff --git a/espfs/heatshrink/heatshrink_config.h b/espfs/heatshrink/heatshrink_config.h
new file mode 100644
index 0000000..51d4772
--- /dev/null
+++ b/espfs/heatshrink/heatshrink_config.h
@@ -0,0 +1,24 @@
+#ifndef HEATSHRINK_CONFIG_H
+#define HEATSHRINK_CONFIG_H
+
+/* Should functionality assuming dynamic allocation be used? */
+#define HEATSHRINK_DYNAMIC_ALLOC 1
+
+#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
diff --git a/espfs/heatshrink/heatshrink_decoder.c b/espfs/heatshrink/heatshrink_decoder.c
new file mode 100644
index 0000000..b92be13
--- /dev/null
+++ b/espfs/heatshrink/heatshrink_decoder.c
@@ -0,0 +1,382 @@
+#include
+#include
+#include "heatshrink_decoder.h"
+
+/* States for the polling state machine. */
+typedef enum {
+ HSDS_EMPTY, /* no input to process */
+ HSDS_INPUT_AVAILABLE, /* new input, completely unprocessed */
+ 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 */
+ HSDS_CHECK_FOR_MORE_INPUT, /* check if input is exhausted */
+} 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[] = {
+ "empty",
+ "input_available",
+ "yield_literal",
+ "backref_index",
+ "backref_count",
+ "yield_backref",
+ "check_for_more_input",
+};
+#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 ((uint32_t)-1)
+
+/* Forward references. */
+static uint32_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_EMPTY;
+ 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;
+ hsd->bit_accumulator = 0x00000000;
+}
+
+/* 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;
+ if (hsd->state == HSDS_EMPTY) {
+ hsd->state = HSDS_INPUT_AVAILABLE;
+ hsd->input_index = 0;
+ }
+ *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_input_available(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);
+static HSD_state st_check_for_input(heatshrink_decoder *hsd);
+
+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_EMPTY:
+ return HSDR_POLL_EMPTY;
+ case HSDS_INPUT_AVAILABLE:
+ hsd->state = st_input_available(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;
+ case HSDS_CHECK_FOR_MORE_INPUT:
+ hsd->state = st_check_for_input(hsd);
+ 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_input_available(heatshrink_decoder *hsd) {
+ uint32_t bits = get_bits(hsd, 1); // get tag bit
+ 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) {
+ uint32_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_CHECK_FOR_MORE_INPUT;
+ } 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);
+ uint32_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);
+ uint32_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);
+ uint32_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);
+ uint32_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) {
+ 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 <= 1 << BACKREF_COUNT_BITS(hsd));
+
+ for (size_t 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_CHECK_FOR_MORE_INPUT; }
+ }
+ return HSDS_YIELD_BACKREF;
+}
+
+static HSD_state st_check_for_input(heatshrink_decoder *hsd) {
+ return (hsd->input_size == 0) ? HSDS_EMPTY : HSDS_INPUT_AVAILABLE;
+}
+
+/* Get the next COUNT bits from the input buffer, saving incremental progress.
+ * Returns NO_BITS on end of input, or if more than 31 bits are requested. */
+static uint32_t get_bits(heatshrink_decoder *hsd, uint8_t count) {
+ if (count > 31) { 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 (int 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",
+ hsd->bit_accumulator, hsd->bit_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;
+ }
+ hsd->bit_accumulator <<= 1;
+ if (hsd->current_byte & hsd->bit_index) {
+ hsd->bit_accumulator |= 0x01;
+ if (0) {
+ LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n",
+ hsd->bit_accumulator, hsd->bit_index);
+ }
+ } else {
+ if (0) {
+ LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n",
+ hsd->bit_accumulator, hsd->bit_index);
+ }
+ }
+ hsd->bit_index >>= 1;
+ }
+
+ uint32_t res = 0;
+ res = hsd->bit_accumulator;
+ hsd->bit_accumulator = 0x00000000;
+ if (count > 1) { LOG(" -- accumulated %08x\n", res); }
+ return res;
+}
+
+HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) {
+ if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; }
+ switch (hsd->state) {
+ case HSDS_EMPTY:
+ return HSDR_FINISH_DONE;
+
+ /* 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;
+}
diff --git a/espfs/heatshrink/heatshrink_decoder.h b/espfs/heatshrink/heatshrink_decoder.h
new file mode 100644
index 0000000..c1eb144
--- /dev/null
+++ b/espfs/heatshrink/heatshrink_decoder.h
@@ -0,0 +1,101 @@
+#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 */
+ uint16_t bit_accumulator;
+ 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
diff --git a/espfs/heatshrink/heatshrink_encoder.c b/espfs/heatshrink/heatshrink_encoder.c
new file mode 100644
index 0000000..ede5f60
--- /dev/null
+++ b/espfs/heatshrink/heatshrink_encoder.c
@@ -0,0 +1,650 @@
+#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,
+ FLAG_HAS_LITERAL = 0x02,
+ FLAG_ON_FINAL_LITERAL = 0x04,
+ FLAG_BACKLOG_IS_PARTIAL = 0x08,
+ FLAG_BACKLOG_IS_FILLED = 0x10,
+};
+
+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 int backlog_is_partial(heatshrink_encoder *hse);
+static int backlog_is_filled(heatshrink_encoder *hse);
+static int on_final_literal(heatshrink_encoder *hse);
+static void save_backlog(heatshrink_encoder *hse);
+static int has_literal(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 ? 0 : lookahead_sz)) {
+ /* Current search buffer is exhausted, copy it into the
+ * backlog and await more input. */
+ LOG("-- end of search @ %d, saving backlog\n", msi);
+ return HSES_SAVE_BACKLOG;
+ }
+
+ uint16_t input_offset = get_input_offset(hse);
+ uint16_t end = input_offset + msi;
+
+ uint16_t start = 0;
+ if (backlog_is_filled(hse)) { /* last WINDOW_LENGTH bytes */
+ start = end - window_length + 1;
+ } else if (backlog_is_partial(hse)) { /* clamp to available data */
+ start = end - window_length + 1;
+ if (start < lookahead_sz) { start = lookahead_sz; }
+ } else { /* only scan available input */
+ start = input_offset;
+ }
+
+ 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->flags |= FLAG_HAS_LITERAL;
+ 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 << hse->window_sz2 /*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);
+ hse->flags &= ~FLAG_HAS_LITERAL;
+ if (on_final_literal(hse)) { return HSES_FLUSH_BITS; }
+ return hse->match_length > 0 ? HSES_YIELD_TAG_BIT : 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) {
+ if (is_finishing(hse)) {
+ /* copy remaining literal (if necessary) */
+ if (has_literal(hse)) {
+ hse->flags |= FLAG_ON_FINAL_LITERAL;
+ return HSES_YIELD_TAG_BIT;
+ } else {
+ return HSES_FLUSH_BITS;
+ }
+ } else {
+ 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);
+ uint16_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 backlog_is_partial(heatshrink_encoder *hse) {
+ return hse->flags & FLAG_BACKLOG_IS_PARTIAL;
+}
+
+static int backlog_is_filled(heatshrink_encoder *hse) {
+ return hse->flags & FLAG_BACKLOG_IS_FILLED;
+}
+
+static int on_final_literal(heatshrink_encoder *hse) {
+ return hse->flags & FLAG_ON_FINAL_LITERAL;
+}
+
+static int has_literal(heatshrink_encoder *hse) {
+ return (hse->flags & FLAG_HAS_LITERAL);
+}
+
+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;
+ const uint16_t break_even_point = 3;
+ 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 >= start) {
+ 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 >= start; 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
+
+ if (match_maxlen >= break_even_point) {
+ 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);
+
+ if (backlog_is_partial(hse)) {
+ /* The whole backlog is filled in now, so include it in scans. */
+ hse->flags |= FLAG_BACKLOG_IS_FILLED;
+ } else {
+ /* Include backlog, except for the first lookahead_sz bytes, which
+ * are still undefined. */
+ hse->flags |= FLAG_BACKLOG_IS_PARTIAL;
+ }
+ hse->match_scan_index = 0;
+ hse->input_size -= input_buf_sz - rem;
+}
diff --git a/espfs/heatshrink/heatshrink_encoder.h b/espfs/heatshrink/heatshrink_encoder.h
new file mode 100644
index 0000000..18c1773
--- /dev/null
+++ b/espfs/heatshrink/heatshrink_encoder.h
@@ -0,0 +1,109 @@
+#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
diff --git a/espfs/heatshrink/test_heatshrink_dynamic.c b/espfs/heatshrink/test_heatshrink_dynamic.c
new file mode 100644
index 0000000..1e18a69
--- /dev/null
+++ b/espfs/heatshrink/test_heatshrink_dynamic.c
@@ -0,0 +1,999 @@
+#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(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, 4);
+ 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, 4);
+ 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, 0x40, 0x80}; //"foofoo"
+ uint8_t output[6];
+ heatshrink_decoder *hsd = heatshrink_decoder_alloc(256, 7, 7);
+ 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(6, out_sz);
+ 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);
+ heatshrink_decoder *hsd = heatshrink_decoder_alloc(cfg->decoder_input_buffer_size,
+ cfg->window_sz2, cfg->lookahead_sz2);
+ 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) {
+ ASSERT(heatshrink_encoder_sink(hse, &input[sunk], input_size - sunk, &count) >= 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(integration);
+ #ifdef HEATSHRINK_HAS_THEFT
+ RUN_SUITE(properties);
+ #endif
+ GREATEST_MAIN_END(); /* display results */
+}
diff --git a/espfs/heatshrink/test_heatshrink_dynamic_theft.c b/espfs/heatshrink/test_heatshrink_dynamic_theft.c
new file mode 100644
index 0000000..2752886
--- /dev/null
+++ b/espfs/heatshrink/test_heatshrink_dynamic_theft.c
@@ -0,0 +1,521 @@
+#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"
+#include "greatest_theft.h"
+
+#if !HEATSHRINK_DYNAMIC_ALLOC
+#error Must set HEATSHRINK_DYNAMIC_ALLOC to 1 for this test suite.
+#endif
+
+SUITE(properties);
+
+typedef struct {
+ int limit;
+ int fails;
+ int dots;
+} 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 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) {
+ /* Make a buffer large enough for the output: 4 KB of input with
+ * each 16 bits becoming up to 16 bytes will fit in a 64 KB buffer.
+ * (4 KB of input comes from `env.limit = 1 << 12;` below.) */
+ uint8_t output[64 * 1024];
+ heatshrink_decoder *hsd = heatshrink_decoder_alloc((64 * 1024L) - 1, 12, 4);
+ 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, sizeof(output), &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 },
+ .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 theft_trial_res prop_encoded_and_decoded_data_should_match(void *input) {
+ uint8_t e_output[64 * 1024];
+ uint8_t d_output[64 * 1024];
+ heatshrink_encoder *hse = heatshrink_encoder_alloc(12, 4);
+ if (hse == NULL) { return THEFT_TRIAL_ERROR; }
+ heatshrink_decoder *hsd = heatshrink_decoder_alloc(4096, 12, 4);
+ if (hsd == NULL) { return THEFT_TRIAL_ERROR; }
+
+ rbuf *r = (rbuf *)input;
+
+ size_t e_input_size = 0;
+ HSE_sink_res esres = heatshrink_encoder_sink(hse,
+ r->buf, r->size, &e_input_size);
+ if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; }
+ if (e_input_size != r->size) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
+
+ HSE_finish_res efres = heatshrink_encoder_finish(hse);
+ if (efres != HSER_FINISH_MORE) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
+
+ size_t e_output_size = 0;
+ HSE_poll_res epres = heatshrink_encoder_poll(hse,
+ e_output, sizeof(e_output), &e_output_size);
+ if (epres != HSER_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
+
+ size_t count = 0;
+ HSD_sink_res sres = heatshrink_decoder_sink(hsd, e_output, e_output_size, &count);
+ if (sres != HSDR_SINK_OK) { return THEFT_TRIAL_ERROR; }
+
+ size_t d_output_size = 0;
+ HSD_poll_res pres = heatshrink_decoder_poll(hsd, d_output,
+ sizeof(d_output), &d_output_size);
+ if (pres != HSDR_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
+ if (d_output_size != r->size) {
+ printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL;
+ }
+
+ if (0 != memcmp(d_output, r->buf, d_output_size)) {
+ return THEFT_TRIAL_FAIL;
+ }
+
+ HSD_finish_res fres = heatshrink_decoder_finish(hsd);
+ if (fres != HSDR_FINISH_DONE) { printf("FAIL %d\n", __LINE__); 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 },
+ .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) {
+ uint8_t output[32 * 1024];
+ heatshrink_encoder *hse = heatshrink_encoder_alloc(12, 4);
+ if (hse == NULL) { return THEFT_TRIAL_ERROR; }
+
+ rbuf *r = (rbuf *)input;
+
+ size_t input_size = 0;
+ HSE_sink_res esres = heatshrink_encoder_sink(hse,
+ r->buf, r->size, &input_size);
+ if (esres != HSER_SINK_OK) { return THEFT_TRIAL_ERROR; }
+ /* Assumes data fits in one sink, failure here means buffer must be larger. */
+ if (input_size != r->size) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
+
+ HSE_finish_res efres = heatshrink_encoder_finish(hse);
+ 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, sizeof(output), &output_size);
+ if (epres != HSER_POLL_EMPTY) { printf("FAIL %d\n", __LINE__); return THEFT_TRIAL_FAIL; }
+
+ size_t ceil_9_8s = ceil_nine_eighths(r->size);
+ if (output_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 },
+ .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) {
+ uint8_t output[64 * 1024];
+ heatshrink_encoder *hse = heatshrink_encoder_alloc(8, 4);
+ 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, sizeof(output), &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 },
+ .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) {
+ uint8_t output[64 * 1024];
+ heatshrink_decoder *hsd = heatshrink_decoder_alloc(512, 8, 4);
+ if (hsd == 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;
+ HSD_sink_res sres = heatshrink_decoder_sink(hsd,
+ &r->buf[sunk], r->size - sunk, &input_size);
+ if (sres != HSER_SINK_OK) { 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) { 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 },
+ .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();
+}
+
+SUITE(properties) {
+ 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);
+}
+#else
+struct because_iso_c_requires_at_least_one_declaration;
+#endif
diff --git a/espfs/heatshrink/test_heatshrink_static.c b/espfs/heatshrink/test_heatshrink_static.c
new file mode 100644
index 0000000..e9c9754
--- /dev/null
+++ b/espfs/heatshrink/test_heatshrink_static.c
@@ -0,0 +1,167 @@
+#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 */
+}
diff --git a/espfs/heatshrink_decoder.c b/espfs/heatshrink_decoder.c
index 522b560..9a4a9f7 100644
--- a/espfs/heatshrink_decoder.c
+++ b/espfs/heatshrink_decoder.c
@@ -13,7 +13,7 @@
#endif
#include "heatshrink_config_custom.h"
-#include "../lib/heatshrink/heatshrink_decoder.c"
+#include "heatshrink/heatshrink_decoder.c"
#endif
diff --git a/espfs/mkespfsimage/Makefile b/espfs/mkespfsimage/Makefile
index 8a39d46..26484d4 100644
--- a/espfs/mkespfsimage/Makefile
+++ b/espfs/mkespfsimage/Makefile
@@ -1,24 +1,36 @@
GZIP_COMPRESSION ?= no
USE_HEATSHRINK ?= yes
-CFLAGS=-I../../lib/heatshrink -I.. -std=gnu99
+TARGET = mkespfsimage.exe
+
+CC = gcc
+LD = $(CC)
+CFLAGS=-c -I../../heatshrink -I.. -Imman-win32 -std=gnu99
+LDFLAGS=-Lmman-win32 -lmman
+
ifeq ("$(GZIP_COMPRESSION)","yes")
-CFLAGS += -DESPFS_GZIP
+CFLAGS += -DESPFS_GZIP
+LDFLAGS += -lz
endif
ifeq ("$(USE_HEATSHRINK)","yes")
-CFLAGS += -DESPFS_HEATSHRINK
+CFLAGS += -DESPFS_HEATSHRINK
endif
-OBJS=main.o heatshrink_encoder.o
-TARGET=mkespfsimage
+OBJECTS = main.o heatshrink_encoder.o
-$(TARGET): $(OBJS)
-ifeq ("$(GZIP_COMPRESSION)","yes")
- $(CC) -o $@ $^ -lz
-else
- $(CC) -o $@ $^
-endif
+all: libmman $(TARGET)
+
+libmman:
+ $(Q) make -C mman-win32
+
+$(TARGET): $(OBJECTS)
+ $(LD) -o $@ $^ $(LDFLAGS)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -o $@ $^
clean:
- rm -f $(TARGET) $(OBJS)
\ No newline at end of file
+ rm -f $(OBJECTS) $(TARGET)
+
+.PHONY: all clean
diff --git a/espfs/mkespfsimage/Makefile.linux b/espfs/mkespfsimage/Makefile.linux
new file mode 100644
index 0000000..f1ae1aa
--- /dev/null
+++ b/espfs/mkespfsimage/Makefile.linux
@@ -0,0 +1,24 @@
+GZIP_COMPRESSION ?= no
+USE_HEATSHRINK ?= yes
+
+CFLAGS=-I../../heatshrink -I../../include -I.. -std=gnu99
+ifeq ("$(GZIP_COMPRESSION)","yes")
+CFLAGS += -DESPFS_GZIP
+endif
+
+ifeq ("$(USE_HEATSHRINK)","yes")
+CFLAGS += -DESPFS_HEATSHRINK
+endif
+
+OBJS=main.o heatshrink_encoder.o
+TARGET=mkespfsimage
+
+$(TARGET): $(OBJS)
+ifeq ("$(GZIP_COMPRESSION)","yes")
+ $(CC) -o $@ $^ -lz
+else
+ $(CC) -o $@ $^
+endif
+
+clean:
+ rm -f $(TARGET) $(OBJS)
\ No newline at end of file
diff --git a/espfs/mkespfsimage/Makefile.windows b/espfs/mkespfsimage/Makefile.windows
new file mode 100644
index 0000000..15aa8e3
--- /dev/null
+++ b/espfs/mkespfsimage/Makefile.windows
@@ -0,0 +1,33 @@
+GZIP_COMPRESSION ?= no
+USE_HEATSHRINK ?= yes
+
+TARGET = mkespfsimage.exe
+
+CC = gcc
+LD = $(CC)
+CFLAGS=-c -I../../heatshrink -I.. -Imman-win32 -std=gnu99
+LDFLAGS=-Lmman-win32 -lmman
+
+ifeq ("$(GZIP_COMPRESSION)","yes")
+CFLAGS += -DESPFS_GZIP
+LDFLAGS += -lz
+endif
+
+ifeq ("$(USE_HEATSHRINK)","yes")
+CFLAGS += -DESPFS_HEATSHRINK
+endif
+
+OBJECTS = main.o heatshrink_encoder.o
+
+all: $(TARGET)
+
+$(TARGET): $(OBJECTS)
+ $(LD) -o $@ $^ $(LDFLAGS)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -o $@ $^
+
+clean:
+ rm -f $(OBJECTS) $(TARGET)
+
+.PHONY: all clean
diff --git a/espfs/mkespfsimage/build-linux.sh b/espfs/mkespfsimage/build-linux.sh
new file mode 100644
index 0000000..69e3166
--- /dev/null
+++ b/espfs/mkespfsimage/build-linux.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+make -f Makefile.linux clean
+make -f Makefile.linux USE_HEATSHRINK="yes" GZIP_COMPRESSION="no"
diff --git a/espfs/mkespfsimage/build-win32.sh b/espfs/mkespfsimage/build-win32.sh
new file mode 100644
index 0000000..f552ea9
--- /dev/null
+++ b/espfs/mkespfsimage/build-win32.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+cd mman-win32
+./configure && make
+cd ..
+make -f Makefile.windows clean
+make -f Makefile.windows USE_HEATSHRINK="yes" GZIP_COMPRESSION="no"
diff --git a/espfs/mkespfsimage/espfsformat.h b/espfs/mkespfsimage/espfsformat.h
new file mode 100644
index 0000000..8ce5549
--- /dev/null
+++ b/espfs/mkespfsimage/espfsformat.h
@@ -0,0 +1,33 @@
+#ifndef ESPROFSFORMAT_H
+#define ESPROFSFORMAT_H
+
+/*
+Stupid cpio-like tool to make read-only 'filesystems' that live on the flash SPI chip of the module.
+Can (will) use lzf compression (when I come around to it) to make shit quicker. Aligns names, files,
+headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself
+when trying to do a <4byte or unaligned read.
+*/
+
+/*
+The idea 'borrows' from cpio: it's basically a concatenation of {header, filename, file} data.
+Header, filename and file data is 32-bit aligned. The last file is indicated by data-less header
+with the FLAG_LASTFILE flag set.
+*/
+
+
+#define FLAG_LASTFILE (1<<0)
+#define FLAG_GZIP (1<<1)
+#define COMPRESS_NONE 0
+#define COMPRESS_HEATSHRINK 1
+#define ESPFS_MAGIC 0x73665345
+
+typedef struct {
+ int32_t magic;
+ int8_t flags;
+ int8_t compression;
+ int16_t nameLen;
+ int32_t fileLenComp;
+ int32_t fileLenDecomp;
+} __attribute__((packed)) EspFsHeader;
+
+#endif
\ No newline at end of file
diff --git a/espfs/mkespfsimage/heatshrink_encoder.c b/espfs/mkespfsimage/heatshrink_encoder.c
index b563970..fd82c9a 100644
--- a/espfs/mkespfsimage/heatshrink_encoder.c
+++ b/espfs/mkespfsimage/heatshrink_encoder.c
@@ -1,4 +1,4 @@
//Stupid wraparound include to make sure object file doesn't end up in heatshrink dir
#ifdef ESPFS_HEATSHRINK
-#include "../lib/heatshrink/heatshrink_encoder.c"
+#include "../heatshrink/heatshrink_encoder.c"
#endif
\ No newline at end of file
diff --git a/espfs/mkespfsimage/main.c b/espfs/mkespfsimage/main.c
index 4d5da0d..20a2b00 100644
--- a/espfs/mkespfsimage/main.c
+++ b/espfs/mkespfsimage/main.c
@@ -5,22 +5,31 @@
#include
#include
#include
+#include
+#include "espfs.h"
+#ifdef __MINGW32__
+#include
+#else
#include
+#endif
+#ifdef __WIN32__
+#include
+#else
#include
+#endif
#include
-#include "espfs.h"
#include "espfsformat.h"
//Heatshrink
#ifdef ESPFS_HEATSHRINK
-#include "heatshrink_common.h"
-#include "heatshrink_config.h"
-#include "heatshrink_encoder.h"
+#include "../heatshrink/heatshrink_common.h"
+#include "../heatshrink/heatshrink_config.h"
+#include "../heatshrink/heatshrink_encoder.h"
#endif
//Gzip
#ifdef ESPFS_GZIP
-// If compiler complains about missing header, try running "sudo apt-get install zlib1g-dev"
+// If compiler complains about missing header, try running "sudo apt-get install zlib1g-dev"
// to install missing package.
#include
#endif
@@ -175,7 +184,7 @@ int parseGzipExtensions(char *input) {
}
#endif
-int handleFile(int f, char *name, int compression, int level, char **compName, off_t *csizePtr) {
+int handleFile(int f, char *name, int compression, int level, char **compName) {
char *fdat, *cdat;
off_t size, csize;
EspFsHeader h;
@@ -229,7 +238,7 @@ int handleFile(int f, char *name, int compression, int level, char **compName, o
h.nameLen=htoxs(h.nameLen);
h.fileLenComp=htoxl(csize);
h.fileLenDecomp=htoxl(size);
-
+
write(1, &h, sizeof(EspFsHeader));
write(1, name, nameLen);
while (nameLen&3) {
@@ -257,7 +266,6 @@ int handleFile(int f, char *name, int compression, int level, char **compName, o
*compName = "unknown";
}
}
- *csizePtr = csize;
return (csize*100)/size;
}
@@ -310,7 +318,7 @@ int main(int argc, char **argv) {
#ifdef ESPFS_GZIP
if (gzipExtensions == NULL) {
- parseGzipExtensions(strdup("html,css,js,ico"));
+ parseGzipExtensions(strdup("html,css,js"));
}
#endif
@@ -334,6 +342,10 @@ int main(int argc, char **argv) {
exit(0);
}
+#ifdef __WIN32__
+ setmode(fileno(stdout), _O_BINARY);
+#endif
+
while(fgets(fileName, sizeof(fileName), stdin)) {
//Kill off '\n' at the end
fileName[strlen(fileName)-1]=0;
@@ -347,9 +359,8 @@ int main(int argc, char **argv) {
f=open(fileName, O_RDONLY);
if (f>0) {
char *compName = "unknown";
- off_t csize;
- rate=handleFile(f, realName, compType, compLvl, &compName, &csize);
- fprintf(stderr, "%-16s (%3d%%, %s, %4u bytes)\n", realName, rate, compName, (uint32_t)csize);
+ rate=handleFile(f, realName, compType, compLvl, &compName);
+ fprintf(stderr, "%s (%d%%, %s)\n", realName, rate, compName);
close(f);
} else {
perror(fileName);
diff --git a/espfs/mkespfsimage/mman-win32/Makefile b/espfs/mkespfsimage/mman-win32/Makefile
new file mode 100644
index 0000000..5624c1b
--- /dev/null
+++ b/espfs/mkespfsimage/mman-win32/Makefile
@@ -0,0 +1,48 @@
+#
+# mman-win32 (mingw32) Makefile
+#
+include config.mak
+
+ifeq ($(BUILD_STATIC),yes)
+ TARGETS+=libmman.a
+ INSTALL+=static-install
+endif
+ifeq ($(BUILD_MSVC),yes)
+ SHFLAGS+=-Wl,--output-def,libmman.def
+ INSTALL+=lib-install
+endif
+
+all: $(TARGETS)
+
+mman.o: mman.c mman.h
+ $(CC) -o mman.o -c mman.c -Wall -O3 -fomit-frame-pointer
+
+libmman.a: mman.o
+ $(AR) cru libmman.a mman.o
+ $(RANLIB) libmman.a
+
+static-install:
+ mkdir -p $(DESTDIR)$(libdir)
+ cp libmman.a $(DESTDIR)$(libdir)
+ mkdir -p $(DESTDIR)$(incdir)
+ cp mman.h $(DESTDIR)$(incdir)
+
+lib-install:
+ mkdir -p $(DESTDIR)$(libdir)
+ cp libmman.lib $(DESTDIR)$(libdir)
+
+install: $(INSTALL)
+
+test.exe: test.c mman.c mman.h
+ $(CC) -o test.exe test.c -L. -lmman
+
+test: $(TARGETS) test.exe
+ test.exe
+
+clean::
+ rm -f mman.o libmman.a libmman.def libmman.lib test.exe *.dat
+
+distclean: clean
+ rm -f config.mak
+
+.PHONY: clean distclean install test
diff --git a/espfs/mkespfsimage/mman-win32/config.mak b/espfs/mkespfsimage/mman-win32/config.mak
new file mode 100644
index 0000000..2d2ae1b
--- /dev/null
+++ b/espfs/mkespfsimage/mman-win32/config.mak
@@ -0,0 +1,11 @@
+# Automatically generated by configure
+PREFIX=/mingw
+libdir=/mingw/lib
+incdir=/mingw/include/sys
+AR=ar
+CC=gcc
+RANLIB=ranlib
+STRIP=strip
+BUILD_STATIC=yes
+BUILD_MSVC=
+LIBCMD=echo ignoring lib
diff --git a/espfs/mkespfsimage/mman-win32/configure b/espfs/mkespfsimage/mman-win32/configure
new file mode 100644
index 0000000..c928f11
--- /dev/null
+++ b/espfs/mkespfsimage/mman-win32/configure
@@ -0,0 +1,157 @@
+#!/bin/sh
+# mmap-win32 configure script
+#
+# Parts copied from FFmpeg's configure
+#
+
+set_all(){
+ value=$1
+ shift
+ for var in $*; do
+ eval $var=$value
+ done
+}
+
+enable(){
+ set_all yes $*
+}
+
+disable(){
+ set_all no $*
+}
+
+enabled(){
+ eval test "x\$$1" = "xyes"
+}
+
+disabled(){
+ eval test "x\$$1" = "xno"
+}
+
+show_help(){
+ echo "Usage: configure [options]"
+ echo "Options: [defaults in brackets after descriptions]"
+ echo "All \"enable\" options have \"disable\" counterparts"
+ echo
+ echo " --help print this message"
+ echo " --prefix=PREFIX install in PREFIX [$PREFIX]"
+ echo " --libdir=DIR install libs in DIR [$PREFIX/lib]"
+ echo " --incdir=DIR install includes in DIR [$PREFIX/include]"
+ echo " --enable-static build static libraries [yes]"
+ echo " --enable-msvc create msvc-compatible import lib [auto]"
+ echo
+ echo " --cc=CC use C compiler CC [$cc_default]"
+ echo " --cross-prefix=PREFIX use PREFIX for compilation tools [$cross_prefix]"
+ exit 1
+}
+
+die_unknown(){
+ echo "Unknown option \"$1\"."
+ echo "See $0 --help for available options."
+ exit 1
+}
+
+PREFIX="/mingw"
+libdir="${PREFIX}/lib"
+incdir="${PREFIX}/include/sys"
+ar="ar"
+cc_default="gcc"
+ranlib="ranlib"
+strip="strip"
+
+DEFAULT="msvc
+"
+
+DEFAULT_YES="static
+ stripping
+"
+
+CMDLINE_SELECT="$DEFAULT
+ $DEFAULT_NO
+ $DEFAULT_YES
+"
+
+enable $DEFAULT_YES
+disable $DEFAULT_NO
+
+for opt do
+ optval="${opt#*=}"
+ case "$opt" in
+ --help)
+ show_help
+ ;;
+ --prefix=*)
+ PREFIX="$optval"
+ ;;
+ --libdir=*)
+ libdir="$optval"
+ ;;
+ --incdir=*)
+ incdir="$optval"
+ ;;
+ --cc=*)
+ cc="$optval"
+ ;;
+ --cross-prefix=*)
+ cross_prefix="$optval"
+ ;;
+ --enable-?*|--disable-?*)
+ eval `echo "$opt" | sed 's/--/action=/;s/-/ option=/;s/-/_/g'`
+ echo "$CMDLINE_SELECT" | grep -q "^ *$option\$" || die_unknown $opt
+ $action $option
+ ;;
+ *)
+ die_unknown $opt
+ ;;
+ esac
+done
+
+ar="${cross_prefix}${ar}"
+cc_default="${cross_prefix}${cc_default}"
+ranlib="${cross_prefix}${ranlib}"
+strip="${cross_prefix}${strip}"
+
+if ! test -z $cc; then
+ cc_default="${cc}"
+fi
+cc="${cc_default}"
+
+disabled static && {
+ echo "At least one library type must be set.";
+ exit 1;
+}
+
+if enabled msvc; then
+ lib /? > /dev/null 2>&1 /dev/null || {
+ echo "MSVC's lib command not found."
+ echo "Make sure MSVC is installed and its bin folder is in your \$PATH."
+ exit 1
+ }
+fi
+
+if ! enabled stripping; then
+ strip="echo ignoring strip"
+fi
+
+enabled msvc && libcmd="lib" || libcmd="echo ignoring lib"
+
+echo "# Automatically generated by configure" > config.mak
+echo "PREFIX=$PREFIX" >> config.mak
+echo "libdir=$libdir" >> config.mak
+echo "incdir=$incdir" >> config.mak
+echo "AR=$ar" >> config.mak
+echo "CC=$cc" >> config.mak
+echo "RANLIB=$ranlib" >> config.mak
+echo "STRIP=$strip" >> config.mak
+echo "BUILD_STATIC=$static" >> config.mak
+echo "BUILD_MSVC=$msvc" >> config.mak
+echo "LIBCMD=$libcmd" >> config.mak
+
+echo "prefix: $PREFIX"
+echo "libdir: $libdir"
+echo "incdir: $incdir"
+echo "ar: $ar"
+echo "cc: $cc"
+echo "ranlib: $ranlib"
+echo "strip: $strip"
+echo "static: $static"
diff --git a/espfs/mkespfsimage/mman-win32/mman.c b/espfs/mkespfsimage/mman-win32/mman.c
new file mode 100644
index 0000000..486ed94
--- /dev/null
+++ b/espfs/mkespfsimage/mman-win32/mman.c
@@ -0,0 +1,180 @@
+
+#include
+#include
+#include
+
+#include "mman.h"
+
+#ifndef FILE_MAP_EXECUTE
+#define FILE_MAP_EXECUTE 0x0020
+#endif /* FILE_MAP_EXECUTE */
+
+static int __map_mman_error(const DWORD err, const int deferr)
+{
+ if (err == 0)
+ return 0;
+ //TODO: implement
+ return err;
+}
+
+static DWORD __map_mmap_prot_page(const int prot)
+{
+ DWORD protect = 0;
+
+ if (prot == PROT_NONE)
+ return protect;
+
+ if ((prot & PROT_EXEC) != 0)
+ {
+ protect = ((prot & PROT_WRITE) != 0) ?
+ PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
+ }
+ else
+ {
+ protect = ((prot & PROT_WRITE) != 0) ?
+ PAGE_READWRITE : PAGE_READONLY;
+ }
+
+ return protect;
+}
+
+static DWORD __map_mmap_prot_file(const int prot)
+{
+ DWORD desiredAccess = 0;
+
+ if (prot == PROT_NONE)
+ return desiredAccess;
+
+ if ((prot & PROT_READ) != 0)
+ desiredAccess |= FILE_MAP_READ;
+ if ((prot & PROT_WRITE) != 0)
+ desiredAccess |= FILE_MAP_WRITE;
+ if ((prot & PROT_EXEC) != 0)
+ desiredAccess |= FILE_MAP_EXECUTE;
+
+ return desiredAccess;
+}
+
+void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
+{
+ HANDLE fm, h;
+
+ void * map = MAP_FAILED;
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4293)
+#endif
+
+ const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) ?
+ (DWORD)off : (DWORD)(off & 0xFFFFFFFFL);
+ const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) ?
+ (DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL);
+ const DWORD protect = __map_mmap_prot_page(prot);
+ const DWORD desiredAccess = __map_mmap_prot_file(prot);
+
+ const off_t maxSize = off + (off_t)len;
+
+ const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) ?
+ (DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL);
+ const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) ?
+ (DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL);
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+ errno = 0;
+
+ if (len == 0
+ /* Unsupported flag combinations */
+ || (flags & MAP_FIXED) != 0
+ /* Usupported protection combinations */
+ || prot == PROT_EXEC)
+ {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
+ h = ((flags & MAP_ANONYMOUS) == 0) ?
+ (HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE;
+
+ if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE)
+ {
+ errno = EBADF;
+ return MAP_FAILED;
+ }
+
+ fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL);
+
+ if (fm == NULL)
+ {
+ errno = __map_mman_error(GetLastError(), EPERM);
+ return MAP_FAILED;
+ }
+
+ map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len);
+
+ CloseHandle(fm);
+
+ if (map == NULL)
+ {
+ errno = __map_mman_error(GetLastError(), EPERM);
+ return MAP_FAILED;
+ }
+
+ return map;
+}
+
+int munmap(void *addr, size_t len)
+{
+ if (UnmapViewOfFile(addr))
+ return 0;
+
+ errno = __map_mman_error(GetLastError(), EPERM);
+
+ return -1;
+}
+
+int mprotect(void *addr, size_t len, int prot)
+{
+ DWORD newProtect = __map_mmap_prot_page(prot);
+ DWORD oldProtect = 0;
+
+ if (VirtualProtect(addr, len, newProtect, &oldProtect))
+ return 0;
+
+ errno = __map_mman_error(GetLastError(), EPERM);
+
+ return -1;
+}
+
+int msync(void *addr, size_t len, int flags)
+{
+ if (FlushViewOfFile(addr, len))
+ return 0;
+
+ errno = __map_mman_error(GetLastError(), EPERM);
+
+ return -1;
+}
+
+int mlock(const void *addr, size_t len)
+{
+ if (VirtualLock((LPVOID)addr, len))
+ return 0;
+
+ errno = __map_mman_error(GetLastError(), EPERM);
+
+ return -1;
+}
+
+int munlock(const void *addr, size_t len)
+{
+ if (VirtualUnlock((LPVOID)addr, len))
+ return 0;
+
+ errno = __map_mman_error(GetLastError(), EPERM);
+
+ return -1;
+}
diff --git a/espfs/mkespfsimage/mman-win32/mman.h b/espfs/mkespfsimage/mman-win32/mman.h
new file mode 100644
index 0000000..ffa3748
--- /dev/null
+++ b/espfs/mkespfsimage/mman-win32/mman.h
@@ -0,0 +1,55 @@
+/*
+ * sys/mman.h
+ * mman-win32
+ */
+
+#ifndef _SYS_MMAN_H_
+#define _SYS_MMAN_H_
+
+#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later.
+#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows.
+#endif
+
+/* All the headers include this file. */
+#ifndef _MSC_VER
+#include <_mingw.h>
+#endif
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PROT_NONE 0
+#define PROT_READ 1
+#define PROT_WRITE 2
+#define PROT_EXEC 4
+
+#define MAP_FILE 0
+#define MAP_SHARED 1
+#define MAP_PRIVATE 2
+#define MAP_TYPE 0xf
+#define MAP_FIXED 0x10
+#define MAP_ANONYMOUS 0x20
+#define MAP_ANON MAP_ANONYMOUS
+
+#define MAP_FAILED ((void *)-1)
+
+/* Flags for msync. */
+#define MS_ASYNC 1
+#define MS_SYNC 2
+#define MS_INVALIDATE 4
+
+void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
+int munmap(void *addr, size_t len);
+int mprotect(void *addr, size_t len, int prot);
+int msync(void *addr, size_t len, int flags);
+int mlock(const void *addr, size_t len);
+int munlock(const void *addr, size_t len);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* _SYS_MMAN_H_ */
diff --git a/espfs/mkespfsimage/mman-win32/test.c b/espfs/mkespfsimage/mman-win32/test.c
new file mode 100644
index 0000000..9374b9f
--- /dev/null
+++ b/espfs/mkespfsimage/mman-win32/test.c
@@ -0,0 +1,235 @@
+
+#include "mman.h"
+
+#include
+#include
+#include
+
+#ifndef NULL
+#define NULL (void*)0
+#endif
+
+const char* map_file_name = "map_file.dat";
+
+int test_anon_map_readwrite()
+{
+ void* map = mmap(NULL, 1024, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (map == MAP_FAILED)
+ {
+ printf("mmap (MAP_ANONYMOUS, PROT_READ | PROT_WRITE) returned unexpected error: %d\n", errno);
+ return -1;
+ }
+
+ *((unsigned char*)map) = 1;
+
+ int result = munmap(map, 1024);
+
+ if (result != 0)
+ printf("munmap (MAP_ANONYMOUS, PROT_READ | PROT_WRITE) returned unexpected error: %d\n", errno);
+
+ return result;
+}
+
+int test_anon_map_readonly()
+{
+ void* map = mmap(NULL, 1024, PROT_READ,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (map == MAP_FAILED)
+ {
+ printf("mmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno);
+ return -1;
+ }
+
+ *((unsigned char*)map) = 1;
+
+ int result = munmap(map, 1024);
+
+ if (result != 0)
+ printf("munmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno);
+
+ return result;
+}
+
+int test_anon_map_writeonly()
+{
+ void* map = mmap(NULL, 1024, PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (map == MAP_FAILED)
+ {
+ printf("mmap (MAP_ANONYMOUS, PROT_WRITE) returned unexpected error: %d\n", errno);
+ return -1;
+ }
+
+ *((unsigned char*)map) = 1;
+
+ int result = munmap(map, 1024);
+
+ if (result != 0)
+ printf("munmap (MAP_ANONYMOUS, PROT_WRITE) returned unexpected error: %d\n", errno);
+
+ return result;
+}
+
+int test_anon_map_readonly_nowrite()
+{
+ void* map = mmap(NULL, 1024, PROT_READ,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (map == MAP_FAILED)
+ {
+ printf("mmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno);
+ return -1;
+ }
+
+ if (*((unsigned char*)map) != 0)
+ printf("test_anon_map_readonly_nowrite (MAP_ANONYMOUS, PROT_READ) returned unexpected value: %d\n",
+ (int)*((unsigned char*)map));
+
+ int result = munmap(map, 1024);
+
+ if (result != 0)
+ printf("munmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno);
+
+ return result;
+}
+
+int test_file_map_readwrite()
+{
+ mode_t mode = S_IRUSR | S_IWUSR;
+ int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode);
+
+ void* map = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0);
+ if (map == MAP_FAILED)
+ {
+ printf("mmap returned unexpected error: %d\n", errno);
+ return -1;
+ }
+
+ *((unsigned char*)map) = 1;
+
+ int result = munmap(map, 1024);
+
+ if (result != 0)
+ printf("munmap returned unexpected error: %d\n", errno);
+
+ close(o);
+
+ /*TODO: get file info and content and compare it with the sources conditions */
+ unlink(map_file_name);
+
+ return result;
+}
+
+int test_file_map_mlock_munlock()
+{
+ const size_t map_size = 1024;
+
+ int result = 0;
+ mode_t mode = S_IRUSR | S_IWUSR;
+ int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode);
+ if (o == -1)
+ {
+ printf("unable to create file %s: %d\n", map_file_name, errno);
+ return -1;
+ }
+
+ void* map = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0);
+ if (map == MAP_FAILED)
+ {
+ printf("mmap returned unexpected error: %d\n", errno);
+ result = -1;
+ goto done_close;
+ }
+
+ if (mlock(map, map_size) != 0)
+ {
+ printf("mlock returned unexpected error: %d\n", errno);
+ result = -1;
+ goto done_munmap;
+ }
+
+ *((unsigned char*)map) = 1;
+
+ if (munlock(map, map_size) != 0)
+ {
+ printf("munlock returned unexpected error: %d\n", errno);
+ result = -1;
+ }
+
+done_munmap:
+ result = munmap(map, map_size);
+
+ if (result != 0)
+ printf("munmap returned unexpected error: %d\n", errno);
+
+done_close:
+ close(o);
+
+ unlink(map_file_name);
+done:
+ return result;
+}
+
+int test_file_map_msync()
+{
+ const size_t map_size = 1024;
+
+ int result = 0;
+ mode_t mode = S_IRUSR | S_IWUSR;
+ int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode);
+ if (o == -1)
+ {
+ printf("unable to create file %s: %d\n", map_file_name, errno);
+ return -1;
+ }
+
+ void* map = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0);
+ if (map == MAP_FAILED)
+ {
+ printf("mmap returned unexpected error: %d\n", errno);
+ result = -1;
+ goto done_close;
+ }
+
+ *((unsigned char*)map) = 1;
+
+ if (msync(map, map_size, MS_SYNC) != 0)
+ {
+ printf("msync returned unexpected error: %d\n", errno);
+ result = -1;
+ }
+
+ result = munmap(map, map_size);
+
+ if (result != 0)
+ printf("munmap returned unexpected error: %d\n", errno);
+
+done_close:
+ close(o);
+
+ unlink(map_file_name);
+done:
+ return result;
+}
+
+#define EXEC_TEST(name) \
+ if (name() != 0) { result = -1; printf( #name ": fail\n"); } \
+ else { printf(#name ": pass\n"); }
+
+int main()
+{
+ int result = 0;
+
+ EXEC_TEST(test_anon_map_readwrite);
+ //NOTE: this test must cause an access violation exception
+ //EXEC_TEST(test_anon_map_readonly);
+ EXEC_TEST(test_anon_map_readonly_nowrite);
+ EXEC_TEST(test_anon_map_writeonly);
+
+ EXEC_TEST(test_file_map_readwrite);
+ EXEC_TEST(test_file_map_mlock_munlock);
+ EXEC_TEST(test_file_map_msync);
+ //TODO: EXEC_TEST(test_file_map_mprotect);
+
+ return result;
+}
diff --git a/espmake.cmd b/espmake.cmd
new file mode 100644
index 0000000..42e25a6
--- /dev/null
+++ b/espmake.cmd
@@ -0,0 +1,7 @@
+@echo off
+
+REM remove automatic created obj folder
+rd obj /S /Q >nul 2>&1
+
+PATH=%PATH%;C:\Espressif\xtensa-lx106-elf\bin;C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\espressif\git-bin;C:\espressif\java-bin;C:\Python27
+make -f Makefile %1 %2 %3 %4 %5
\ No newline at end of file