Created
January 15, 2024 19:17
-
-
Save Staars/6e5dfe69c1d38ba928692c7b658dd74b to your computer and use it in GitHub Desktop.
Some compression tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/lib/default/tamp/library.properties b/lib/default/tamp/library.properties | |
new file mode 100644 | |
index 000000000..c4bb469cd | |
--- /dev/null | |
+++ b/lib/default/tamp/library.properties | |
@@ -0,0 +1,7 @@ | |
+name=Tamp | |
+version=1.0 | |
+author= | |
+maintainer= | |
+sentence=Tamp compression for ESP32 | |
+paragraph= | |
+architectures=esp32 | |
diff --git a/lib/default/tamp/src/tamp_common.c b/lib/default/tamp/src/tamp_common.c | |
new file mode 100644 | |
index 000000000..36c83660a | |
--- /dev/null | |
+++ b/lib/default/tamp/src/tamp_common.c | |
@@ -0,0 +1,34 @@ | |
+#include <stdlib.h> | |
+#include "tamp_common.h" | |
+ | |
+static const unsigned char common_characters[] = { | |
+ 0x20, 0x00, 0x30, 0x65, 0x69, 0x3e, 0x74, 0x6f, | |
+ 0x3c, 0x61, 0x6e, 0x73, 0xa, 0x72, 0x2f, 0x2e | |
+}; | |
+ | |
+ | |
+static inline uint32_t xorshift32(uint32_t *state) { | |
+ uint32_t x = *state; | |
+ x ^= x << 13; | |
+ x ^= x >> 17; | |
+ x ^= x << 5; | |
+ *state = x; | |
+ return x; | |
+} | |
+ | |
+ | |
+void tamp_initialize_dictionary(unsigned char *buffer, size_t size){ | |
+ uint32_t seed = 3758097560; | |
+ uint32_t randbuf = 0; | |
+ for(size_t i=0; i < size; i++){ | |
+ if( TAMP_UNLIKELY((i & 0x7) == 0) ) | |
+ randbuf = xorshift32(&seed); | |
+ buffer[i] = common_characters[randbuf & 0x0F]; | |
+ randbuf >>= 4; | |
+ } | |
+} | |
+ | |
+ | |
+int8_t tamp_compute_min_pattern_size(uint8_t window, uint8_t literal) { | |
+ return 2 + (window > (10 + ((literal - 5) << 1))); | |
+} | |
diff --git a/lib/default/tamp/src/tamp_common.h b/lib/default/tamp/src/tamp_common.h | |
new file mode 100644 | |
index 000000000..69a2dfa92 | |
--- /dev/null | |
+++ b/lib/default/tamp/src/tamp_common.h | |
@@ -0,0 +1,62 @@ | |
+#ifndef TAMP_COMMON_H | |
+#define TAMP_COMMON_H | |
+ | |
+#ifdef __cplusplus | |
+extern "C" { | |
+#endif | |
+ | |
+#include <stdlib.h> | |
+#include <stdbool.h> | |
+#include <stdint.h> | |
+ | |
+/* Compiler branch optimizations */ | |
+#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 2)) | |
+#define TAMP_LIKELY(c) (__builtin_expect(!!(c), 1)) | |
+#define TAMP_UNLIKELY(c) (__builtin_expect(!!(c), 0)) | |
+#else | |
+#define TAMP_LIKELY(c) (c) | |
+#define TAMP_UNLIKELY(c) (c) | |
+#endif | |
+ | |
+enum { | |
+ /* Normal/Recoverable status >= 0 */ | |
+ TAMP_OK = 0, | |
+ TAMP_OUTPUT_FULL = 1, // Wasn't able to complete action due to full output buffer. | |
+ TAMP_INPUT_EXHAUSTED = 2, // Wasn't able to complete action due to exhausted input buffer. | |
+ | |
+ /* Error codes < 0 */ | |
+ TAMP_ERROR = -1, // Generic error | |
+ TAMP_EXCESS_BITS = -2, // Provided symbol has more bits than conf->literal | |
+ TAMP_INVALID_CONF = -3, // Invalid configuration parameters. | |
+}; | |
+typedef int8_t tamp_res; | |
+ | |
+typedef struct TampConf { | |
+ uint16_t window:4; // number of window bits | |
+ uint16_t literal:4; // number of literal bits | |
+ uint16_t use_custom_dictionary:1; // Use a custom initialized dictionary. | |
+} TampConf; | |
+ | |
+/** | |
+ * @brief Pre-populate a window buffer with common characters. | |
+ * | |
+ * @param[out] buffer Populated output buffer. | |
+ * @param[in] size Size of output buffer in bytes. | |
+ */ | |
+void tamp_initialize_dictionary(unsigned char *buffer, size_t size); | |
+ | |
+/** | |
+ * @brief Compute the minimum viable pattern size given window and literal config parameters. | |
+ * | |
+ * @param[in] window Number of window bits. Valid values are [8, 15]. | |
+ * @param[in] literal Number of literal bits. Valid values are [5, 8]. | |
+ * | |
+ * @return The minimum pattern size in bytes. Either 2 or 3. | |
+ */ | |
+int8_t tamp_compute_min_pattern_size(uint8_t window, uint8_t literal); | |
+ | |
+#ifdef __cplusplus | |
+} | |
+#endif | |
+ | |
+#endif | |
diff --git a/lib/default/tamp/src/tamp_compressor.c b/lib/default/tamp/src/tamp_compressor.c | |
new file mode 100644 | |
index 000000000..7c6daad61 | |
--- /dev/null | |
+++ b/lib/default/tamp/src/tamp_compressor.c | |
@@ -0,0 +1,359 @@ | |
+#include "tamp_compressor.h" | |
+#include "tamp_common.h" | |
+#include <stdlib.h> | |
+#include <stdbool.h> | |
+ | |
+#define MAX(x, y) (((x) > (y)) ? (x) : (y)) | |
+#define MIN(x, y) (((x) < (y)) ? (x) : (y)) | |
+#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) | |
+ | |
+#define MAX_PATTERN_SIZE (compressor->min_pattern_size + 13) | |
+#define WINDOW_SIZE (1 << compressor->conf_window) | |
+// 0xF because sizeof(TampCompressor.input) == 16; | |
+#define input_add(offset) (\ | |
+ (compressor->input_pos + offset) & 0xF \ | |
+ ) | |
+#define read_input(offset) ( \ | |
+ compressor->input[input_add(offset)] \ | |
+ ) | |
+#define IS_LITERAL_FLAG (1 << compressor->conf_literal) | |
+ | |
+#define FLUSH_CODE (0xAB) | |
+ | |
+// encodes [min_pattern_bytes, min_pattern_bytes + 13] pattern lengths | |
+static const unsigned char huffman_codes[] = { | |
+ 0x0, 0x3, 0x8, 0xb, 0x14, 0x24, 0x26, 0x2b, 0x4b, 0x54, 0x94, 0x95, 0xaa, 0x27 | |
+}; | |
+// These bit lengths pre-add the 1 bit for the 0-value is_literal flag. | |
+static const uint8_t huffman_bits[] = { | |
+ 0x2, 0x3, 0x5, 0x5, 0x6, 0x7, 0x7, 0x7, 0x8, 0x8, 0x9, 0x9, 0x9, 0x7 | |
+}; | |
+ | |
+static inline void write_to_bit_buffer(TampCompressor *compressor, uint32_t bits, uint8_t n_bits){ | |
+ compressor->bit_buffer_pos += n_bits; | |
+ compressor->bit_buffer |= bits << (32 - compressor->bit_buffer_pos); | |
+} | |
+ | |
+/** | |
+ * @brief Partially flush the internal bit buffer. | |
+ * | |
+ * Up to 7 bits may remain in the internal bit buffer. | |
+ */ | |
+static inline tamp_res partial_flush(TampCompressor *compressor, unsigned char *output, size_t output_size, size_t *output_written_size) { | |
+ for ( | |
+ *output_written_size = output_size; | |
+ compressor->bit_buffer_pos >= 8 && output_size; | |
+ output_size--, compressor->bit_buffer_pos -= 8, compressor->bit_buffer <<= 8 | |
+ ) | |
+ *output++ = compressor->bit_buffer >> 24; | |
+ *output_written_size -= output_size; | |
+ return (compressor->bit_buffer_pos >= 8) ? TAMP_OUTPUT_FULL : TAMP_OK; | |
+} | |
+ | |
+/** | |
+ * @brief Find the best match for the current input buffer. | |
+ * | |
+ * @param[in,out] compressor TampCompressor object to perform search on. | |
+ * @param[out] match_index If match_size is 0, this value is undefined. | |
+ * @param[out] match_size Size of best found match. | |
+ */ | |
+static inline void find_best_match( | |
+ TampCompressor *compressor, | |
+ uint16_t *match_index, | |
+ uint8_t *match_size | |
+ ){ | |
+ *match_size = 0; | |
+ | |
+ if(TAMP_UNLIKELY(compressor->input_size < compressor->min_pattern_size)) | |
+ return; | |
+ | |
+ const uint16_t first_second = (read_input(0) << 8) | read_input(1); | |
+ const uint16_t window_size_minus_1 = WINDOW_SIZE - 1; | |
+ const uint8_t max_pattern_size = MIN(compressor->input_size, MAX_PATTERN_SIZE); | |
+ uint16_t window_rolling_2_byte = compressor->window[0]; | |
+ unsigned char c; | |
+ | |
+ for(uint16_t window_index=0; window_index < window_size_minus_1; window_index++){ | |
+ window_rolling_2_byte <<= 8; | |
+ window_rolling_2_byte |= compressor->window[window_index + 1]; | |
+ if(TAMP_LIKELY(window_rolling_2_byte != first_second)){ | |
+ continue; | |
+ } | |
+ | |
+ for(uint8_t input_offset=2; ; input_offset++){ | |
+ if(TAMP_UNLIKELY(input_offset > *match_size)){ | |
+ *match_size = input_offset; | |
+ *match_index = window_index; | |
+ if(TAMP_UNLIKELY(*match_size == max_pattern_size)) | |
+ return; | |
+ } | |
+ | |
+ if(TAMP_UNLIKELY(window_index + input_offset > window_size_minus_1)) | |
+ return; | |
+ | |
+ c = read_input(input_offset); | |
+ if(TAMP_LIKELY(compressor->window[window_index + input_offset] != c)) | |
+ break; | |
+ } | |
+ } | |
+} | |
+ | |
+tamp_res tamp_compressor_init(TampCompressor *compressor, const TampConf *conf, unsigned char *window){ | |
+ const TampConf conf_default = {.window=10, .literal=8, .use_custom_dictionary=false}; | |
+ if(!conf) | |
+ conf = &conf_default; | |
+ if( conf->window < 8 || conf->window > 15) | |
+ return TAMP_INVALID_CONF; | |
+ if( conf->literal < 5 || conf->literal > 8) | |
+ return TAMP_INVALID_CONF; | |
+ | |
+ for(uint8_t i=0; i < sizeof(TampCompressor); i++) // Zero-out the struct | |
+ ((unsigned char *)compressor)[i] = 0; | |
+ | |
+ compressor->conf_literal = conf->literal; | |
+ compressor->conf_window = conf->window; | |
+ compressor->conf_use_custom_dictionary = conf->use_custom_dictionary; | |
+ | |
+ compressor->window = window; | |
+ compressor->min_pattern_size = tamp_compute_min_pattern_size(conf->window, conf->literal); | |
+ | |
+ if(!compressor->conf_use_custom_dictionary) | |
+ tamp_initialize_dictionary(window, (1 << conf->window)); | |
+ | |
+ // Write header to bit buffer | |
+ write_to_bit_buffer(compressor, conf->window - 8, 3); | |
+ write_to_bit_buffer(compressor, conf->literal - 5, 2); | |
+ write_to_bit_buffer(compressor, conf->use_custom_dictionary, 1); | |
+ write_to_bit_buffer(compressor, 0, 1); // Reserved | |
+ write_to_bit_buffer(compressor, 0, 1); // No more header bytes | |
+ | |
+ return TAMP_OK; | |
+} | |
+ | |
+ | |
+tamp_res tamp_compressor_compress_poll(TampCompressor *compressor, unsigned char *output, size_t output_size, size_t *output_written_size){ | |
+ tamp_res res; | |
+ const uint16_t window_mask = (1 << compressor->conf_window) - 1; | |
+ size_t output_written_size_proxy; | |
+ | |
+ if(!output_written_size) | |
+ output_written_size = &output_written_size_proxy; | |
+ *output_written_size = 0; | |
+ | |
+ if(TAMP_UNLIKELY(compressor->input_size == 0)) | |
+ return TAMP_OK; | |
+ | |
+ { | |
+ // Make sure there's enough room in the bit buffer. | |
+ size_t flush_bytes_written; | |
+ res = partial_flush(compressor, output, output_size, &flush_bytes_written); | |
+ (*output_written_size) += flush_bytes_written; | |
+ if(TAMP_UNLIKELY(res != TAMP_OK)) | |
+ return res; | |
+ output_size -= flush_bytes_written; | |
+ output += flush_bytes_written; | |
+ } | |
+ | |
+ if(TAMP_UNLIKELY(output_size == 0)) | |
+ return TAMP_OUTPUT_FULL; | |
+ | |
+ uint8_t match_size = 0; | |
+ uint16_t match_index = 0; | |
+ find_best_match(compressor, &match_index, &match_size); | |
+ | |
+ if(TAMP_UNLIKELY(match_size < compressor->min_pattern_size)){ | |
+ // Write LITERAL | |
+ match_size = 1; | |
+ unsigned char c = read_input(0); | |
+ if(TAMP_UNLIKELY(c >> compressor->conf_literal)){ | |
+ return TAMP_EXCESS_BITS; | |
+ } | |
+ write_to_bit_buffer(compressor, IS_LITERAL_FLAG | c, compressor->conf_literal + 1); | |
+ } | |
+ else{ | |
+ // Write TOKEN | |
+ uint8_t huffman_index = match_size - compressor->min_pattern_size; | |
+ write_to_bit_buffer(compressor, huffman_codes[huffman_index], huffman_bits[huffman_index]); | |
+ write_to_bit_buffer(compressor, match_index, compressor->conf_window); | |
+ } | |
+ // Populate Window | |
+ for(uint8_t i=0; i < match_size; i++){ | |
+ compressor->window[compressor->window_pos] = read_input(0); | |
+ compressor->window_pos = (compressor->window_pos + 1) & window_mask; | |
+ compressor->input_pos = input_add(1); | |
+ } | |
+ compressor->input_size -= match_size; | |
+ | |
+ return TAMP_OK; | |
+} | |
+ | |
+ | |
+void tamp_compressor_sink( | |
+ TampCompressor *compressor, | |
+ const unsigned char *input, | |
+ size_t input_size, | |
+ size_t *consumed_size | |
+ ){ | |
+ size_t consumed_size_proxy; | |
+ if(TAMP_LIKELY(consumed_size)) | |
+ *consumed_size = 0; | |
+ else | |
+ consumed_size = &consumed_size_proxy; | |
+ | |
+ for(size_t i=0; i < input_size; i++){ | |
+ if(TAMP_UNLIKELY(compressor->input_size == sizeof(compressor->input))) | |
+ break; | |
+ compressor->input[input_add(compressor->input_size)] = input[i]; | |
+ compressor->input_size += 1; | |
+ (*consumed_size)++; | |
+ } | |
+} | |
+ | |
+tamp_res tamp_compressor_compress( | |
+ TampCompressor *compressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size, | |
+ const unsigned char *input, | |
+ size_t input_size, | |
+ size_t *input_consumed_size | |
+ ){ | |
+ tamp_res res; | |
+ size_t input_consumed_size_proxy, output_written_size_proxy; | |
+ | |
+ if(TAMP_LIKELY(output_written_size)) | |
+ *output_written_size = 0; | |
+ else | |
+ output_written_size = &output_written_size_proxy; | |
+ | |
+ if(TAMP_LIKELY(input_consumed_size)) | |
+ *input_consumed_size = 0; | |
+ else | |
+ input_consumed_size = &input_consumed_size_proxy; | |
+ | |
+ while(input_size > 0 && output_size > 0){ | |
+ { | |
+ // Sink Data into input buffer. | |
+ size_t consumed; | |
+ tamp_compressor_sink(compressor, input, input_size, &consumed); | |
+ input += consumed; | |
+ input_size -= consumed; | |
+ (*input_consumed_size) += consumed; | |
+ } | |
+ if(TAMP_LIKELY(compressor->input_size == sizeof(compressor->input))){ | |
+ // Input buffer is full and ready to start compressing. | |
+ size_t chunk_output_written_size; | |
+ res = tamp_compressor_compress_poll(compressor, output, output_size, &chunk_output_written_size); | |
+ output += chunk_output_written_size; | |
+ output_size -= chunk_output_written_size; | |
+ (*output_written_size) += chunk_output_written_size; | |
+ if(TAMP_UNLIKELY(res != TAMP_OK)) | |
+ return res; | |
+ } | |
+ } | |
+ return TAMP_OK; | |
+} | |
+ | |
+tamp_res tamp_compressor_flush( | |
+ TampCompressor *compressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size, | |
+ bool write_token | |
+ ){ | |
+ tamp_res res; | |
+ size_t chunk_output_written_size; | |
+ size_t output_written_size_proxy; | |
+ | |
+ if(!output_written_size) | |
+ output_written_size = &output_written_size_proxy; | |
+ *output_written_size = 0; | |
+ | |
+ while(compressor->input_size){ | |
+ // Compress the remainder of the input buffer. | |
+ res = tamp_compressor_compress_poll(compressor, output, output_size, &chunk_output_written_size); | |
+ (*output_written_size) += chunk_output_written_size; | |
+ if(TAMP_UNLIKELY(res != TAMP_OK)) | |
+ return res; | |
+ output_size -= chunk_output_written_size; | |
+ output += chunk_output_written_size; | |
+ } | |
+ | |
+ // Perform partial flush to see if we need a FLUSH token, and to subsequently | |
+ // make room for the FLUSH token. | |
+ res = partial_flush(compressor, output, output_size, &chunk_output_written_size); | |
+ output_size -= chunk_output_written_size; | |
+ (*output_written_size) += chunk_output_written_size; | |
+ output += chunk_output_written_size; | |
+ if(TAMP_UNLIKELY(res != TAMP_OK)) | |
+ return res; | |
+ | |
+ // Check if there's enough output buffer space | |
+ if (compressor->bit_buffer_pos){ | |
+ if (output_size == 0){ | |
+ return TAMP_OUTPUT_FULL; | |
+ } | |
+ if(write_token){ | |
+ if(output_size < 2) | |
+ return TAMP_OUTPUT_FULL; | |
+ write_to_bit_buffer(compressor, FLUSH_CODE, 9); | |
+ } | |
+ } | |
+ | |
+ // Flush the remainder of the output bit-buffer | |
+ while(compressor->bit_buffer_pos){ | |
+ *output = compressor->bit_buffer >> 24; | |
+ output++; | |
+ compressor->bit_buffer <<= 8; | |
+ compressor->bit_buffer_pos -= MIN(compressor->bit_buffer_pos, 8); | |
+ output_size--; | |
+ (*output_written_size)++; | |
+ } | |
+ | |
+ return TAMP_OK; | |
+} | |
+ | |
+tamp_res tamp_compressor_compress_and_flush( | |
+ TampCompressor *compressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size, | |
+ const unsigned char *input, | |
+ size_t input_size, | |
+ size_t *input_consumed_size, | |
+ bool write_token | |
+ ){ | |
+ tamp_res res; | |
+ size_t flush_size; | |
+ size_t output_written_size_proxy; | |
+ | |
+ if(!output_written_size) | |
+ output_written_size = &output_written_size_proxy; | |
+ | |
+ res = tamp_compressor_compress( | |
+ compressor, | |
+ output, | |
+ output_size, | |
+ output_written_size, | |
+ input, | |
+ input_size, | |
+ input_consumed_size | |
+ ); | |
+ if(TAMP_UNLIKELY(res != TAMP_OK)) | |
+ return res; | |
+ | |
+ res = tamp_compressor_flush( | |
+ compressor, | |
+ output + *output_written_size, | |
+ output_size - *output_written_size, | |
+ &flush_size, | |
+ write_token | |
+ ); | |
+ | |
+ (*output_written_size) += flush_size; | |
+ | |
+ if(TAMP_UNLIKELY(res != TAMP_OK)) | |
+ return res; | |
+ | |
+ return TAMP_OK; | |
+} | |
diff --git a/lib/default/tamp/src/tamp_compressor.h b/lib/default/tamp/src/tamp_compressor.h | |
new file mode 100644 | |
index 000000000..e7891b0ab | |
--- /dev/null | |
+++ b/lib/default/tamp/src/tamp_compressor.h | |
@@ -0,0 +1,174 @@ | |
+#ifndef TAMP_COMPRESSOR_H | |
+#define TAMP_COMPRESSOR_H | |
+ | |
+#ifdef __cplusplus | |
+extern "C" { | |
+#endif | |
+ | |
+#include "tamp_common.h" | |
+ | |
+/* Externally, do not directly edit ANY of these attributes */ | |
+typedef struct TampCompressor{ | |
+ /* nicely aligned attributes */ | |
+ unsigned char *window; | |
+ unsigned char input[16]; | |
+ uint32_t bit_buffer; | |
+ | |
+ /* Conf attributes */ | |
+ uint32_t conf_window:4; // number of window bits | |
+ uint32_t conf_literal:4; // number of literal bits | |
+ uint32_t conf_use_custom_dictionary:1; // Use a custom initialized dictionary. | |
+ | |
+ /* Other small attributes */ | |
+ uint32_t window_pos:15; | |
+ uint32_t bit_buffer_pos:6; | |
+ uint32_t min_pattern_size:2; | |
+ | |
+ uint32_t input_size:5; | |
+ uint32_t input_pos:4; | |
+} TampCompressor; | |
+ | |
+ | |
+/** | |
+ * @brief Initialize Tamp Compressor object. | |
+ * | |
+ * @param[out] compressor Object to initialize. | |
+ * @param[in] conf Compressor configuration. Set to NULL for default (window=10, literal=8). | |
+ * @param[in] window Pre-allocated window buffer. Size must agree with conf->window. | |
+ * If conf.use_custom_dictionary is true, then the window must be | |
+ * externally initialized. | |
+ * | |
+ * @return Tamp Status Code. Returns TAMP_INVALID_CONF if an invalid conf state is provided. | |
+ */ | |
+tamp_res tamp_compressor_init(TampCompressor *compressor, const TampConf *conf, unsigned char *window); | |
+ | |
+/** | |
+ * @brief Sink data into input buffer. | |
+ * | |
+ * @param[in,out] compressor TampCompressor object to perform compression with. | |
+ * @param[in] input Pointer to the input data to be sinked into compressor. | |
+ * @param[in] input_size Size of input. | |
+ * @param[out] consumed_size Number of bytes of input consumed. May be NULL. | |
+ */ | |
+void tamp_compressor_sink( | |
+ TampCompressor *compressor, | |
+ const unsigned char *input, | |
+ size_t input_size, | |
+ size_t *consumed_size | |
+ ); | |
+ | |
+/** | |
+ * @brief Run a single compression iteration on the internal input buffer. | |
+ * | |
+ * The most that will ever be written to output in a single invocation is: | |
+ * | |
+ * (1 + 8 + WINDOW_BITS + 7) // 8 | |
+ * | |
+ * or more simply: | |
+ * | |
+ * (16 + WINDOW_BITS) // 8 | |
+ * | |
+ * where // represents floor-division. Explanation: | |
+ * * 1 - is_literal bit | |
+ * * 8 - maximum huffman code length | |
+ * * WINDOW_BITS - The number of bits to represent the match index. By default, 10. | |
+ * * 7 - The internal bit buffer may have up to 7 bits from a previous invocation. | |
+ * * // 8 - Floor divide by 8 to get bytes; the upto remaining 7 bits remain in the internal output bit buffer. | |
+ * | |
+ * A reasonable 4-byte output buffer should be able to handle any compressor configuration. | |
+ * | |
+ * @param[in,out] compressor TampCompressor object to perform compression with. | |
+ * @param[out] output Pointer to a pre-allocated buffer to hold the output compressed data. | |
+ * @param[in] output_size Size of the pre-allocated buffer. Will compress up-to this many bytes. | |
+ * @param[out] output_written_size Number of bytes written to output. May be NULL. | |
+ * | |
+ * @return Tamp Status Code. Can return TAMP_OK, TAMP_OUTPUT_FULL, or TAMP_EXCESS_BITS. | |
+ */ | |
+tamp_res tamp_compressor_compress_poll( | |
+ TampCompressor *compressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size | |
+ ); | |
+ | |
+/** | |
+ * @brief Completely flush the internal bit buffer. Makes output "complete". | |
+ * | |
+ * At a maximum, the compressor will have 16 bytes in it's input buffer. | |
+ * The worst-case compression scenario would use `literal + 1` bits per input byte. | |
+ * This means that for the typical `literal=8` scenario, the output buffer size | |
+ * should be 18 bytes long. If `write_token=true`, then the output buffer size should | |
+ * be 20 bytes long to absolutely guarantee a complete flush. | |
+ * | |
+ * @param[in,out] compressor TampCompressor object to flush. | |
+ * @param[out] output Pointer to a pre-allocated buffer to hold the output compressed data. | |
+ * @param[in] output_size Size of the pre-allocated buffer. Will compress up-to this many bytes. | |
+ * @param[out] output_written_size Number of bytes written to output. May be NULL. | |
+ * @param[in] write_token Write the FLUSH token, if appropriate. Set to true if you want to continue using the compressor. Set to false if you are done with the compressor, usually at the end of a stream. | |
+ * | |
+ * @return Tamp Status Code. Can return TAMP_OK, or TAMP_OUTPUT_FULL. | |
+ */ | |
+tamp_res tamp_compressor_flush( | |
+ TampCompressor *compressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size, | |
+ bool write_token | |
+ ); | |
+ | |
+/** | |
+ * @brief Compress a chunk of data until input or output buffer is exhausted. | |
+ * | |
+ * @param[in,out] compressor TampCompressor object to perform compression with. | |
+ * @param[out] output Pointer to a pre-allocated buffer to hold the output compressed data. | |
+ * @param[in] output_size Size of the pre-allocated buffer. Will compress up-to this many bytes. | |
+ * @param[out] output_written_size Number of bytes written to output. May be NULL. | |
+ * @param[in] input Pointer to the input data to be compressed. | |
+ * @param[in] input_size Number of bytes in input data. | |
+ * @param[out] input_consumed_size Number of bytes of input data consumed. May be NULL. | |
+ * | |
+ * @return Tamp Status Code. Can return TAMP_OK, TAMP_OUTPUT_FULL, or TAMP_EXCESS_BITS. | |
+ */ | |
+tamp_res tamp_compressor_compress( | |
+ TampCompressor *compressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size, | |
+ const unsigned char *input, | |
+ size_t input_size, | |
+ size_t *input_consumed_size | |
+ ); | |
+ | |
+/** | |
+ * @brief Compress a chunk of data until input or output buffer is exhausted. | |
+ * | |
+ * If the output buffer is full, buffer flushing will not be performed and TAMP_OUTPUT_FULL will be returned. | |
+ * May be called again with an appropriately updated pointers and sizes. | |
+ * | |
+ * @param[in,out] compressor TampCompressor object to perform compression with. | |
+ * @param[out] output Pointer to a pre-allocated buffer to hold the output compressed data. | |
+ * @param[in] output_size Size of the pre-allocated buffer. Will compress up-to this many bytes. | |
+ * @param[out] output_written_size Number of bytes written to output. May be NULL. | |
+ * @param[in] input Pointer to the input data to be compressed. | |
+ * @param[in] input_size Number of bytes in input data. | |
+ * @param[out] input_consumed_size Number of bytes of input data consumed. May be NULL. | |
+ * | |
+ * @return Tamp Status Code. Can return TAMP_OK, TAMP_OUTPUT_FULL, or TAMP_EXCESS_BITS. | |
+ */ | |
+tamp_res tamp_compressor_compress_and_flush( | |
+ TampCompressor *compressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size, | |
+ const unsigned char *input, | |
+ size_t input_size, | |
+ size_t *input_consumed_size, | |
+ bool write_token | |
+ ); | |
+ | |
+ | |
+#ifdef __cplusplus | |
+} | |
+#endif | |
+ | |
+#endif | |
diff --git a/lib/default/tamp/src/tamp_decompressor.c b/lib/default/tamp/src/tamp_decompressor.c | |
new file mode 100644 | |
index 000000000..42abf5ccc | |
--- /dev/null | |
+++ b/lib/default/tamp/src/tamp_decompressor.c | |
@@ -0,0 +1,237 @@ | |
+#include "tamp_decompressor.h" | |
+#include "tamp_common.h" | |
+ | |
+#define MAX(x, y) (((x) > (y)) ? (x) : (y)) | |
+#define MIN(x, y) (((x) < (y)) ? (x) : (y)) | |
+ | |
+#define FLUSH 15 | |
+ | |
+static const uint8_t HUFFMAN_TABLE[128] = {50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 85, 85, 85, 85, 122, 123, 104, 104, 86, 86, 86, 86, 93, 93, 93, 93, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 124, 127, 87, 87, 87, 87, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17}; | |
+ | |
+ | |
+/** | |
+ * @brief Decode a huffman match-size symbol from the decompressor's bit_buffer. | |
+ * | |
+ * Internally updates bit_buffer and bit_buffer_pos. | |
+ * | |
+ * bit_buffer MUST have at least 8 bits prior to calling. | |
+ * | |
+ * @returns Decoded match_size | |
+ */ | |
+static inline int8_t huffman_decode(uint32_t *bit_buffer, uint8_t *bit_buffer_pos){ | |
+ uint8_t code; | |
+ uint8_t bit_len; | |
+ | |
+ (*bit_buffer_pos)--; | |
+ code = *bit_buffer >> 31; | |
+ *bit_buffer <<= 1; | |
+ if(TAMP_LIKELY(code == 0)) | |
+ return 0; | |
+ | |
+ code = *bit_buffer >> (32 - 7); | |
+ code = HUFFMAN_TABLE[code]; | |
+ bit_len = code >> 4; | |
+ *bit_buffer <<= bit_len; | |
+ (*bit_buffer_pos) -= bit_len; | |
+ | |
+ return code & 0xF; | |
+} | |
+ | |
+tamp_res tamp_decompressor_read_header(TampConf *conf, const unsigned char *input, size_t input_size, size_t *input_consumed_size) { | |
+ if(input_consumed_size) | |
+ (*input_consumed_size) = 0; | |
+ if(input_size == 0) | |
+ return TAMP_INPUT_EXHAUSTED; | |
+ if(input[0] & 0x2) | |
+ return TAMP_INVALID_CONF; // Reserved | |
+ if(input[0] & 0x1) | |
+ return TAMP_INVALID_CONF; // Currently only a single header byte is supported. | |
+ if(input_consumed_size) | |
+ (*input_consumed_size)++; | |
+ | |
+ conf->window = ((input[0] >> 5) & 0x7) + 8; | |
+ conf->literal = ((input[0] >> 3) & 0x3) + 5; | |
+ conf->use_custom_dictionary = ((input[0] >> 2) & 0x1); | |
+ | |
+ return TAMP_OK; | |
+} | |
+ | |
+/** | |
+ * Populate the rest of the decompressor structure after the following fields have been populated: | |
+ * * conf | |
+ * * window | |
+ */ | |
+static tamp_res tamp_decompressor_populate_from_conf(TampDecompressor *decompressor, uint8_t conf_window, uint8_t conf_literal, uint8_t conf_use_custom_dictionary){ | |
+ if(conf_window < 8 || conf_window > 15) | |
+ return TAMP_INVALID_CONF; | |
+ if(conf_literal < 5 || conf_literal > 8) | |
+ return TAMP_INVALID_CONF; | |
+ if(!conf_use_custom_dictionary) | |
+ tamp_initialize_dictionary(decompressor->window, (1 << conf_window)); | |
+ | |
+ decompressor->conf_window = conf_window; | |
+ decompressor->conf_literal = conf_literal; | |
+ | |
+ decompressor->min_pattern_size = tamp_compute_min_pattern_size(conf_window, conf_literal); | |
+ decompressor->configured = true; | |
+ | |
+ return TAMP_OK; | |
+} | |
+ | |
+tamp_res tamp_decompressor_init(TampDecompressor *decompressor, const TampConf *conf, unsigned char *window){ | |
+ tamp_res res = TAMP_OK; | |
+ for(uint8_t i=0; i < sizeof(TampDecompressor); i++) // Zero-out the struct | |
+ ((unsigned char *)decompressor)[i] = 0; | |
+ decompressor->window = window; | |
+ if(conf){ | |
+ res = tamp_decompressor_populate_from_conf(decompressor, conf->window, conf->literal, conf->use_custom_dictionary); | |
+ } | |
+ | |
+ return res; | |
+} | |
+ | |
+tamp_res tamp_decompressor_decompress( | |
+ TampDecompressor *decompressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size, | |
+ const unsigned char *input, | |
+ size_t input_size, | |
+ size_t *input_consumed_size | |
+ ){ | |
+ size_t input_consumed_size_proxy; | |
+ size_t output_written_size_proxy; | |
+ tamp_res res; | |
+ const unsigned char *input_end = input + input_size; | |
+ const unsigned char *output_end = output + output_size; | |
+ | |
+ if(!output_written_size) | |
+ output_written_size = &output_written_size_proxy; | |
+ if(!input_consumed_size) | |
+ input_consumed_size = &input_consumed_size_proxy; | |
+ | |
+ *input_consumed_size = 0; | |
+ *output_written_size = 0; | |
+ | |
+ if(!decompressor->configured){ | |
+ //Read in header | |
+ size_t header_consumed_size; | |
+ TampConf conf; | |
+ res = tamp_decompressor_read_header(&conf, input, input_end - input, &header_consumed_size); | |
+ if(res != TAMP_OK) | |
+ return res; | |
+ | |
+ res = tamp_decompressor_populate_from_conf(decompressor, conf.window, conf.literal, conf.use_custom_dictionary); | |
+ if(res != TAMP_OK) | |
+ return res; | |
+ | |
+ input += header_consumed_size; | |
+ (*input_consumed_size) += header_consumed_size; | |
+ } | |
+ const uint16_t window_mask = (1 << decompressor->conf_window) - 1; | |
+ while(input != input_end || decompressor->bit_buffer_pos){ | |
+ // Populate the bit buffer | |
+ while(input != input_end && decompressor->bit_buffer_pos <= 24){ | |
+ decompressor->bit_buffer_pos += 8; | |
+ decompressor->bit_buffer |= *input << (32 - decompressor->bit_buffer_pos); | |
+ input++; | |
+ (*input_consumed_size)++; | |
+ } | |
+ | |
+ if(TAMP_UNLIKELY(decompressor->bit_buffer_pos == 0)) | |
+ return TAMP_INPUT_EXHAUSTED; | |
+ | |
+ if(TAMP_UNLIKELY(output == output_end)) | |
+ return TAMP_OUTPUT_FULL; | |
+ | |
+ // Hint that patterns are more likely than literals | |
+ if(TAMP_UNLIKELY(decompressor->bit_buffer >> 31)){ | |
+ // is literal | |
+ if(TAMP_UNLIKELY(decompressor->bit_buffer_pos < (1 + decompressor->conf_literal))) | |
+ return TAMP_INPUT_EXHAUSTED; | |
+ decompressor->bit_buffer <<= 1; // shift out the is_literal flag | |
+ | |
+ // Copy literal to output | |
+ *output = decompressor->bit_buffer >> (32 - decompressor->conf_literal); | |
+ decompressor->bit_buffer <<= decompressor->conf_literal; | |
+ decompressor->bit_buffer_pos -= (1 + decompressor->conf_literal); | |
+ | |
+ // Update window | |
+ decompressor->window[decompressor->window_pos] = *output; | |
+ decompressor->window_pos = (decompressor->window_pos + 1) & window_mask; | |
+ | |
+ output++; | |
+ (*output_written_size)++; | |
+ } | |
+ else{ | |
+ // is token; attempt a decode | |
+ /* copy the bit buffers so that we can abort at any time */ | |
+ uint32_t bit_buffer = decompressor->bit_buffer; | |
+ uint16_t window_offset; | |
+ uint16_t window_offset_skip; | |
+ uint8_t bit_buffer_pos = decompressor->bit_buffer_pos; | |
+ int8_t match_size; | |
+ int8_t match_size_skip; | |
+ | |
+ // shift out the is_literal flag | |
+ bit_buffer <<= 1; | |
+ bit_buffer_pos--; | |
+ | |
+ // There must be at least 8 bits, otherwise no possible decoding. | |
+ if(TAMP_UNLIKELY(bit_buffer_pos < 8)) | |
+ return TAMP_INPUT_EXHAUSTED; | |
+ | |
+ match_size = huffman_decode(&bit_buffer, &bit_buffer_pos); | |
+ if(TAMP_UNLIKELY(match_size == FLUSH)){ | |
+ // flush bit_buffer to the nearest byte and skip the remainder of decoding | |
+ decompressor->bit_buffer = bit_buffer << (bit_buffer_pos & 7); | |
+ decompressor->bit_buffer_pos = bit_buffer_pos & ~7; // Round bit_buffer_pos down to nearest multiple of 8. | |
+ continue; | |
+ } | |
+ if(TAMP_UNLIKELY(bit_buffer_pos < decompressor->conf_window)){ | |
+ // There are not enough bits to decode window offset | |
+ return TAMP_INPUT_EXHAUSTED; | |
+ } | |
+ match_size += decompressor->min_pattern_size; | |
+ window_offset = bit_buffer >> (32 - decompressor->conf_window); | |
+ | |
+ // Apply skip_bytes | |
+ match_size_skip = match_size - decompressor->skip_bytes; | |
+ window_offset_skip = window_offset + decompressor->skip_bytes; | |
+ | |
+ // Check if we are output-buffer-limited, and if so to set skip_bytes | |
+ // Otherwise, update the decompressor buffers | |
+ size_t remaining = output_end - output; | |
+ if(TAMP_UNLIKELY((uint8_t)match_size_skip > remaining)){ | |
+ decompressor->skip_bytes += remaining; | |
+ match_size_skip = remaining; | |
+ } | |
+ else { | |
+ decompressor->skip_bytes = 0; | |
+ decompressor->bit_buffer = bit_buffer << decompressor->conf_window; | |
+ decompressor->bit_buffer_pos = bit_buffer_pos - decompressor->conf_window; | |
+ } | |
+ | |
+ // Copy pattern to output | |
+ for(uint8_t i=0; i < match_size_skip; i++){ | |
+ *output++ = decompressor->window[window_offset_skip + i]; | |
+ } | |
+ (*output_written_size) += match_size_skip; | |
+ | |
+ if(TAMP_LIKELY(decompressor->skip_bytes == 0)){ | |
+ // Perform window update; | |
+ // copy to a temporary buffer in case src precedes dst, and is overlapping. | |
+ uint8_t tmp_buf[16]; | |
+ for(uint8_t i=0; i < match_size; i++){ | |
+ tmp_buf[i] = decompressor->window[window_offset + i]; | |
+ } | |
+ for(uint8_t i=0; i < match_size; i++){ | |
+ decompressor->window[decompressor->window_pos] = tmp_buf[i]; | |
+ decompressor->window_pos = (decompressor->window_pos + 1) & window_mask; | |
+ } | |
+ } | |
+ } | |
+ | |
+ } | |
+ return TAMP_INPUT_EXHAUSTED; | |
+} | |
diff --git a/lib/default/tamp/src/tamp_decompressor.h b/lib/default/tamp/src/tamp_decompressor.h | |
new file mode 100644 | |
index 000000000..e74c95db8 | |
--- /dev/null | |
+++ b/lib/default/tamp/src/tamp_decompressor.h | |
@@ -0,0 +1,95 @@ | |
+#ifndef TAMP_DECOMPRESSOR_H | |
+#define TAMP_DECOMPRESSOR_H | |
+ | |
+#ifdef __cplusplus | |
+extern "C" { | |
+#endif | |
+ | |
+#include "tamp_common.h" | |
+ | |
+/* Externally, do not directly edit ANY of these attributes */ | |
+typedef struct { | |
+ unsigned char *window; | |
+ uint32_t bit_buffer; | |
+ | |
+ /* Conf attributes */ | |
+ uint32_t conf_window:4; // number of window bits | |
+ uint32_t conf_literal:4; // number of literal bits | |
+ //uint32_t conf_use_custom_dictionary:1; // Not used past initialization. | |
+ | |
+ uint32_t bit_buffer_pos:6; | |
+ uint32_t min_pattern_size:2; | |
+ uint32_t window_pos:15; | |
+ uint32_t configured:1; // Whether or not conf has been properly set | |
+ | |
+ uint32_t skip_bytes:4; // Skip this many decompressed bytes (from previous output-buffer-limited decompression). | |
+} TampDecompressor; | |
+ | |
+/** | |
+ * @brief Read tamp header and populate configuration. | |
+ * | |
+ * Don't invoke if setting conf to NULL in tamp_decompressor_init. | |
+ * | |
+ * @param[out] conf Configuration read from header | |
+ * @param[in] data Tamp compressed data stream. | |
+ */ | |
+tamp_res tamp_decompressor_read_header(TampConf *conf, const unsigned char *input, size_t input_size, size_t *input_consumed_size); | |
+ | |
+/** | |
+ * @brief Initialize decompressor object. | |
+ * | |
+ * | |
+ * | |
+ * @param[in,out] TampDecompressor object to perform decompression with. | |
+ * @param[in] conf Compressor configuration. Set to NULL to perform an implicit header read. | |
+ * @param[in] window Pre-allocated window buffer. Size must agree with conf->window. | |
+ * If conf.use_custom_dictionary is true, then the window must be | |
+ * externally initialized and be at least as big as conf->window. | |
+ */ | |
+tamp_res tamp_decompressor_init(TampDecompressor *decompressor, const TampConf *conf, unsigned char *window); | |
+ | |
+/** | |
+ * @brief Decompress an input stream of data. | |
+ * | |
+ * Input data is **not** guaranteed to be consumed. Imagine if a 6-byte sequence has been encoded, and | |
+ * tamp_decompressor_decompress is called multiple times with a 2-byte output buffer: | |
+ * | |
+ * 1. On the 1st call, a few input bytes may be consumed, filling the internal input buffer. | |
+ * The first 2 bytes of the 6-byte output sequence are returned. | |
+ * The internal input buffer remains full. | |
+ * 2. On the 2nd call, no input bytes are consumed since the internal input buffer is still full. | |
+ * The {3, 4} bytes of the 6-byte output sequence are returned. | |
+ * The internal input buffer remains full. | |
+ * 3. On the 3rd call, no input bytes are consumed since the internal input buffer is still full. | |
+ * The {5, 6} bytes of the 6-byte output sequence are returned. | |
+ * The input buffer is no longer full since this sequence has now been fully decoded. | |
+ * 4. On the 4th call, more input bytes are consumed, potentially filling the internal input buffer. | |
+ * It is not strictly necessary for the internal input buffer to be full to further decode the output. | |
+ * There simply has to be enough to decode a token/literal. | |
+ * If there is not enough bits in the internal input buffer, then TAMP_INPUT_EXHAUSTED will be returned. | |
+ * | |
+ * @param[in,out] TampDecompressor object to perform decompression with. | |
+ * @param[out] output Pointer to a pre-allocated buffer to hold the output decompressed data. | |
+ * @param[in] output_size Size of the pre-allocated buffer. Will decompress up-to this many bytes. | |
+ * @param[out] output_written_size Number of bytes written to output. May be NULL. | |
+ * @param[in] input Pointer to the compressed input data. | |
+ * @param[in] input_size Number of bytes in input data. | |
+ * @param[out] input_consumed_size Number of bytes of input data consumed. May be NULL. | |
+ * | |
+ * @return Tamp Status Code. In cases of success, will return TAMP_INPUT_EXHAUSTED or TAMP_OUTPUT_FULL, in lieu of TAMP_OK. | |
+ */ | |
+tamp_res tamp_decompressor_decompress( | |
+ TampDecompressor *decompressor, | |
+ unsigned char *output, | |
+ size_t output_size, | |
+ size_t *output_written_size, | |
+ const unsigned char *input, | |
+ size_t input_size, | |
+ size_t *input_consumed_size | |
+ ); | |
+ | |
+#ifdef __cplusplus | |
+} | |
+#endif | |
+ | |
+#endif | |
diff --git a/lib/libesp32/berry/default/be_modtab.c b/lib/libesp32/berry/default/be_modtab.c | |
index a4e7c7a33..6e2f7a24e 100644 | |
--- a/lib/libesp32/berry/default/be_modtab.c | |
+++ b/lib/libesp32/berry/default/be_modtab.c | |
@@ -51,7 +51,11 @@ be_extern_native_module(crc); | |
be_extern_native_module(crypto); | |
be_extern_native_module(ULP); | |
be_extern_native_module(TFL); | |
+be_extern_native_module(miniz); | |
be_extern_native_module(mdns); | |
+#ifdef USE_TAMP_COMPRESSION | |
+be_extern_native_module(tamp); | |
+#endif //USE_TAMP_COMPRESSION | |
#ifdef USE_ZIGBEE | |
be_extern_native_module(zigbee); | |
#endif // USE_ZIGBEE | |
@@ -188,6 +192,12 @@ BERRY_LOCAL const bntvmodule_t* const be_module_table[] = { | |
#ifdef USE_MATTER_DEVICE | |
&be_native_module(matter), | |
#endif // USE_MATTER_DEVICE | |
+#ifdef USE_MINIZ_COMPRESSION | |
+ &be_native_module(miniz), | |
+#endif // USE_MINIZ_COMPRESSION | |
+#ifdef USE_TAMP_COMPRESSION | |
+ &be_native_module(tamp), | |
+#endif // USE_TAMP_COMPRESSION | |
#endif // TASMOTA | |
/* user-defined modules register end */ | |
NULL /* do not remove */ | |
diff --git a/lib/libesp32/berry_tasmota/src/be_miniz_lib.c b/lib/libesp32/berry_tasmota/src/be_miniz_lib.c | |
new file mode 100644 | |
index 000000000..66daddb27 | |
--- /dev/null | |
+++ b/lib/libesp32/berry_tasmota/src/be_miniz_lib.c | |
@@ -0,0 +1,22 @@ | |
+/******************************************************************** | |
+ * Tasmota lib | |
+ * | |
+ * To use: import miniz` | |
+ *******************************************************************/ | |
+#include "be_constobj.h" | |
+#include "be_mapping.h" | |
+ | |
+#if defined(USE_MINIZ_COMPRESSION) | |
+ | |
+extern void be_miniz_version(void); | |
+BE_FUNC_CTYPE_DECLARE(be_miniz_version, "s", ""); | |
+ | |
+ | |
+/* @const_object_info_begin | |
+module miniz (scope: global) { | |
+ run, ctype_func(be_miniz_version) | |
+} | |
+@const_object_info_end */ | |
+#include "be_fixed_miniz.h" | |
+ | |
+#endif // USE_MINIZ_COMPRESSION | |
diff --git a/lib/libesp32/berry_tasmota/src/be_tamp_lib.c b/lib/libesp32/berry_tasmota/src/be_tamp_lib.c | |
new file mode 100644 | |
index 000000000..690c3047f | |
--- /dev/null | |
+++ b/lib/libesp32/berry_tasmota/src/be_tamp_lib.c | |
@@ -0,0 +1,33 @@ | |
+/******************************************************************** | |
+ * Tasmota lib | |
+ * | |
+ * To use: import tamp` | |
+ *******************************************************************/ | |
+#include "be_constobj.h" | |
+#include "be_mapping.h" | |
+ | |
+ | |
+#if defined(USE_TAMP_COMPRESSION) | |
+ | |
+extern int be_tamp_comp_init(int window, int literal, bbool use_custom_dictionary); | |
+BE_FUNC_CTYPE_DECLARE(be_tamp_comp_init, "i", "[iib]"); | |
+ | |
+extern int be_tamp_comp_run(struct bvm *vm); | |
+ | |
+extern int be_tamp_decomp_init(int window); | |
+BE_FUNC_CTYPE_DECLARE(be_tamp_decomp_init, "i", "[i]"); | |
+ | |
+extern int be_tamp_decomp_run(struct bvm *vm); | |
+ | |
+ | |
+/* @const_object_info_begin | |
+module tamp (scope: global) { | |
+ compressor, ctype_func(be_tamp_comp_init) | |
+ decompressor, ctype_func(be_tamp_decomp_init) | |
+ compress, func(be_tamp_comp_run) | |
+ decompress, func(be_tamp_decomp_run) | |
+} | |
+@const_object_info_end */ | |
+#include "be_fixed_tamp.h" | |
+ | |
+#endif // USE_TAMP_COMPRESSION | |
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_miniz.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_miniz.ino | |
new file mode 100644 | |
index 000000000..f97e116b9 | |
--- /dev/null | |
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_miniz.ino | |
@@ -0,0 +1,44 @@ | |
+/* | |
+ xdrv_52_3_berry_miniz.ino - Berry scripting language, MINIZ ROM functions | |
+ | |
+ Copyright (C) 2024 Stephan Hadinger & Christian Baars, Berry language by Guan Wenliang https://github.com/Skiars/berry | |
+ | |
+ This program is free software: you can redistribute it and/or modify | |
+ it under the terms of the GNU General Public License as published by | |
+ the Free Software Foundation, either version 3 of the License, or | |
+ (at your option) any later version. | |
+ | |
+ This program is distributed in the hope that it will be useful, | |
+ but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
+ GNU General Public License for more details. | |
+ | |
+ You should have received a copy of the GNU General Public License | |
+ along with this program. If not, see <http://www.gnu.org/licenses/>. | |
+*/ | |
+ | |
+#ifdef USE_BERRY | |
+#ifdef USE_MINIZ_COMPRESSION | |
+ | |
+#include "be_mapping.h" | |
+#include <string.h> | |
+ | |
+ | |
+extern "C" { | |
+ /*********************************************************************************************\ | |
+ * Native functions mapped to Berry functions | |
+ * | |
+ * import miniz | |
+ * | |
+ * | |
+ \*********************************************************************************************/ | |
+ #include <miniz.h> | |
+ | |
+ const char * be_miniz_version(){ | |
+ return "0"; | |
+ } | |
+ | |
+} | |
+ | |
+#endif // USE_MINIZ_COMPRESSION | |
+#endif // USE_BERRY | |
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tamp.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tamp.ino | |
new file mode 100644 | |
index 000000000..39ccfb579 | |
--- /dev/null | |
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tamp.ino | |
@@ -0,0 +1,99 @@ | |
+/* | |
+ xdrv_52_3_berry_tamp.ino - Berry scripting language, Tamp compression library | |
+ | |
+ Copyright (C) 2024 Stephan Hadinger & Christian Baars, Berry language by Guan Wenliang https://github.com/Skiars/berry | |
+ | |
+ This program is free software: you can redistribute it and/or modify | |
+ it under the terms of the GNU General Public License as published by | |
+ the Free Software Foundation, either version 3 of the License, or | |
+ (at your option) any later version. | |
+ | |
+ This program is distributed in the hope that it will be useful, | |
+ but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
+ GNU General Public License for more details. | |
+ | |
+ You should have received a copy of the GNU General Public License | |
+ along with this program. If not, see <http://www.gnu.org/licenses/>. | |
+*/ | |
+ | |
+// Mappgin from internal light and a generic `light_state` Berry class | |
+ | |
+#ifdef USE_BERRY | |
+#ifdef USE_TAMP_COMPRESSION | |
+ | |
+#include "be_mapping.h" | |
+#include <string.h> | |
+#include <tamp_compressor.h> | |
+#include <tamp_decompressor.h> | |
+ | |
+extern "C" { | |
+ /*********************************************************************************************\ | |
+ * Native functions mapped to Berry functions | |
+ * | |
+ * import tamp | |
+ * | |
+ \*********************************************************************************************/ | |
+ | |
+ struct{ | |
+ TampCompressor compressor; | |
+ TampDecompressor decompressor; | |
+ unsigned char window[1024]; | |
+ }TAMP; | |
+ | |
+ int be_tamp_comp_init(int window, int literal, bool use_custom_dictionary){ | |
+ TampConf conf = { | |
+ .window = 10, | |
+ .literal = 8, | |
+ .use_custom_dictionary = false | |
+ }; | |
+ return tamp_compressor_init(&TAMP.compressor, &conf, TAMP.window); | |
+ } | |
+ | |
+ int be_tamp_comp_run(bvm *vm){ | |
+ int32_t argc = be_top(vm); | |
+ if (argc == 1 && be_isbytes(vm, 1)) { | |
+ size_t input_size; | |
+ const void * input = be_tobytes(vm, 1, &input_size); | |
+ size_t input_consumed_size, output_written_size; | |
+ size_t output_size = input_size + input_size/2; | |
+ uint8_t output[output_size]; | |
+ int error = tamp_compressor_compress_and_flush( | |
+ &TAMP.compressor, | |
+ (unsigned char*)output, output_size, &output_written_size, | |
+ (unsigned char*)input, input_size, &input_consumed_size, | |
+ false); | |
+ be_pushbytes(vm, output, output_written_size); | |
+ AddLog(LOG_LEVEL_DEBUG, "TAM: error %i input size '%u', bytes written '%u'", error, input_size, output_written_size); | |
+ be_return(vm); | |
+ } | |
+ be_raise(vm, "attribute_error", NULL); | |
+ } | |
+ | |
+ int be_tamp_decomp_init(int window){ | |
+ return tamp_decompressor_init(&TAMP.decompressor, NULL, TAMP.window); | |
+ } | |
+ | |
+ int be_tamp_decomp_run(bvm *vm){ | |
+ int32_t argc = be_top(vm); | |
+ if (argc == 1 && be_isbytes(vm, 1)) { | |
+ size_t input_size; | |
+ const void * input = be_tobytes(vm, 1, &input_size); | |
+ size_t input_consumed_size, output_written_size; | |
+ size_t output_size = input_size * 8; | |
+ uint8_t output[output_size]; | |
+ int error = tamp_decompressor_decompress( | |
+ &TAMP.decompressor, | |
+ (unsigned char*)output, output_size, &output_written_size, | |
+ (unsigned char*)input, input_size, &input_consumed_size | |
+ ); | |
+ AddLog(LOG_LEVEL_DEBUG, "TAM: error %i input size '%u', bytes written '%u'", error, input_size, output_written_size); | |
+ be_pushbytes(vm, output, output_written_size); | |
+ be_return(vm); | |
+ } | |
+ be_raise(vm, "attribute_error", NULL); | |
+ } | |
+} | |
+ | |
+#endif // USE_TAMP_COMPRESSION | |
+#endif // USE_BERRY |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment