diff --git a/lib/uzlib/LICENSE b/lib/uzlib/LICENSE new file mode 100644 index 0000000..c96e382 --- /dev/null +++ b/lib/uzlib/LICENSE @@ -0,0 +1,65 @@ +Introduction +------------ + +uzlib as a whole is licensed under the terms of Zlib license, listed in +the next section. It consists of substantial works of individuals whose +copyrights listed in the next session. Some portions of the uzlib codebase +originally were licensed under different license(s), however compatible +with the Zlib license. Such license(s) are listed at the end of this file. + +License +------- + +uzlib - Deflate/Zlib-compatible LZ77 compression/decompression library + +Copyright (c) 2003 Joergen Ibsen +Copyright (c) 1997-2014 Simon Tatham +Copyright (c) 2014-2020 Paul Sokolovsky + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +------- +Original license for src/defl_static.c: + +PuTTY is copyright 1997-2014 Simon Tatham. + +Portions copyright Robert de Bath, Joris van Rantwijk, Delian +Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, +Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus +Kuhn, Colin Watson, and CORE SDI S.A. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/uzlib/README.md b/lib/uzlib/README.md new file mode 100644 index 0000000..af036f3 --- /dev/null +++ b/lib/uzlib/README.md @@ -0,0 +1,18 @@ +# uzlib + +Vendored copy of `pfalcon/uzlib` for OpenDisplay firmware. + +Upstream: https://github.com/pfalcon/uzlib +Commit: `6d60d651a4499a64f2e5b21b4cc08d98cb84b5c1` + +Only the files needed for current zlib/DEFLATE inflate usage are included: + +- `tinflate.c` +- `tinfzlib.c` +- `adler32.c` +- `crc32.c` +- public headers required by those sources + +Local changes should be kept in this folder so the firmware build is +self-contained and future streaming/nonblocking inflater patches can be +reviewed together with the firmware code. diff --git a/lib/uzlib/library.json b/lib/uzlib/library.json new file mode 100644 index 0000000..f324422 --- /dev/null +++ b/lib/uzlib/library.json @@ -0,0 +1,10 @@ +{ + "name": "uzlib", + "version": "0.0.0-opendisplay", + "description": "Vendored uzlib inflater snapshot for OpenDisplay firmware", + "license": "Zlib", + "build": { + "srcDir": "src", + "includeDir": "src" + } +} diff --git a/lib/uzlib/src/adler32.c b/lib/uzlib/src/adler32.c new file mode 100644 index 0000000..a362ead --- /dev/null +++ b/lib/uzlib/src/adler32.c @@ -0,0 +1,78 @@ +/* + * Adler-32 checksum + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +/* + * Adler-32 algorithm taken from the zlib source, which is + * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + */ + +#include "uzlib.h" + +#define A32_BASE 65521 +#define A32_NMAX 5552 + +uint32_t uzlib_adler32(const void *data, unsigned int length, uint32_t prev_sum /* 1 */) +{ + const unsigned char *buf = (const unsigned char *)data; + + unsigned int s1 = prev_sum & 0xffff; + unsigned int s2 = prev_sum >> 16; + + while (length > 0) + { + int k = length < A32_NMAX ? length : A32_NMAX; + int i; + + for (i = k / 16; i; --i, buf += 16) + { + s1 += buf[0]; s2 += s1; s1 += buf[1]; s2 += s1; + s1 += buf[2]; s2 += s1; s1 += buf[3]; s2 += s1; + s1 += buf[4]; s2 += s1; s1 += buf[5]; s2 += s1; + s1 += buf[6]; s2 += s1; s1 += buf[7]; s2 += s1; + + s1 += buf[8]; s2 += s1; s1 += buf[9]; s2 += s1; + s1 += buf[10]; s2 += s1; s1 += buf[11]; s2 += s1; + s1 += buf[12]; s2 += s1; s1 += buf[13]; s2 += s1; + s1 += buf[14]; s2 += s1; s1 += buf[15]; s2 += s1; + } + + for (i = k % 16; i; --i) { s1 += *buf++; s2 += s1; } + + s1 %= A32_BASE; + s2 %= A32_BASE; + + length -= k; + } + + return ((uint32_t)s2 << 16) | s1; +} diff --git a/lib/uzlib/src/crc32.c b/lib/uzlib/src/crc32.c new file mode 100644 index 0000000..9358aae --- /dev/null +++ b/lib/uzlib/src/crc32.c @@ -0,0 +1,63 @@ +/* + * CRC32 checksum + * + * Copyright (c) 1998-2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +/* + * CRC32 algorithm taken from the zlib source, which is + * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + */ + +#include "uzlib.h" + +static const uint32_t tinf_crc32tab[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, + 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, + 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, + 0xbdbdf21c +}; + +/* crc is previous value for incremental computation, 0xffffffff initially */ +uint32_t uzlib_crc32(const void *data, unsigned int length, uint32_t crc) +{ + const unsigned char *buf = (const unsigned char *)data; + unsigned int i; + + for (i = 0; i < length; ++i) + { + crc ^= buf[i]; + crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4); + crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4); + } + + // return value suitable for passing in next time, for final value invert it + return crc/* ^ 0xffffffff*/; +} diff --git a/lib/uzlib/src/od_zlib_stream.c b/lib/uzlib/src/od_zlib_stream.c new file mode 100644 index 0000000..0944ab7 --- /dev/null +++ b/lib/uzlib/src/od_zlib_stream.c @@ -0,0 +1,694 @@ +/* + * OpenDisplay streaming zlib inflater, based on uzlib/tinf. + */ + +#include +#include "uzlib.h" + +#define TINF_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr))) + +typedef struct { + unsigned short table[16]; + unsigned short trans[288]; +} TINF_LITERAL_TREE; + +typedef struct { + unsigned short table[16]; + unsigned short trans[32]; +} TINF_DISTANCE_TREE; + +typedef struct { + unsigned short table[16]; + unsigned short trans[19]; +} TINF_CODELEN_TREE; + +static const unsigned char length_bits[30] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, + 5, 5, 5, 5 +}; + +static const unsigned short length_base[30] = { + 3, 4, 5, 6, 7, 8, 9, 10, + 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, + 131, 163, 195, 227, 258 +}; + +static const unsigned char dist_bits[30] = { + 0, 0, 0, 0, 1, 1, 2, 2, + 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13 +}; + +static const unsigned short dist_base[30] = { + 1, 2, 3, 4, 5, 7, 9, 13, + 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, + 4097, 6145, 8193, 12289, 16385, 24577 +}; + +static const unsigned char clcidx[] = { + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +}; + +typedef enum { + ST_ZLIB_CMF, + ST_ZLIB_FLG, + ST_BLOCK_FINAL, + ST_BLOCK_TYPE, + ST_DYNAMIC_TREES, + ST_STORED_LEN, + ST_STORED_DATA, + ST_BLOCK_DATA, + ST_TRAILER, + ST_DONE, + ST_ERROR, +} inflate_stage_t; + +typedef enum { + BLK_SYMBOL, + BLK_LEN_EXTRA, + BLK_DIST_SYMBOL, + BLK_DIST_EXTRA, + BLK_MATCH_COPY, +} block_stage_t; + +typedef enum { + DYN_HLIT, + DYN_HDIST, + DYN_HCLEN, + DYN_CLEAR_CLEN, + DYN_READ_CLEN, + DYN_BUILD_CLTREE, + DYN_READ_LENGTHS, + DYN_REPEAT_BITS, + DYN_BUILD_TREES, +} dynamic_stage_t; + +typedef struct { + inflate_stage_t stage; + block_stage_t block_stage; + dynamic_stage_t dynamic_stage; + + const uint8_t *input; + size_t input_remaining; + bool input_final; + bool initialized; + + uint8_t bit_tag; + uint8_t bit_count; + + bool read_bits_active; + uint8_t read_bits_num; + uint8_t read_bits_pos; + unsigned int read_bits_base; + unsigned int read_bits_value; + + bool sym_active; + int sym_sum; + int sym_cur; + int sym_len; + + uint8_t cmf; + uint8_t bfinal; + uint8_t btype; + + TINF_LITERAL_TREE ltree; + union { + TINF_DISTANCE_TREE dtree; + TINF_CODELEN_TREE cltree; + } tree; + + unsigned char lengths[288 + 32]; + unsigned int hlit; + unsigned int hdist; + unsigned int hclen; + unsigned int hlimit; + unsigned int dyn_i; + unsigned int dyn_num; + unsigned char dyn_fill_value; + unsigned int dyn_repeat_len; + unsigned int dyn_repeat_bits; + unsigned int dyn_repeat_base; + + uint8_t stored_header_index; + uint16_t stored_len; + uint16_t stored_invlen; + + unsigned int match_len; + unsigned int match_offset; + int length_sym; + int dist_sym; + + uint8_t window[OPENDISPLAY_ZLIB_WINDOW_SIZE]; + unsigned int window_pos; + uint32_t expected_output; + uint32_t output_count; + uint32_t adler; + uint8_t trailer_index; + uint32_t trailer_value; + + const char *error; +} od_zlib_stream_state_t; + +static od_zlib_stream_state_t s; + +static void set_error(const char *error) { + s.stage = ST_ERROR; + s.error = error; +} + +static bool build_tree(unsigned short *table, unsigned short *trans, unsigned int trans_size, const unsigned char *lengths, unsigned int num) { + unsigned short offs[16]; + unsigned int i, sum; + + for (i = 0; i < 16; ++i) table[i] = 0; + for (i = 0; i < num; ++i) { + if (lengths[i] >= TINF_ARRAY_SIZE(offs)) { + set_error("invalid huffman code length"); + return false; + } + table[lengths[i]]++; + } + table[0] = 0; + + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += table[i]; + } + if (sum > trans_size) { + set_error("huffman tree exceeds symbol storage"); + return false; + } + + for (i = 0; i < num; ++i) { + if (lengths[i]) trans[offs[lengths[i]]++] = i; + } + return true; +} + +static void build_fixed_trees(void) { + int i; + memset(&s.ltree, 0, sizeof(s.ltree)); + memset(&s.tree.dtree, 0, sizeof(s.tree.dtree)); + + s.ltree.table[7] = 24; + s.ltree.table[8] = 152; + s.ltree.table[9] = 112; + for (i = 0; i < 24; ++i) s.ltree.trans[i] = 256 + i; + for (i = 0; i < 144; ++i) s.ltree.trans[24 + i] = i; + for (i = 0; i < 8; ++i) s.ltree.trans[24 + 144 + i] = 280 + i; + for (i = 0; i < 112; ++i) s.ltree.trans[24 + 144 + 8 + i] = 144 + i; + + s.tree.dtree.table[5] = 32; + for (i = 0; i < 32; ++i) s.tree.dtree.trans[i] = i; +} + +static int read_byte(uint8_t *value) { + if (s.input_remaining > 0) { + *value = *s.input++; + s.input_remaining--; + return 1; + } + if (!s.input_final) return 0; + set_error("truncated zlib stream"); + return -1; +} + +static int read_bit(unsigned int *bit) { + uint8_t byte; + if (s.bit_count == 0) { + int rc = read_byte(&byte); + if (rc <= 0) return rc; + s.bit_tag = byte; + s.bit_count = 8; + } + *bit = s.bit_tag & 1u; + s.bit_tag >>= 1u; + s.bit_count--; + return 1; +} + +static int read_bits(unsigned int num, unsigned int base, unsigned int *value) { + if (!s.read_bits_active) { + s.read_bits_active = true; + s.read_bits_num = (uint8_t)num; + s.read_bits_pos = 0; + s.read_bits_base = base; + s.read_bits_value = 0; + } + + while (s.read_bits_pos < s.read_bits_num) { + unsigned int bit; + int rc = read_bit(&bit); + if (rc <= 0) return rc; + if (bit) s.read_bits_value += 1u << s.read_bits_pos; + s.read_bits_pos++; + } + + *value = s.read_bits_value + s.read_bits_base; + s.read_bits_active = false; + return 1; +} + +static int decode_symbol(const unsigned short *table, const unsigned short *trans, unsigned int trans_size, int *symbol) { + if (!s.sym_active) { + s.sym_active = true; + s.sym_sum = 0; + s.sym_cur = 0; + s.sym_len = 0; + } + + do { + unsigned int bit; + int rc = read_bit(&bit); + if (rc <= 0) return rc; + + s.sym_cur = 2 * s.sym_cur + (int)bit; + if (++s.sym_len == 16) { + set_error("invalid huffman code"); + return -1; + } + + s.sym_sum += table[s.sym_len]; + s.sym_cur -= table[s.sym_len]; + } while (s.sym_cur >= 0); + + s.sym_sum += s.sym_cur; + if (s.sym_sum < 0 || s.sym_sum >= (int)trans_size) { + set_error("invalid huffman symbol"); + return -1; + } + *symbol = trans[s.sym_sum]; + s.sym_active = false; + return 1; +} + +static void reset_code_readers(void) { + s.read_bits_active = false; + s.sym_active = false; +} + +static void adler_update_byte(uint8_t byte) { + uint32_t s1 = s.adler & 0xffffu; + uint32_t s2 = s.adler >> 16; + s1 += byte; + if (s1 >= 65521u) s1 -= 65521u; + s2 += s1; + s2 %= 65521u; + s.adler = (s2 << 16) | s1; +} + +static bool put_output_byte(uint8_t byte, uint8_t *output, size_t capacity, size_t *produced) { + if (*produced >= capacity) return false; + if (s.output_count >= s.expected_output) { + set_error("decompressed output exceeds expected size"); + return false; + } + + output[(*produced)++] = byte; + s.window[s.window_pos] = byte; + s.window_pos = (s.window_pos + 1u) & (OPENDISPLAY_ZLIB_WINDOW_SIZE - 1u); + s.output_count++; + adler_update_byte(byte); + return true; +} + +static int process_dynamic_trees(void) { + int sym; + unsigned int value; + + for (;;) { + switch (s.dynamic_stage) { + case DYN_HLIT: + if (read_bits(5, 257, &s.hlit) <= 0) return s.stage == ST_ERROR ? -1 : 0; + s.dynamic_stage = DYN_HDIST; + break; + case DYN_HDIST: + if (read_bits(5, 1, &s.hdist) <= 0) return s.stage == ST_ERROR ? -1 : 0; + s.dynamic_stage = DYN_HCLEN; + break; + case DYN_HCLEN: + if (read_bits(4, 4, &s.hclen) <= 0) return s.stage == ST_ERROR ? -1 : 0; + if (s.hlit > 286 || s.hdist > 32 || s.hlit + s.hdist > TINF_ARRAY_SIZE(s.lengths)) { + set_error("invalid dynamic tree sizes"); + return -1; + } + s.dyn_i = 0; + s.dynamic_stage = DYN_CLEAR_CLEN; + break; + case DYN_CLEAR_CLEN: + while (s.dyn_i < 19) s.lengths[s.dyn_i++] = 0; + s.dyn_i = 0; + s.dynamic_stage = DYN_READ_CLEN; + break; + case DYN_READ_CLEN: + while (s.dyn_i < s.hclen) { + if (read_bits(3, 0, &value) <= 0) return s.stage == ST_ERROR ? -1 : 0; + s.lengths[clcidx[s.dyn_i++]] = (unsigned char)value; + } + s.dynamic_stage = DYN_BUILD_CLTREE; + break; + case DYN_BUILD_CLTREE: + if (!build_tree(s.tree.cltree.table, s.tree.cltree.trans, TINF_ARRAY_SIZE(s.tree.cltree.trans), s.lengths, 19)) return -1; + s.hlimit = s.hlit + s.hdist; + s.dyn_num = 0; + s.dynamic_stage = DYN_READ_LENGTHS; + reset_code_readers(); + break; + case DYN_READ_LENGTHS: + while (s.dyn_num < s.hlimit) { + int rc = decode_symbol(s.tree.cltree.table, s.tree.cltree.trans, TINF_ARRAY_SIZE(s.tree.cltree.trans), &sym); + if (rc <= 0) return s.stage == ST_ERROR ? -1 : 0; + if (sym < 16) { + s.lengths[s.dyn_num++] = (unsigned char)sym; + continue; + } + if (sym == 16) { + if (s.dyn_num == 0) { + set_error("invalid dynamic repeat"); + return -1; + } + s.dyn_fill_value = s.lengths[s.dyn_num - 1]; + s.dyn_repeat_bits = 2; + s.dyn_repeat_base = 3; + } else if (sym == 17) { + s.dyn_fill_value = 0; + s.dyn_repeat_bits = 3; + s.dyn_repeat_base = 3; + } else if (sym == 18) { + s.dyn_fill_value = 0; + s.dyn_repeat_bits = 7; + s.dyn_repeat_base = 11; + } else { + set_error("invalid dynamic code length symbol"); + return -1; + } + s.dynamic_stage = DYN_REPEAT_BITS; + break; + } + if (s.dynamic_stage != DYN_READ_LENGTHS) break; + s.dynamic_stage = DYN_BUILD_TREES; + break; + case DYN_REPEAT_BITS: + if (read_bits(s.dyn_repeat_bits, s.dyn_repeat_base, &s.dyn_repeat_len) <= 0) { + return s.stage == ST_ERROR ? -1 : 0; + } + if (s.dyn_num + s.dyn_repeat_len > s.hlimit) { + set_error("dynamic repeat exceeds tree size"); + return -1; + } + while (s.dyn_repeat_len--) s.lengths[s.dyn_num++] = s.dyn_fill_value; + s.dynamic_stage = DYN_READ_LENGTHS; + break; + case DYN_BUILD_TREES: + if (s.lengths[256] == 0) { + set_error("dynamic tree missing end-of-block"); + return -1; + } + if (!build_tree(s.ltree.table, s.ltree.trans, TINF_ARRAY_SIZE(s.ltree.trans), s.lengths, s.hlit)) return -1; + if (!build_tree(s.tree.dtree.table, s.tree.dtree.trans, TINF_ARRAY_SIZE(s.tree.dtree.trans), s.lengths + s.hlit, s.hdist)) return -1; + s.dynamic_stage = DYN_HLIT; + reset_code_readers(); + return 1; + } + } +} + +static int process_stored_header(void) { + uint8_t byte; + while (s.stored_header_index < 4) { + int rc = read_byte(&byte); + if (rc <= 0) return rc; + if (s.stored_header_index == 0) s.stored_len = byte; + else if (s.stored_header_index == 1) s.stored_len |= (uint16_t)byte << 8; + else if (s.stored_header_index == 2) s.stored_invlen = byte; + else s.stored_invlen |= (uint16_t)byte << 8; + s.stored_header_index++; + } + if (s.stored_len != (uint16_t)(~s.stored_invlen)) { + set_error("invalid stored block length"); + return -1; + } + s.stored_header_index = 0; + return 1; +} + +static int process_stored_data(uint8_t *output, size_t capacity, size_t *produced) { + while (s.stored_len > 0) { + uint8_t byte; + int rc; + if (*produced >= capacity) return 2; + rc = read_byte(&byte); + if (rc <= 0) return rc; + if (!put_output_byte(byte, output, capacity, produced)) return s.stage == ST_ERROR ? -1 : 2; + s.stored_len--; + } + return 1; +} + +static int process_block_data(uint8_t *output, size_t capacity, size_t *produced) { + int sym; + + for (;;) { + if (*produced >= capacity) return 2; + switch (s.block_stage) { + case BLK_SYMBOL: { + int rc = decode_symbol(s.ltree.table, s.ltree.trans, TINF_ARRAY_SIZE(s.ltree.trans), &sym); + if (rc <= 0) return rc; + if (sym < 256) { + if (!put_output_byte((uint8_t)sym, output, capacity, produced)) return s.stage == ST_ERROR ? -1 : 2; + return 1; + } + if (sym == 256) { + s.block_stage = BLK_SYMBOL; + return 3; + } + sym -= 257; + if (sym < 0 || sym >= 29) { + set_error("invalid length symbol"); + return -1; + } + s.length_sym = sym; + s.block_stage = BLK_LEN_EXTRA; + break; + } + case BLK_LEN_EXTRA: + if (read_bits(length_bits[s.length_sym], length_base[s.length_sym], &s.match_len) <= 0) { + return s.stage == ST_ERROR ? -1 : 0; + } + s.block_stage = BLK_DIST_SYMBOL; + break; + case BLK_DIST_SYMBOL: { + int rc = decode_symbol(s.tree.dtree.table, s.tree.dtree.trans, TINF_ARRAY_SIZE(s.tree.dtree.trans), &s.dist_sym); + if (rc <= 0) return rc; + if (s.dist_sym < 0 || s.dist_sym >= 30) { + set_error("invalid distance symbol"); + return -1; + } + s.block_stage = BLK_DIST_EXTRA; + break; + } + case BLK_DIST_EXTRA: + if (read_bits(dist_bits[s.dist_sym], dist_base[s.dist_sym], &s.match_offset) <= 0) { + return s.stage == ST_ERROR ? -1 : 0; + } + if (s.match_offset == 0 || s.match_offset > OPENDISPLAY_ZLIB_WINDOW_SIZE || s.match_offset > s.output_count) { + set_error("invalid back-reference distance"); + return -1; + } + s.block_stage = BLK_MATCH_COPY; + break; + case BLK_MATCH_COPY: + while (s.match_len > 0) { + uint8_t byte; + unsigned int source; + if (*produced >= capacity) return 2; + source = (s.window_pos + OPENDISPLAY_ZLIB_WINDOW_SIZE - s.match_offset) & (OPENDISPLAY_ZLIB_WINDOW_SIZE - 1u); + byte = s.window[source]; + if (!put_output_byte(byte, output, capacity, produced)) return s.stage == ST_ERROR ? -1 : 2; + s.match_len--; + } + s.block_stage = BLK_SYMBOL; + break; + } + } +} + +static int process_trailer(void) { + uint8_t byte; + while (s.trailer_index < 4) { + int rc = read_byte(&byte); + if (rc <= 0) return rc; + s.trailer_value = (s.trailer_value << 8) | byte; + s.trailer_index++; + } + if (s.trailer_value != s.adler) { + set_error("zlib adler32 mismatch"); + return -1; + } + if (s.output_count != s.expected_output) { + set_error("decompressed output size mismatch"); + return -1; + } + if (s.input_remaining > 0) { + set_error("input after end of zlib stream"); + return -1; + } + s.stage = ST_DONE; + return 1; +} + +void od_zlib_stream_reset(uint32_t expected_output_size) { + memset(&s, 0, sizeof(s)); + s.stage = ST_ZLIB_CMF; + s.block_stage = BLK_SYMBOL; + s.dynamic_stage = DYN_HLIT; + s.expected_output = expected_output_size; + s.adler = 1; + s.initialized = true; +} + +od_zlib_status_t od_zlib_stream_push(const uint8_t *input, size_t len, bool final) { + if (!s.initialized) { + set_error("zlib stream not initialized"); + return OD_ZLIB_STATUS_ERROR; + } + if (len > 0 && input == NULL) { + set_error("zlib stream input is null"); + return OD_ZLIB_STATUS_ERROR; + } + if (s.input_remaining > 0) { + set_error("previous input not fully consumed"); + return OD_ZLIB_STATUS_ERROR; + } + s.input = input; + s.input_remaining = len; + s.input_final = final; + if (s.stage == ST_DONE) { + if (len != 0) { + set_error("input after end of zlib stream"); + return OD_ZLIB_STATUS_ERROR; + } + return OD_ZLIB_STATUS_DONE; + } + return OD_ZLIB_STATUS_NEEDS_INPUT; +} + +od_zlib_status_t od_zlib_stream_poll(uint8_t *output, size_t capacity, size_t *produced) { + *produced = 0; + if (!s.initialized) { + set_error("zlib stream not initialized"); + return OD_ZLIB_STATUS_ERROR; + } + if (s.stage == ST_ERROR) return OD_ZLIB_STATUS_ERROR; + if (s.stage == ST_DONE) return OD_ZLIB_STATUS_DONE; + + for (;;) { + int rc; + unsigned int value; + uint8_t byte; + + if (*produced >= capacity) return OD_ZLIB_STATUS_OUTPUT_READY; + + switch (s.stage) { + case ST_ZLIB_CMF: + rc = read_byte(&s.cmf); + if (rc <= 0) return s.stage == ST_ERROR ? OD_ZLIB_STATUS_ERROR : OD_ZLIB_STATUS_NEEDS_INPUT; + s.stage = ST_ZLIB_FLG; + break; + case ST_ZLIB_FLG: + rc = read_byte(&byte); + if (rc <= 0) return s.stage == ST_ERROR ? OD_ZLIB_STATUS_ERROR : OD_ZLIB_STATUS_NEEDS_INPUT; + if (((256u * s.cmf + byte) % 31u) != 0 || (s.cmf & 0x0fu) != 8u || (byte & 0x20u) != 0) { + set_error("invalid zlib header"); + return OD_ZLIB_STATUS_ERROR; + } + if (((s.cmf >> 4) + 8u) > OPENDISPLAY_ZLIB_WINDOW_BITS) { + set_error("zlib stream window exceeds firmware limit"); + return OD_ZLIB_STATUS_ERROR; + } + s.stage = ST_BLOCK_FINAL; + break; + case ST_BLOCK_FINAL: + rc = read_bit(&value); + if (rc <= 0) return s.stage == ST_ERROR ? OD_ZLIB_STATUS_ERROR : OD_ZLIB_STATUS_NEEDS_INPUT; + s.bfinal = (uint8_t)value; + s.stage = ST_BLOCK_TYPE; + break; + case ST_BLOCK_TYPE: + rc = read_bits(2, 0, &value); + if (rc <= 0) return s.stage == ST_ERROR ? OD_ZLIB_STATUS_ERROR : OD_ZLIB_STATUS_NEEDS_INPUT; + s.btype = (uint8_t)value; + if (s.btype == 0) { + s.bit_count = 0; + s.stored_header_index = 0; + s.stored_len = 0; + s.stored_invlen = 0; + s.stage = ST_STORED_LEN; + } else if (s.btype == 1) { + build_fixed_trees(); + s.block_stage = BLK_SYMBOL; + reset_code_readers(); + s.stage = ST_BLOCK_DATA; + } else if (s.btype == 2) { + s.dynamic_stage = DYN_HLIT; + reset_code_readers(); + s.stage = ST_DYNAMIC_TREES; + } else { + set_error("invalid deflate block type"); + return OD_ZLIB_STATUS_ERROR; + } + break; + case ST_DYNAMIC_TREES: + rc = process_dynamic_trees(); + if (rc < 0) return OD_ZLIB_STATUS_ERROR; + if (rc == 0) return OD_ZLIB_STATUS_NEEDS_INPUT; + s.block_stage = BLK_SYMBOL; + s.stage = ST_BLOCK_DATA; + break; + case ST_STORED_LEN: + rc = process_stored_header(); + if (rc < 0) return OD_ZLIB_STATUS_ERROR; + if (rc == 0) return OD_ZLIB_STATUS_NEEDS_INPUT; + s.stage = ST_STORED_DATA; + break; + case ST_STORED_DATA: + rc = process_stored_data(output, capacity, produced); + if (rc < 0) return OD_ZLIB_STATUS_ERROR; + if (rc == 0) return *produced ? OD_ZLIB_STATUS_OUTPUT_READY : OD_ZLIB_STATUS_NEEDS_INPUT; + if (rc == 2) return OD_ZLIB_STATUS_OUTPUT_READY; + s.stage = s.bfinal ? ST_TRAILER : ST_BLOCK_FINAL; + break; + case ST_BLOCK_DATA: + rc = process_block_data(output, capacity, produced); + if (rc < 0) return OD_ZLIB_STATUS_ERROR; + if (rc == 0) return *produced ? OD_ZLIB_STATUS_OUTPUT_READY : OD_ZLIB_STATUS_NEEDS_INPUT; + if (rc == 2) return OD_ZLIB_STATUS_OUTPUT_READY; + if (rc == 3) s.stage = s.bfinal ? ST_TRAILER : ST_BLOCK_FINAL; + break; + case ST_TRAILER: + rc = process_trailer(); + if (rc < 0) return OD_ZLIB_STATUS_ERROR; + if (rc == 0) return *produced ? OD_ZLIB_STATUS_OUTPUT_READY : OD_ZLIB_STATUS_NEEDS_INPUT; + return OD_ZLIB_STATUS_DONE; + case ST_DONE: + return OD_ZLIB_STATUS_DONE; + case ST_ERROR: + return OD_ZLIB_STATUS_ERROR; + } + } +} + +const char *od_zlib_stream_error(void) { + return s.error ? s.error : ""; +} + +uint32_t od_zlib_stream_output_count(void) { + return s.output_count; +} diff --git a/lib/uzlib/src/uzlib.h b/lib/uzlib/src/uzlib.h new file mode 100644 index 0000000..a978783 --- /dev/null +++ b/lib/uzlib/src/uzlib.h @@ -0,0 +1,51 @@ +/* + * OpenDisplay streaming zlib inflater, based on uzlib/tinf. + * + * The original uzlib one-shot/callback inflater API is intentionally not + * exposed in this vendored copy. Firmware uses one global streaming inflater. + */ + +#ifndef UZLIB_H_INCLUDED +#define UZLIB_H_INCLUDED + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "uzlib_conf.h" + +#ifndef OPENDISPLAY_ZLIB_WINDOW_BITS +#define OPENDISPLAY_ZLIB_WINDOW_BITS 9 +#endif + +#if OPENDISPLAY_ZLIB_WINDOW_BITS < 9 || OPENDISPLAY_ZLIB_WINDOW_BITS > 15 +#error "OPENDISPLAY_ZLIB_WINDOW_BITS must be in range 9..15" +#endif + +#define OPENDISPLAY_ZLIB_WINDOW_SIZE (1u << OPENDISPLAY_ZLIB_WINDOW_BITS) + +typedef enum { + OD_ZLIB_STATUS_NEEDS_INPUT = 0, + OD_ZLIB_STATUS_OUTPUT_READY = 1, + OD_ZLIB_STATUS_DONE = 2, + OD_ZLIB_STATUS_ERROR = -1, +} od_zlib_status_t; + +void od_zlib_stream_reset(uint32_t expected_output_size); +od_zlib_status_t od_zlib_stream_push(const uint8_t *input, size_t len, bool final); +od_zlib_status_t od_zlib_stream_poll(uint8_t *output, size_t capacity, size_t *produced); +const char *od_zlib_stream_error(void); +uint32_t od_zlib_stream_output_count(void); + +uint32_t uzlib_adler32(const void *data, unsigned int length, uint32_t prev_sum); +uint32_t uzlib_crc32(const void *data, unsigned int length, uint32_t crc); + +#ifdef __cplusplus +} +#endif + +#endif /* UZLIB_H_INCLUDED */ diff --git a/lib/uzlib/src/uzlib_conf.h b/lib/uzlib/src/uzlib_conf.h new file mode 100644 index 0000000..fd67dec --- /dev/null +++ b/lib/uzlib/src/uzlib_conf.h @@ -0,0 +1,32 @@ +/* + * uzlib - tiny deflate/inflate library (deflate, gzip, zlib) + * + * Copyright (c) 2014-2018 by Paul Sokolovsky + */ + +#ifndef UZLIB_CONF_H_INCLUDED +#define UZLIB_CONF_H_INCLUDED + +#ifndef UZLIB_CONF_DEBUG_LOG +/* Debug logging level 0, 1, 2, etc. */ +#define UZLIB_CONF_DEBUG_LOG 0 +#endif + +#ifndef UZLIB_CONF_PARANOID_CHECKS +/* Perform extra checks on the input stream, even if they aren't proven + to be strictly required (== lack of them wasn't proven to lead to + crashes). */ +#define UZLIB_CONF_PARANOID_CHECKS 0 +#endif + +#ifndef UZLIB_CONF_USE_MEMCPY +/* Use memcpy() for copying data out of LZ window or uncompressed blocks, + instead of doing this byte by byte. For well-compressed data, this + may noticeably increase decompression speed. But for less compressed, + it can actually deteriorate it (due to the fact that many memcpy() + implementations are optimized for large blocks of data, and have + too much overhead for short strings of just a few bytes). */ +#define UZLIB_CONF_USE_MEMCPY 0 +#endif + +#endif /* UZLIB_CONF_H_INCLUDED */ diff --git a/platformio.ini b/platformio.ini index 2e2d1c1..2ee9870 100644 --- a/platformio.ini +++ b/platformio.ini @@ -5,7 +5,6 @@ extra_configs = [env] lib_deps = https://github.com/bitbank2/bb_epaper.git - https://github.com/pfalcon/uzlib [env:nrf52840custom] build_flags = diff --git a/src/display_service.cpp b/src/display_service.cpp index 387df44..d24b3d5 100644 --- a/src/display_service.cpp +++ b/src/display_service.cpp @@ -37,9 +37,7 @@ extern bool connectionRequested; extern uint8_t mloopcounter; extern bool displayPowerState; extern uint32_t directWriteStartTime; -extern uint8_t* directWriteCompressedBuffer; extern uint32_t directWriteCompressedReceived; -extern uint32_t directWriteCompressedSize; extern uint8_t directWriteRefreshMode; extern uint32_t directWriteTotalBytes; extern uint16_t directWriteHeight; @@ -50,9 +48,7 @@ extern bool directWritePlane2; extern bool directWriteBitplanes; extern bool directWriteCompressed; extern bool directWriteActive; -extern uint8_t* compressedDataBuffer; -extern uint8_t dictionaryBuffer[]; -extern uint8_t decompressionChunk[]; +extern uint8_t decompressionChunk[OPENDISPLAY_DECOMPRESSION_CHUNK_SIZE]; extern uint32_t displayed_etag; @@ -78,18 +74,10 @@ struct PartialStreamContext { uint32_t expected_stream_size; uint32_t plane_size; uint32_t bytes_received; + uint32_t bytes_written; + uint8_t current_plane; }; -uint32_t max_compressed_image_rx_bytes(uint8_t tm) { - if ((tm & TRANSMISSION_MODE_ZIP) == 0) return 0; - if ((tm & TRANSMISSION_MODE_ZIPXL) != 0 && - MAX_COMPRESSED_BUFFER_BYTES > (54u * 1024u)) { - return MAX_COMPRESSED_BUFFER_BYTES; - } - uint32_t stdlim = 54u * 1024u; - return stdlim < MAX_COMPRESSED_BUFFER_BYTES ? stdlim : MAX_COMPRESSED_BUFFER_BYTES; -} - #ifdef TARGET_ESP32 extern BLEAdvertisementData* advertisementData; extern BLEServer* pServer; @@ -113,8 +101,10 @@ void flashLed(uint8_t color, uint8_t brightness); static void cleanup_partial_write_state(void); static bool partial_consume_bytes(uint8_t* data, uint32_t len); static void partial_prepare_panel_ram(void); -static bool partial_prepare_logical_stream(void); static bool partial_write_to_panel(int refreshMode); +static bool partial_write_stream_bytes(uint8_t* data, uint32_t len); +static bool zlib_stream_to_direct_write(const uint8_t* data, uint32_t len, bool final); +static bool zlib_stream_to_partial_write(const uint8_t* data, uint32_t len, bool final); static uint32_t calc_controller_plane_bytes(uint16_t width, uint16_t height); static uint32_t parse_be_u32(const uint8_t* data); static void send_direct_write_nack(uint8_t opcode, uint8_t error, bool cleanupState); @@ -222,6 +212,7 @@ int mapEpd(int id){ case 0x003F: return EP31_240x320; case 0x0040: return EP75YR_800x480; case 0x0041: return EP_PANEL_UNDEFINED; + case 0x0042: return EP133_960x680; default: return EP_PANEL_UNDEFINED; } } @@ -1121,62 +1112,23 @@ void updatemsdata(){ } void handleDirectWriteCompressedData(uint8_t* data, uint16_t len) { - if (!compressedDataBuffer) { - cleanupDirectWriteState(false); + if (len > UINT32_MAX - directWriteCompressedReceived) { + cleanupDirectWriteState(true); uint8_t errorResponse[] = {0xFF, 0xFF}; sendResponse(errorResponse, sizeof(errorResponse)); return; } - uint32_t newTotalSize = directWriteCompressedReceived + len; - uint32_t cap = (globalConfig.display_count > 0) - ? max_compressed_image_rx_bytes(globalConfig.displays[0].transmission_modes) - : 0; - if (cap == 0 || newTotalSize > cap) { + if (!zlib_stream_to_direct_write(data, len, false)) { cleanupDirectWriteState(true); uint8_t errorResponse[] = {0xFF, 0xFF}; sendResponse(errorResponse, sizeof(errorResponse)); return; } - memcpy(directWriteCompressedBuffer + directWriteCompressedReceived, data, len); directWriteCompressedReceived += len; uint8_t ackResponse[] = {0x00, 0x71}; sendResponse(ackResponse, sizeof(ackResponse)); } -void decompressDirectWriteData() { - if (directWriteCompressedReceived == 0) return; - struct uzlib_uncomp d; - memset(&d, 0, sizeof(d)); - d.source = directWriteCompressedBuffer; - d.source_limit = directWriteCompressedBuffer + directWriteCompressedReceived; - d.source_read_cb = NULL; - uzlib_init(); - int hdr = uzlib_zlib_parse_header(&d); - if (hdr < 0) return; - uint16_t window = 0x100 << hdr; - if (window > (uint16_t)(32 * 1024)) window = (uint16_t)(32 * 1024); - uzlib_uncompress_init(&d, dictionaryBuffer, window); - int res; - do { - d.dest_start = decompressionChunk; - d.dest = decompressionChunk; - d.dest_limit = decompressionChunk + 4096; - res = uzlib_uncompress(&d); - size_t bytesOut = d.dest - d.dest_start; - if (bytesOut > 0) { -#if defined(TARGET_ESP32) && defined(OPENDISPLAY_SEEED_GFX) - if (seeed_driver_used()) { - seeed_gfx_direct_write_chunk(decompressionChunk, (uint32_t)bytesOut); - } else -#endif - { - bbepWriteData(&bbep, decompressionChunk, bytesOut); - } - directWriteBytesWritten += bytesOut; - } - } while (res == TINF_OK && directWriteBytesWritten < directWriteTotalBytes); -} - void cleanupDirectWriteState(bool refreshDisplay) { directWriteActive = false; directWriteCompressed = false; @@ -1184,9 +1136,7 @@ void cleanupDirectWriteState(bool refreshDisplay) { directWritePlane2 = false; directWriteBytesWritten = 0; directWriteCompressedReceived = 0; - directWriteCompressedSize = 0; directWriteDecompressedTotal = 0; - directWriteCompressedBuffer = nullptr; directWriteWidth = 0; directWriteHeight = 0; directWriteTotalBytes = 0; @@ -1230,27 +1180,18 @@ void handleDirectWriteStart(uint8_t* data, uint16_t len) { else directWriteTotalBytes = (pixels + 7) / 8; } if (directWriteCompressed) { - if (!compressedDataBuffer) { + if ((globalConfig.displays[0].transmission_modes & TRANSMISSION_MODE_ZIP) == 0) { cleanupDirectWriteState(false); uint8_t errorResponse[] = {0xFF, 0xFF}; sendResponse(errorResponse, sizeof(errorResponse)); return; } memcpy(&directWriteDecompressedTotal, data, 4); - directWriteCompressedBuffer = compressedDataBuffer; - directWriteCompressedSize = 0; - directWriteCompressedReceived = 0; - if (len > 4) { - uint32_t compressedDataLen = len - 4; - uint32_t cap = max_compressed_image_rx_bytes(globalConfig.displays[0].transmission_modes); - if (compressedDataLen > cap) { - cleanupDirectWriteState(false); - uint8_t errorResponse[] = {0xFF, 0xFF}; - sendResponse(errorResponse, sizeof(errorResponse)); - return; - } - memcpy(directWriteCompressedBuffer, data + 4, compressedDataLen); - directWriteCompressedReceived = compressedDataLen; + if (directWriteDecompressedTotal != directWriteTotalBytes) { + cleanupDirectWriteState(false); + uint8_t errorResponse[] = {0xFF, 0xFF}; + sendResponse(errorResponse, sizeof(errorResponse)); + return; } } directWriteActive = true; @@ -1273,6 +1214,19 @@ void handleDirectWriteStart(uint8_t* data, uint16_t len) { bbepSetAddrWindow(&bbep, 0, 0, globalConfig.displays[0].pixel_width, globalConfig.displays[0].pixel_height); bbepStartWrite(&bbep, directWriteBitplanes ? PLANE_0 : getplane()); } + if (directWriteCompressed) { + od_zlib_stream_reset(directWriteDecompressedTotal); + if (len > 4) { + uint32_t compressedDataLen = len - 4; + if (!zlib_stream_to_direct_write(data + 4, compressedDataLen, false)) { + cleanupDirectWriteState(false); + uint8_t errorResponse[] = {0xFF, 0xFF}; + sendResponse(errorResponse, sizeof(errorResponse)); + return; + } + directWriteCompressedReceived = compressedDataLen; + } + } uint8_t ackResponse[] = {0x00, 0x70}; sendResponse(ackResponse, sizeof(ackResponse)); } @@ -1335,18 +1289,12 @@ void handlePartialWriteStart(uint8_t* data, uint16_t len) { uint32_t planeBytes = calc_controller_plane_bytes(rectW, rectH); uint32_t expectedLogicalSize = planeBytes * 2u; - if (!compressedDataBuffer || expectedLogicalSize > MAX_COMPRESSED_BUFFER_BYTES) { + if (expectedLogicalSize == 0) { uint8_t errResponse[] = {0xFF, 0xFF}; sendResponse(errResponse, sizeof(errResponse)); return; } - uint32_t rxOffset = ((flags & PARTIAL_FLAG_COMPRESSED) != 0) ? expectedLogicalSize : 0u; - if (rxOffset >= MAX_COMPRESSED_BUFFER_BYTES) { - send_direct_write_nack(0x76, ERR_PARTIAL_STREAM, false); - return; - } - memset(&partialCtx, 0, sizeof(partialCtx)); partialCtx.active = true; partialCtx.compressed = (flags & PARTIAL_FLAG_COMPRESSED) != 0; @@ -1358,8 +1306,10 @@ void handlePartialWriteStart(uint8_t* data, uint16_t len) { partialCtx.height = rectH; partialCtx.expected_stream_size = expectedLogicalSize; partialCtx.plane_size = planeBytes; - directWriteCompressedBuffer = compressedDataBuffer + rxOffset; - directWriteCompressedReceived = 0; + partialCtx.current_plane = 0xFF; + + partial_prepare_panel_ram(); + if (partialCtx.compressed) od_zlib_stream_reset(expectedLogicalSize); // Process optional initial stream bytes before ACK if (len > 17) { @@ -1370,8 +1320,6 @@ void handlePartialWriteStart(uint8_t* data, uint16_t len) { } } - partial_prepare_panel_ram(); - uint8_t ackResponse[] = {0x00, 0x76}; sendResponse(ackResponse, sizeof(ackResponse)); } @@ -1420,11 +1368,11 @@ void handleDirectWriteEnd(uint8_t* data, uint16_t len) { return; } if (partialCtx.compressed) { - if (partialCtx.bytes_received == 0 || !partial_prepare_logical_stream()) { + if (partialCtx.bytes_received == 0 || !zlib_stream_to_partial_write(nullptr, 0, true)) { send_direct_write_nack(0x72, ERR_PARTIAL_STREAM, true); return; } - } else if (partialCtx.bytes_received != partialCtx.expected_stream_size) { + } else if (partialCtx.bytes_written != partialCtx.expected_stream_size) { send_direct_write_nack(0x72, ERR_PARTIAL_STREAM, true); return; } @@ -1448,7 +1396,12 @@ void handleDirectWriteEnd(uint8_t* data, uint16_t len) { } if (!directWriteActive) return; directWriteStartTime = 0; - if (directWriteCompressed && directWriteCompressedReceived > 0) decompressDirectWriteData(); + if (directWriteCompressed && !zlib_stream_to_direct_write(nullptr, 0, true)) { + cleanupDirectWriteState(true); + uint8_t errorResponse[] = {0xFF, 0xFF}; + sendResponse(errorResponse, sizeof(errorResponse)); + return; + } int refreshMode = REFRESH_FULL; if (data != nullptr && len >= 1 && data[0] == 1) refreshMode = REFRESH_FAST; writeSerial("EPD refresh: ", false); @@ -1496,59 +1449,106 @@ void handleDirectWriteEnd(uint8_t* data, uint16_t len) { } static void cleanup_partial_write_state(void) { + bool powerDown = partialCtx.active && displayPowerState; memset(&partialCtx, 0, sizeof(partialCtx)); - directWriteCompressedBuffer = nullptr; - directWriteCompressedReceived = 0; + if (powerDown) { + bbepSleep(&bbep, 1); + delay(200); + displayPowerState = false; + pwrmgm(false); + } } static bool partial_consume_bytes(uint8_t* data, uint32_t len) { - if (!directWriteCompressedBuffer) return false; - uint32_t rxLimit = partialCtx.compressed - ? (MAX_COMPRESSED_BUFFER_BYTES - partialCtx.expected_stream_size) - : partialCtx.expected_stream_size; - if (len > rxLimit - partialCtx.bytes_received) return false; - memcpy(directWriteCompressedBuffer + partialCtx.bytes_received, data, len); + if (partialCtx.compressed) { + if (len > UINT32_MAX - partialCtx.bytes_received) return false; + } else { + if (partialCtx.bytes_received > partialCtx.expected_stream_size || + len > partialCtx.expected_stream_size - partialCtx.bytes_received) { + return false; + } + } partialCtx.bytes_received += len; - directWriteCompressedReceived = partialCtx.bytes_received; - return true; + if (partialCtx.compressed) return zlib_stream_to_partial_write(data, len, false); + return partial_write_stream_bytes(data, len); } -static bool partial_prepare_logical_stream(void) { - if (!directWriteCompressedBuffer || !compressedDataBuffer) return false; - if (!partialCtx.compressed) return partialCtx.bytes_received == partialCtx.expected_stream_size; - - struct uzlib_uncomp d; - memset(&d, 0, sizeof(d)); - d.source = directWriteCompressedBuffer; - d.source_limit = directWriteCompressedBuffer + partialCtx.bytes_received; - d.source_read_cb = NULL; - uzlib_init(); - int hdr = uzlib_zlib_parse_header(&d); - if (hdr < 0) return false; - uint16_t window = 0x100 << hdr; - if (window > (uint16_t)(32 * 1024)) window = (uint16_t)(32 * 1024); - uzlib_uncompress_init(&d, dictionaryBuffer, window); - - uint32_t bytesOutTotal = 0; - int res; - do { - d.dest_start = decompressionChunk; - d.dest = decompressionChunk; - d.dest_limit = decompressionChunk + 4096; - res = uzlib_uncompress(&d); - size_t bytesOut = d.dest - d.dest_start; +static bool zlib_stream_to_direct_write(const uint8_t* data, uint32_t len, bool final) { + od_zlib_status_t status = od_zlib_stream_push(data, len, final); + if (status == OD_ZLIB_STATUS_ERROR) { + writeSerial(String("zlib stream error: ") + od_zlib_stream_error(), true); + return false; + } + + for (;;) { + size_t bytesOut = 0; + status = od_zlib_stream_poll(decompressionChunk, OPENDISPLAY_DECOMPRESSION_CHUNK_SIZE, &bytesOut); if (bytesOut > 0) { - if (bytesOutTotal + bytesOut > partialCtx.expected_stream_size) return false; - memcpy(compressedDataBuffer + bytesOutTotal, decompressionChunk, bytesOut); - bytesOutTotal += bytesOut; +#if defined(TARGET_ESP32) && defined(OPENDISPLAY_SEEED_GFX) + if (seeed_driver_used()) { + seeed_gfx_direct_write_chunk(decompressionChunk, (uint32_t)bytesOut); + } else +#endif + { + bbepWriteData(&bbep, decompressionChunk, bytesOut); + } + directWriteBytesWritten += (uint32_t)bytesOut; + if (directWriteBytesWritten > directWriteDecompressedTotal) { + return false; + } + } + if (status == OD_ZLIB_STATUS_OUTPUT_READY) continue; + if (status == OD_ZLIB_STATUS_NEEDS_INPUT) return !final; + if (status == OD_ZLIB_STATUS_DONE) { + if (directWriteBytesWritten != directWriteDecompressedTotal) { + return false; + } + return true; } - } while (res == TINF_OK && bytesOutTotal < partialCtx.expected_stream_size); + writeSerial(String("zlib stream error: ") + od_zlib_stream_error(), true); + return false; + } +} + +static bool zlib_stream_to_partial_write(const uint8_t* data, uint32_t len, bool final) { + od_zlib_status_t status = od_zlib_stream_push(data, len, final); + if (status == OD_ZLIB_STATUS_ERROR) { + writeSerial(String("partial zlib stream error: ") + od_zlib_stream_error(), true); + return false; + } - if (res != TINF_DONE) return false; - if (bytesOutTotal != partialCtx.expected_stream_size) return false; - directWriteCompressedBuffer = compressedDataBuffer; - directWriteCompressedReceived = bytesOutTotal; - partialCtx.bytes_received = bytesOutTotal; + for (;;) { + size_t bytesOut = 0; + status = od_zlib_stream_poll(decompressionChunk, OPENDISPLAY_DECOMPRESSION_CHUNK_SIZE, &bytesOut); + if (bytesOut > 0 && !partial_write_stream_bytes(decompressionChunk, (uint32_t)bytesOut)) return false; + if (status == OD_ZLIB_STATUS_OUTPUT_READY) continue; + if (status == OD_ZLIB_STATUS_NEEDS_INPUT) return !final; + if (status == OD_ZLIB_STATUS_DONE) return partialCtx.bytes_written == partialCtx.expected_stream_size; + writeSerial(String("partial zlib stream error: ") + od_zlib_stream_error(), true); + return false; + } +} + +static bool partial_write_stream_bytes(uint8_t* data, uint32_t len) { + uint32_t offset = 0; + while (offset < len) { + if (partialCtx.bytes_written >= partialCtx.expected_stream_size) return false; + + uint8_t targetPlane = partialCtx.bytes_written < partialCtx.plane_size ? PLANE_1 : PLANE_0; + if (partialCtx.current_plane != targetPlane) { + if (targetPlane == PLANE_0 && partialCtx.bytes_written != partialCtx.plane_size) return false; + bbepSetAddrWindow(&bbep, partialCtx.x, partialCtx.y, partialCtx.width, partialCtx.height); + bbepStartWrite(&bbep, targetPlane); + partialCtx.current_plane = targetPlane; + } + + uint32_t planeEnd = targetPlane == PLANE_1 ? partialCtx.plane_size : partialCtx.expected_stream_size; + uint32_t chunk = planeEnd - partialCtx.bytes_written; + if (chunk > len - offset) chunk = len - offset; + bbepWriteData(&bbep, data + offset, (int)chunk); + partialCtx.bytes_written += chunk; + offset += chunk; + } return true; } @@ -1565,8 +1565,6 @@ static void partial_prepare_panel_ram(void) { } static bool partial_write_to_panel(int refreshMode) { - if (!compressedDataBuffer) return false; - writeSerial("EPD refresh: PARTIAL (raw rect ", false); writeSerial(String(partialCtx.x), false); writeSerial(",", false); @@ -1577,18 +1575,7 @@ static bool partial_write_to_panel(int refreshMode) { writeSerial(String(partialCtx.height), false); writeSerial(")", true); - if (!displayPowerState) { - pwrmgm(true); - bbepInitIO(&bbep, globalConfig.displays[0].dc_pin, globalConfig.displays[0].reset_pin, globalConfig.displays[0].busy_pin, globalConfig.displays[0].cs_pin, globalConfig.displays[0].data_pin, globalConfig.displays[0].clk_pin, 8000000); - bbepWakeUp(&bbep); - bbepSendCMDSequence(&bbep, bbep.pInitFull); - } - bbepSetAddrWindow(&bbep, partialCtx.x, partialCtx.y, partialCtx.width, partialCtx.height); - bbepStartWrite(&bbep, PLANE_1); - bbepWriteData(&bbep, compressedDataBuffer, partialCtx.plane_size); - bbepSetAddrWindow(&bbep, partialCtx.x, partialCtx.y, partialCtx.width, partialCtx.height); - bbepStartWrite(&bbep, PLANE_0); - bbepWriteData(&bbep, compressedDataBuffer + partialCtx.plane_size, partialCtx.plane_size); + if (partialCtx.bytes_written != partialCtx.expected_stream_size) return false; delay(20); bbepRefresh(&bbep, refreshMode); bool refreshSuccess = waitforrefresh(60); diff --git a/src/display_service.h b/src/display_service.h index d478fbe..52c4403 100644 --- a/src/display_service.h +++ b/src/display_service.h @@ -4,7 +4,7 @@ #include #include -uint32_t max_compressed_image_rx_bytes(uint8_t transmission_modes); +#define OPENDISPLAY_DECOMPRESSION_CHUNK_SIZE 256 bool seeed_driver_used(void); int mapEpd(int id); @@ -26,7 +26,6 @@ void writeTextAndFill(const char* text); void handleDirectWriteStart(uint8_t* data, uint16_t len); void handleDirectWriteData(uint8_t* data, uint16_t len); void handleDirectWriteCompressedData(uint8_t* data, uint16_t len); -void decompressDirectWriteData(); void cleanupDirectWriteState(bool refreshDisplay); void handleDirectWriteEnd(uint8_t* data, uint16_t len); void handlePartialWriteStart(uint8_t* data, uint16_t len); diff --git a/src/main.cpp b/src/main.cpp index 7d7b0c2..0fa5962 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,4 @@ #include "main.h" -#ifdef TARGET_ESP32 -#include -#endif #include "boot_screen.h" #include "communication.h" #include "device_control.h" @@ -20,26 +17,6 @@ static HardwareSerial LogSerialPort(1); #endif -#if defined(TARGET_NRF) -static uint8_t s_compressedDataStorage[MAX_COMPRESSED_BUFFER_BYTES]; -uint8_t* compressedDataBuffer = s_compressedDataStorage; -#elif defined(TARGET_ESP32) && defined(TARGET_LARGE_MEMORY) && defined(BOARD_HAS_PSRAM) -uint8_t* compressedDataBuffer = nullptr; -#else -static uint8_t s_compressedDataStorage[MAX_COMPRESSED_BUFFER_BYTES]; -uint8_t* compressedDataBuffer = s_compressedDataStorage; -#endif - -void allocCompressedDataBuffer(void) { -#if defined(TARGET_ESP32) && defined(TARGET_LARGE_MEMORY) && defined(BOARD_HAS_PSRAM) - if (compressedDataBuffer) return; - compressedDataBuffer = (uint8_t*)heap_caps_malloc(MAX_COMPRESSED_BUFFER_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - if (!compressedDataBuffer) { - compressedDataBuffer = (uint8_t*)heap_caps_malloc(MAX_COMPRESSED_BUFFER_BYTES, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - } -#endif -} - void setup() { #if defined(TARGET_ESP32) && defined(OPENDISPLAY_LOG_UART) LogSerialPort.begin(115200, SERIAL_8N1, OPENDISPLAY_LOG_UART_RX, OPENDISPLAY_LOG_UART_TX); @@ -48,7 +25,6 @@ void setup() { Serial.begin(115200); delay(100); #endif - allocCompressedDataBuffer(); writeSerial("=== FIRMWARE INFO ==="); writeSerial("Firmware Version: " + String(getFirmwareMajor()) + "." + String(getFirmwareMinor())); const char* shaCStr = SHA_STRING; @@ -239,7 +215,6 @@ uint8_t pinToButtonIndex[64] = {0xFF}; // Map pin number to button index (max 6 #ifdef TARGET_ESP32 void minimalSetup() { - allocCompressedDataBuffer(); writeSerial("=== Minimal Setup (Deep Sleep Wake) ==="); full_config_init(); initio(); @@ -520,4 +495,4 @@ bool powerDownExternalFlash(uint8_t mosiPin, uint8_t misoPin, uint8_t sckPin, ui return false; #endif return false; -} \ No newline at end of file +} diff --git a/src/main.h b/src/main.h index 21d19ab..856eff1 100644 --- a/src/main.h +++ b/src/main.h @@ -7,6 +7,7 @@ #include "config_parser.h" #include "ble_init.h" #include "wifi_service.h" +#include "display_service.h" #ifndef BUILD_VERSION #define BUILD_VERSION "0.0" @@ -31,9 +32,6 @@ using namespace Adafruit_LittleFS_Namespace; #include -#define DECOMP_CHUNK 512 -#define DECOMP_CHUNK_SIZE 4096 -#define MAX_DICT_SIZE 32768 #define MAX_BLOCKS 64 // Text rendering constants #define FONT_CHAR_WIDTH 16 // 7 font columns + 1 blank column, each doubled (8*2) @@ -120,11 +118,7 @@ void bbepSendCMDSequence(BBEPDISP *pBBEP, const uint8_t *pSeq); void bbepSetAddrWindow(BBEPDISP *pBBEP, int x, int y, int cx, int cy); void bbepWriteData(BBEPDISP *pBBEP, uint8_t *pData, int iLen); -extern uint8_t* compressedDataBuffer; -void allocCompressedDataBuffer(void); - -uint8_t decompressionChunk[DECOMP_CHUNK_SIZE]; -uint8_t dictionaryBuffer[MAX_DICT_SIZE]; +uint8_t decompressionChunk[OPENDISPLAY_DECOMPRESSION_CHUNK_SIZE]; uint8_t bleResponseBuffer[94]; uint8_t mloopcounter = 0; uint8_t rebootFlag = 1; // Set to 1 after reboot, cleared to 0 after BLE connection @@ -191,11 +185,8 @@ uint16_t directWriteHeight = 0; // Display height in pixels uint32_t directWriteTotalBytes = 0; // Total bytes expected per plane (for bitplanes) or total (for others) uint8_t directWriteRefreshMode = 0; // 0 = FULL (default), 1 = FAST/PARTIAL (if supported) uint8_t directWriteDataKind = 0; // none; display_service.cpp tracks full vs partial 0x71 streams +uint32_t directWriteCompressedReceived = 0; // Total compressed bytes received for diagnostics/overflow guard -// Direct write compressed mode: use same buffer as regular image upload -uint32_t directWriteCompressedSize = 0; // Total compressed size expected -uint32_t directWriteCompressedReceived = 0; // Total compressed bytes received -uint8_t* directWriteCompressedBuffer = nullptr; // Points at compressedDataBuffer when compressed direct-write is active uint32_t directWriteStartTime = 0; // Timestamp when direct write started (for timeout detection) bool displayPowerState = false; // Track display power state (true = powered on, false = powered off) diff --git a/src/structs.h b/src/structs.h index 45f165e..c336f6f 100644 --- a/src/structs.h +++ b/src/structs.h @@ -65,14 +65,7 @@ struct PowerOption { // display.color_scheme (config.yaml); use with matching panel (e.g. gray16 + panel_ic 3001). #define COLOR_SCHEME_GRAY16 6u -// display.transmission_modes (config.yaml bitfield). ZIPXL extends ZIP on builds with a larger buffer. -#if defined(TARGET_ESP32) && defined(TARGET_LARGE_MEMORY) && defined(BOARD_HAS_PSRAM) -#define MAX_COMPRESSED_BUFFER_BYTES (512u * 1024u) -#elif defined(TARGET_ESP32) && defined(TARGET_LARGE_MEMORY) -#define MAX_COMPRESSED_BUFFER_BYTES (256u * 1024u) -#else -#define MAX_COMPRESSED_BUFFER_BYTES (54u * 1024u) -#endif +// display.transmission_modes (config.yaml bitfield). #define TRANSMISSION_MODE_ZIPXL (1u << 0) #define TRANSMISSION_MODE_ZIP (1u << 1) #define TRANSMISSION_MODE_G5 (1u << 2) @@ -280,4 +273,4 @@ struct SecurityConfig { #define SECURITY_FLAG_RESET_PIN_PULLUP (1 << 4) #define SECURITY_FLAG_RESET_PIN_PULLDOWN (1 << 5) -#endif \ No newline at end of file +#endif diff --git a/tools/test_zlib_stream.c b/tools/test_zlib_stream.c new file mode 100644 index 0000000..5c4a0da --- /dev/null +++ b/tools/test_zlib_stream.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include + +#include "uzlib.h" + +static int make_zlib(const uint8_t *src, size_t src_len, int window_bits, int level, int strategy, uint8_t **out, size_t *out_len) { + z_stream zs; + size_t cap = src_len + 1024; + int rc; + memset(&zs, 0, sizeof(zs)); + *out = (uint8_t *)malloc(cap); + if (!*out) return 0; + + rc = deflateInit2(&zs, level, Z_DEFLATED, window_bits, 8, strategy); + if (rc != Z_OK) return 0; + zs.next_in = (Bytef *)src; + zs.avail_in = (uInt)src_len; + zs.next_out = *out; + zs.avail_out = (uInt)cap; + rc = deflate(&zs, Z_FINISH); + if (rc != Z_STREAM_END) { + deflateEnd(&zs); + return 0; + } + *out_len = zs.total_out; + deflateEnd(&zs); + return 1; +} + +static int run_decode_case(const char *name, const uint8_t *compressed, size_t compressed_len, + const uint8_t *expected, size_t expected_len, + size_t input_chunk, size_t output_chunk) { + uint8_t *actual = (uint8_t *)malloc(expected_len ? expected_len : 1); + size_t actual_len = 0; + size_t pos = 0; + bool final_sent = false; + od_zlib_status_t status = OD_ZLIB_STATUS_NEEDS_INPUT; + + if (!actual) return 0; + od_zlib_stream_reset((uint32_t)expected_len); + + while (status != OD_ZLIB_STATUS_DONE) { + if (status == OD_ZLIB_STATUS_NEEDS_INPUT) { + if (pos < compressed_len) { + size_t n = compressed_len - pos; + if (n > input_chunk) n = input_chunk; + status = od_zlib_stream_push(compressed + pos, n, false); + pos += n; + } else if (!final_sent) { + status = od_zlib_stream_push(NULL, 0, true); + final_sent = true; + } else { + fprintf(stderr, "%s: decoder requested input after final\n", name); + free(actual); + return 0; + } + if (status == OD_ZLIB_STATUS_ERROR) { + fprintf(stderr, "%s: push error: %s\n", name, od_zlib_stream_error()); + free(actual); + return 0; + } + } + + for (;;) { + uint8_t outbuf[4096]; + size_t produced = 0; + size_t cap = output_chunk < sizeof(outbuf) ? output_chunk : sizeof(outbuf); + status = od_zlib_stream_poll(outbuf, cap, &produced); + if (produced > 0) { + if (actual_len + produced > expected_len) { + fprintf(stderr, "%s: produced too much output\n", name); + free(actual); + return 0; + } + memcpy(actual + actual_len, outbuf, produced); + actual_len += produced; + } + if (status == OD_ZLIB_STATUS_OUTPUT_READY) continue; + break; + } + + if (status == OD_ZLIB_STATUS_ERROR) { + fprintf(stderr, "%s: poll error: %s (out=%u expected=%zu)\n", name, od_zlib_stream_error(), od_zlib_stream_output_count(), expected_len); + free(actual); + return 0; + } + } + + if (actual_len != expected_len || memcmp(actual, expected, expected_len) != 0) { + fprintf(stderr, "%s: output mismatch (%zu != %zu)\n", name, actual_len, expected_len); + size_t limit = actual_len < expected_len ? actual_len : expected_len; + for (size_t i = 0; i < limit; ++i) { + if (actual[i] != expected[i]) { + fprintf(stderr, "%s: first diff at %zu actual=%02x expected=%02x\n", name, i, actual[i], expected[i]); + break; + } + } + free(actual); + return 0; + } + free(actual); + return 1; +} + +static int expect_error(const char *name, const uint8_t *compressed, size_t compressed_len, size_t expected_len) { + size_t pos = 0; + od_zlib_status_t status = OD_ZLIB_STATUS_NEEDS_INPUT; + od_zlib_stream_reset((uint32_t)expected_len); + while (status != OD_ZLIB_STATUS_DONE) { + if (status == OD_ZLIB_STATUS_NEEDS_INPUT) { + size_t n = compressed_len - pos; + if (n > 3) n = 3; + status = od_zlib_stream_push(pos < compressed_len ? compressed + pos : NULL, pos < compressed_len ? n : 0, pos >= compressed_len); + pos += n; + if (status == OD_ZLIB_STATUS_ERROR) return 1; + } + uint8_t outbuf[7]; + size_t produced = 0; + status = od_zlib_stream_poll(outbuf, sizeof(outbuf), &produced); + if (status == OD_ZLIB_STATUS_ERROR) return 1; + if (pos >= compressed_len && status == OD_ZLIB_STATUS_NEEDS_INPUT) { + status = od_zlib_stream_push(NULL, 0, true); + } + } + fprintf(stderr, "%s: expected error but decode succeeded\n", name); + return 0; +} + +static int expect_error_one_push(const char *name, const uint8_t *compressed, size_t compressed_len, size_t expected_len) { + od_zlib_status_t status; + od_zlib_stream_reset((uint32_t)expected_len); + status = od_zlib_stream_push(compressed, compressed_len, true); + if (status == OD_ZLIB_STATUS_ERROR) return 1; + while (status != OD_ZLIB_STATUS_DONE) { + uint8_t outbuf[4096]; + size_t produced = 0; + status = od_zlib_stream_poll(outbuf, sizeof(outbuf), &produced); + if (status == OD_ZLIB_STATUS_ERROR) return 1; + } + fprintf(stderr, "%s: expected error but decode succeeded\n", name); + return 0; +} + +static void fill_fixture(uint8_t *buf, size_t len) { + for (size_t i = 0; i < len; ++i) { + if ((i % 97) < 64) buf[i] = (uint8_t)('A' + (i % 5)); + else buf[i] = (uint8_t)((i * 37u + i / 3u) & 0xffu); + } +} + +int main(void) { + uint8_t src[8192]; + uint8_t *compressed = NULL; + size_t compressed_len = 0; + int ok = 1; + fill_fixture(src, sizeof(src)); + + struct { + const char *name; + int level; + int strategy; + } cases[] = { + {"stored", 0, Z_DEFAULT_STRATEGY}, + {"fixed", 6, Z_FIXED}, + {"dynamic", 9, Z_DEFAULT_STRATEGY}, + }; + + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + if (!make_zlib(src, sizeof(src), 9, cases[i].level, cases[i].strategy, &compressed, &compressed_len)) { + fprintf(stderr, "failed to create %s fixture\n", cases[i].name); + return 1; + } + ok &= run_decode_case(cases[i].name, compressed, compressed_len, src, sizeof(src), 1, 1); + ok &= run_decode_case(cases[i].name, compressed, compressed_len, src, sizeof(src), 23, 7); + ok &= run_decode_case(cases[i].name, compressed, compressed_len, src, sizeof(src), compressed_len, 4096); + free(compressed); + compressed = NULL; + } + + if (!make_zlib(src, sizeof(src), 10, 6, Z_DEFAULT_STRATEGY, &compressed, &compressed_len)) { + fprintf(stderr, "failed to create ws10 fixture\n"); + return 1; + } + ok &= expect_error("oversized-window", compressed, compressed_len, sizeof(src)); + free(compressed); + + if (!make_zlib(src, sizeof(src), 9, 6, Z_DEFAULT_STRATEGY, &compressed, &compressed_len)) { + fprintf(stderr, "failed to create corrupt fixtures\n"); + return 1; + } + ok &= expect_error("truncated", compressed, compressed_len / 2, sizeof(src)); + compressed[compressed_len - 1] ^= 0x55; + ok &= expect_error("bad-adler", compressed, compressed_len, sizeof(src)); + free(compressed); + + if (!make_zlib(src, sizeof(src), 9, 6, Z_DEFAULT_STRATEGY, &compressed, &compressed_len)) { + fprintf(stderr, "failed to create trailing fixture\n"); + return 1; + } + uint8_t *with_trailing = (uint8_t *)malloc(compressed_len + 1); + if (!with_trailing) return 1; + memcpy(with_trailing, compressed, compressed_len); + with_trailing[compressed_len] = 0xaa; + ok &= expect_error_one_push("trailing-input", with_trailing, compressed_len + 1, sizeof(src)); + free(with_trailing); + free(compressed); + + if (!ok) return 1; + printf("zlib stream tests passed\n"); + return 0; +}