tamp 2.2.4__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tamp-2.2.4 → tamp-2.3.0}/PKG-INFO +9 -8
- {tamp-2.2.4 → tamp-2.3.0}/README.md +8 -7
- {tamp-2.2.4 → tamp-2.3.0}/build.py +5 -0
- {tamp-2.2.4 → tamp-2.3.0}/pyproject.toml +3 -3
- {tamp-2.2.4 → tamp-2.3.0}/tamp/__init__.py +1 -1
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_compressor.pyx +16 -8
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_decompressor.pyx +21 -4
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_src/tamp/compressor.c +71 -8
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_src/tamp/compressor_find_match_desktop.c +6 -2
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_src/tamp/decompressor.c +33 -11
- {tamp-2.2.4 → tamp-2.3.0}/tamp/cli/build_dictionary.py +27 -2
- {tamp-2.2.4 → tamp-2.3.0}/tamp/compressor.py +20 -5
- {tamp-2.2.4 → tamp-2.3.0}/tamp/compressor_viper.py +12 -7
- {tamp-2.2.4 → tamp-2.3.0}/tamp/decompressor.py +28 -6
- {tamp-2.2.4 → tamp-2.3.0}/tamp/decompressor_viper.py +8 -0
- {tamp-2.2.4 → tamp-2.3.0}/LICENSE +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/__init__.pyi +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/__main__.py +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_build_dictionary.pyx +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_common.pxd +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_common.pyx +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_src/tamp/common.c +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_src/tamp/common.h +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_src/tamp/compressor.h +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/_c_src/tamp/decompressor.h +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/cli/__init__.py +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/cli/main.py +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/ctamp.pxd +0 -0
- {tamp-2.2.4 → tamp-2.3.0}/tamp/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tamp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary:
|
|
5
5
|
Home-page: https://github.com/BrianPugh/tamp
|
|
6
6
|
License: Apache-2.0
|
|
@@ -343,13 +343,14 @@ low-memory compressor.
|
|
|
343
343
|
|
|
344
344
|
To give an idea of Tamp's speed on an embedded device, the following table shows
|
|
345
345
|
compression/decompression in **bytes/second of the first 100KB of enwik8 on a pi
|
|
346
|
-
pico (rp2040)** at the default 125MHz clock rate
|
|
346
|
+
pico (rp2040)** at the default 125MHz clock rate, with native-module
|
|
347
|
+
measurements taken on MicroPython v1.26.1 firmware. The C benchmark **does not**
|
|
347
348
|
use a filesystem nor dynamic memory allocation, so it represents the maximum
|
|
348
349
|
speed Tamp can achieve. In all tests, a 1KB window (10 bit) was used.
|
|
349
350
|
|
|
350
351
|
| | Compression (bytes/s) | Decompression (bytes/s) |
|
|
351
352
|
| -------------------------------- | --------------------- | ----------------------- |
|
|
352
|
-
| Tamp (Micropython Native Module) |
|
|
353
|
+
| Tamp (Micropython Native Module) | 34,510 | 980,392 |
|
|
353
354
|
| Tamp (C) | 36,127 | 1,400,600 |
|
|
354
355
|
| Deflate (micropython builtin) | 6,885 | 294,985 |
|
|
355
356
|
|
|
@@ -365,11 +366,11 @@ Numbers reported in bytes. Tamp sizes were measured using `arm-none-eabi-gcc`
|
|
|
365
366
|
|
|
366
367
|
| | Compressor | Decompressor | Compressor + Decompressor |
|
|
367
368
|
| -------------------------------- | ---------- | ------------ | ------------------------- |
|
|
368
|
-
| Tamp (MicroPython Native) |
|
|
369
|
-
| Tamp (C, no extended, no stream) |
|
|
370
|
-
| Tamp (C, no extended) |
|
|
371
|
-
| Tamp (C, extended, no stream) |
|
|
372
|
-
| Tamp (C, extended) |
|
|
369
|
+
| Tamp (MicroPython Native) | 5469 | 4725 | 9125 |
|
|
370
|
+
| Tamp (C, no extended, no stream) | 1932 | 1634 | 3328 |
|
|
371
|
+
| Tamp (C, no extended) | 2214 | 1872 | 3848 |
|
|
372
|
+
| Tamp (C, extended, no stream) | 3120 | 2452 | 5334 |
|
|
373
|
+
| Tamp (C, extended) | 3402 | 2690 | 5854 |
|
|
373
374
|
| Heatshrink (C) | 2956 | 3876 | 6832 |
|
|
374
375
|
| uzlib (C) | 2355 | 3963 | 6318 |
|
|
375
376
|
|
|
@@ -322,13 +322,14 @@ low-memory compressor.
|
|
|
322
322
|
|
|
323
323
|
To give an idea of Tamp's speed on an embedded device, the following table shows
|
|
324
324
|
compression/decompression in **bytes/second of the first 100KB of enwik8 on a pi
|
|
325
|
-
pico (rp2040)** at the default 125MHz clock rate
|
|
325
|
+
pico (rp2040)** at the default 125MHz clock rate, with native-module
|
|
326
|
+
measurements taken on MicroPython v1.26.1 firmware. The C benchmark **does not**
|
|
326
327
|
use a filesystem nor dynamic memory allocation, so it represents the maximum
|
|
327
328
|
speed Tamp can achieve. In all tests, a 1KB window (10 bit) was used.
|
|
328
329
|
|
|
329
330
|
| | Compression (bytes/s) | Decompression (bytes/s) |
|
|
330
331
|
| -------------------------------- | --------------------- | ----------------------- |
|
|
331
|
-
| Tamp (Micropython Native Module) |
|
|
332
|
+
| Tamp (Micropython Native Module) | 34,510 | 980,392 |
|
|
332
333
|
| Tamp (C) | 36,127 | 1,400,600 |
|
|
333
334
|
| Deflate (micropython builtin) | 6,885 | 294,985 |
|
|
334
335
|
|
|
@@ -344,11 +345,11 @@ Numbers reported in bytes. Tamp sizes were measured using `arm-none-eabi-gcc`
|
|
|
344
345
|
|
|
345
346
|
| | Compressor | Decompressor | Compressor + Decompressor |
|
|
346
347
|
| -------------------------------- | ---------- | ------------ | ------------------------- |
|
|
347
|
-
| Tamp (MicroPython Native) |
|
|
348
|
-
| Tamp (C, no extended, no stream) |
|
|
349
|
-
| Tamp (C, no extended) |
|
|
350
|
-
| Tamp (C, extended, no stream) |
|
|
351
|
-
| Tamp (C, extended) |
|
|
348
|
+
| Tamp (MicroPython Native) | 5469 | 4725 | 9125 |
|
|
349
|
+
| Tamp (C, no extended, no stream) | 1932 | 1634 | 3328 |
|
|
350
|
+
| Tamp (C, no extended) | 2214 | 1872 | 3848 |
|
|
351
|
+
| Tamp (C, extended, no stream) | 3120 | 2452 | 5334 |
|
|
352
|
+
| Tamp (C, extended) | 3402 | 2690 | 5854 |
|
|
352
353
|
| Heatshrink (C) | 2956 | 3876 | 6832 |
|
|
353
354
|
| uzlib (C) | 2355 | 3963 | 6318 |
|
|
354
355
|
|
|
@@ -20,6 +20,11 @@ if profile and sanitize:
|
|
|
20
20
|
"Please choose either TAMP_PROFILE=1 or TAMP_SANITIZE=1, not both."
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
+
# Sanitizer flags below are UNIX-only; previously this path crashed with a
|
|
24
|
+
# NameError (extra_link_args unbound) instead of a clear message.
|
|
25
|
+
if sanitize and os.name == "nt":
|
|
26
|
+
raise ValueError("TAMP_SANITIZE=1 is not supported on Windows.")
|
|
27
|
+
|
|
23
28
|
|
|
24
29
|
def build_cython_extensions():
|
|
25
30
|
import Cython.Compiler.Options
|
|
@@ -9,7 +9,7 @@ style = "semver"
|
|
|
9
9
|
|
|
10
10
|
[tool.poetry]
|
|
11
11
|
name = "tamp"
|
|
12
|
-
version = "2.
|
|
12
|
+
version = "2.3.0" # Do not change, let poetry-dynamic-versioning handle it.
|
|
13
13
|
homepage = "https://github.com/BrianPugh/tamp"
|
|
14
14
|
repository = "https://github.com/BrianPugh/tamp"
|
|
15
15
|
license = "Apache-2.0"
|
|
@@ -56,12 +56,12 @@ pytest-cov = ">=3.0.0"
|
|
|
56
56
|
pytest-mock = ">=3.7.0"
|
|
57
57
|
black = ">=23.1.0"
|
|
58
58
|
belay = ">=0.21.3"
|
|
59
|
-
tqdm = ">=4.
|
|
59
|
+
tqdm = ">=4.68.2"
|
|
60
60
|
matplotlib = ">=3.7.4"
|
|
61
61
|
ipdb = ">=0.13.13"
|
|
62
62
|
packaging = ">=26.1"
|
|
63
63
|
mpremote = ">=1.20.0"
|
|
64
|
-
cython = ">=3.2.
|
|
64
|
+
cython = ">=3.2.8"
|
|
65
65
|
pytest-subtests = ">=0.10.0"
|
|
66
66
|
numpy = ">=1.24"
|
|
67
67
|
pyelftools = "^0.32"
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import builtins
|
|
2
1
|
cimport ctamp
|
|
3
2
|
from libcpp cimport bool
|
|
4
3
|
from cpython.exc cimport PyErr_CheckSignals
|
|
5
4
|
from cpython.mem cimport PyMem_Malloc, PyMem_Free
|
|
6
5
|
from libc.stddef cimport size_t
|
|
7
6
|
from io import BytesIO
|
|
8
|
-
from . import bit_size
|
|
9
7
|
from ._c_common cimport CHUNK_SIZE
|
|
10
8
|
from ._c_common import ERROR_LOOKUP
|
|
11
9
|
|
|
@@ -42,7 +40,9 @@ cdef class Compressor:
|
|
|
42
40
|
):
|
|
43
41
|
cdef ctamp.TampConf conf
|
|
44
42
|
|
|
45
|
-
if dictionary and
|
|
43
|
+
if dictionary is not None and len(dictionary) != (1 << window):
|
|
44
|
+
# The exact size is required: a too-small buffer used as the window
|
|
45
|
+
# would let the C compressor write out of bounds.
|
|
46
46
|
raise ValueError("Dictionary-window size mismatch.")
|
|
47
47
|
|
|
48
48
|
if not hasattr(f, "write"): # It's probably a path-like object.
|
|
@@ -55,7 +55,7 @@ cdef class Compressor:
|
|
|
55
55
|
|
|
56
56
|
conf.window = window
|
|
57
57
|
conf.literal = literal
|
|
58
|
-
conf.use_custom_dictionary =
|
|
58
|
+
conf.use_custom_dictionary = dictionary is not None
|
|
59
59
|
# Set lazy_matching - this field is conditionally compiled based on TAMP_LAZY_MATCHING
|
|
60
60
|
# The build system defines this macro, so the field should be available
|
|
61
61
|
conf.lazy_matching = lazy_matching
|
|
@@ -63,7 +63,7 @@ cdef class Compressor:
|
|
|
63
63
|
conf.dictionary_reset = dictionary_reset
|
|
64
64
|
conf.append = append
|
|
65
65
|
|
|
66
|
-
self._window_buffer = dictionary if dictionary else bytearray(1 << window)
|
|
66
|
+
self._window_buffer = dictionary if dictionary is not None else bytearray(1 << window)
|
|
67
67
|
self._window_buffer_ptr = <unsigned char *>self._window_buffer
|
|
68
68
|
|
|
69
69
|
self._dictionary_reset = dictionary_reset
|
|
@@ -75,15 +75,23 @@ cdef class Compressor:
|
|
|
75
75
|
cdef:
|
|
76
76
|
ctamp.tamp_res res
|
|
77
77
|
|
|
78
|
-
bytearray output_buffer
|
|
79
|
-
unsigned char *output_buffer_ptr
|
|
80
|
-
const unsigned char* data_ptr
|
|
78
|
+
bytearray output_buffer
|
|
79
|
+
unsigned char *output_buffer_ptr
|
|
80
|
+
const unsigned char* data_ptr
|
|
81
81
|
|
|
82
82
|
size_t input_consumed_size = 0
|
|
83
83
|
size_t input_remaining_size = data.shape[0]
|
|
84
84
|
size_t output_buffer_written_size
|
|
85
85
|
int written_to_disk_size = 0
|
|
86
86
|
|
|
87
|
+
if input_remaining_size == 0:
|
|
88
|
+
# &data[0] on an empty buffer raises IndexError.
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
output_buffer = bytearray(CHUNK_SIZE)
|
|
92
|
+
output_buffer_ptr = output_buffer
|
|
93
|
+
data_ptr = &data[0]
|
|
94
|
+
|
|
87
95
|
output_buffer_mv = memoryview(output_buffer)
|
|
88
96
|
|
|
89
97
|
while input_remaining_size:
|
|
@@ -3,7 +3,6 @@ from cpython.exc cimport PyErr_CheckSignals
|
|
|
3
3
|
from cpython.mem cimport PyMem_Malloc, PyMem_Free
|
|
4
4
|
from libc.stddef cimport size_t
|
|
5
5
|
from io import BytesIO
|
|
6
|
-
from . import bit_size
|
|
7
6
|
from ._c_common cimport CHUNK_SIZE
|
|
8
7
|
from ._c_common import ERROR_LOOKUP
|
|
9
8
|
|
|
@@ -64,7 +63,13 @@ cdef class Decompressor:
|
|
|
64
63
|
if conf.use_custom_dictionary and dictionary is None:
|
|
65
64
|
raise ValueError
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
if dictionary is not None and len(dictionary) < (1 << conf.window):
|
|
67
|
+
# A too-small buffer used as the window would let the C
|
|
68
|
+
# decompressor write out of bounds. An oversized buffer is fine:
|
|
69
|
+
# C uses only the first 2**window bytes, in place.
|
|
70
|
+
raise ValueError("Dictionary-window size mismatch.")
|
|
71
|
+
|
|
72
|
+
self._window_buffer = dictionary if dictionary is not None else bytearray(1 << conf.window)
|
|
68
73
|
self._window_buffer_ptr = <unsigned char *>self._window_buffer
|
|
69
74
|
|
|
70
75
|
res = ctamp.tamp_decompressor_init(self._c_decompressor, &conf, self._window_buffer_ptr, conf.window)
|
|
@@ -105,7 +110,17 @@ cdef class Decompressor:
|
|
|
105
110
|
|
|
106
111
|
if res == ctamp.TAMP_INPUT_EXHAUSTED:
|
|
107
112
|
# Read in more data
|
|
108
|
-
|
|
113
|
+
if hasattr(self.f, "readinto"):
|
|
114
|
+
self.input_size = self.f.readinto(self.input_buffer)
|
|
115
|
+
else:
|
|
116
|
+
# Fall back for file-like objects that only implement read().
|
|
117
|
+
chunk = self.f.read(CHUNK_SIZE)
|
|
118
|
+
self.input_size = len(chunk)
|
|
119
|
+
if self.input_size > <size_t>CHUNK_SIZE:
|
|
120
|
+
# A larger chunk would resize (reallocate) input_buffer,
|
|
121
|
+
# leaving input_buffer_ptr dangling.
|
|
122
|
+
raise ValueError("read() returned more bytes than requested.")
|
|
123
|
+
self.input_buffer[:self.input_size] = chunk
|
|
109
124
|
self.input_consumed = 0
|
|
110
125
|
if self.input_size == 0:
|
|
111
126
|
break;
|
|
@@ -137,7 +152,9 @@ cdef class Decompressor:
|
|
|
137
152
|
# decompression happens inside of readinto
|
|
138
153
|
read_size = self.readinto(buf)
|
|
139
154
|
if size > 0:
|
|
140
|
-
# We one-shot the decompression
|
|
155
|
+
# We one-shot the decompression.
|
|
156
|
+
# Trim unwritten zero-padding when the stream ended early.
|
|
157
|
+
del buf[read_size:]
|
|
141
158
|
out.append(buf)
|
|
142
159
|
break
|
|
143
160
|
# We are decompressing in chunks
|
|
@@ -121,8 +121,29 @@ static TAMP_NOINLINE void find_best_match(TampCompressor* compressor, uint16_t*
|
|
|
121
121
|
const uint8_t max_pattern_size = MIN(compressor->input_size, MAX_PATTERN_SIZE);
|
|
122
122
|
const unsigned char* window = compressor->window;
|
|
123
123
|
|
|
124
|
+
#if defined(__GNUC__)
|
|
125
|
+
// Word-at-a-time skip over bytes that can't start a match: ~3 cycles/byte
|
|
126
|
+
// instead of ~9 on Cortex-M0+, and the scan dominates compression time.
|
|
127
|
+
const uint32_t first_broadcast = first_byte * 0x01010101u;
|
|
128
|
+
#endif
|
|
129
|
+
|
|
124
130
|
for (uint32_t window_index = 0; window_index < window_size_minus_1; window_index++) {
|
|
125
131
|
if (TAMP_LIKELY(window[window_index] != first_byte)) {
|
|
132
|
+
#if defined(__GNUC__)
|
|
133
|
+
uint32_t wi = window_index + 1;
|
|
134
|
+
if (TAMP_LIKELY(((uintptr_t)(window + wi) & 3) == 0)) {
|
|
135
|
+
// Aligned: skip 4 bytes per iteration until a word may hold
|
|
136
|
+
// first_byte (zero-byte detect on the XOR; false positives
|
|
137
|
+
// are fine, the byte loop re-checks).
|
|
138
|
+
typedef uint32_t __attribute__((may_alias)) tamp_word_alias;
|
|
139
|
+
while (wi + 4 <= window_size_minus_1) {
|
|
140
|
+
uint32_t word = *(const tamp_word_alias*)(window + wi) ^ first_broadcast;
|
|
141
|
+
if ((word - 0x01010101u) & ~word & 0x80808080u) break;
|
|
142
|
+
wi += 4;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
window_index = wi - 1; // loop increment advances to wi
|
|
146
|
+
#endif
|
|
126
147
|
continue;
|
|
127
148
|
}
|
|
128
149
|
if (TAMP_LIKELY(window[window_index + 1] != second_byte)) {
|
|
@@ -267,6 +288,12 @@ static TAMP_NOINLINE TAMP_OPTIMIZE_SIZE uint8_t get_last_window_byte(TampCompres
|
|
|
267
288
|
* @param[out] new_pos Position of found match (only valid if new_count > current_count)
|
|
268
289
|
* @param[out] new_count Length of found match
|
|
269
290
|
*/
|
|
291
|
+
#if TAMP_ESP32
|
|
292
|
+
/* ESP32-optimized implementation in espidf/tamp/compressor_esp32.cpp (same contract,
|
|
293
|
+
* but may return a different equally-long match position on ties). */
|
|
294
|
+
extern void find_extended_match(TampCompressor* compressor, uint16_t current_pos, uint8_t current_count,
|
|
295
|
+
uint16_t* new_pos, uint8_t* new_count);
|
|
296
|
+
#else
|
|
270
297
|
static TAMP_NOINLINE TAMP_OPTIMIZE_SIZE void find_extended_match(TampCompressor* compressor, uint16_t current_pos,
|
|
271
298
|
uint8_t current_count, uint16_t* new_pos,
|
|
272
299
|
uint8_t* new_count) {
|
|
@@ -304,6 +331,7 @@ static TAMP_NOINLINE TAMP_OPTIMIZE_SIZE void find_extended_match(TampCompressor*
|
|
|
304
331
|
}
|
|
305
332
|
}
|
|
306
333
|
}
|
|
334
|
+
#endif // TAMP_ESP32
|
|
307
335
|
|
|
308
336
|
/**
|
|
309
337
|
* @brief Write RLE token to bit buffer and update window.
|
|
@@ -396,13 +424,20 @@ static TAMP_NOINLINE tamp_res write_extended_match_token(TampCompressor* compres
|
|
|
396
424
|
* significant code size on register-constrained Cortex-M0+ where the compiler
|
|
397
425
|
* otherwise spills heavily to stack (~48 bytes saved on armv6m).
|
|
398
426
|
*
|
|
427
|
+
* @param[out] match_index Pre-computed match position; only set when a match was
|
|
428
|
+
* already found while deciding RLE-vs-pattern (see match_size).
|
|
429
|
+
* @param[out] match_size Set (>= 2) together with match_index when returning
|
|
430
|
+
* TAMP_POLL_CONTINUE with a match already found, so the caller can skip
|
|
431
|
+
* its own find_best_match. Left untouched otherwise.
|
|
432
|
+
*
|
|
399
433
|
* @return TAMP_OK if fully handled (caller should return TAMP_OK),
|
|
400
434
|
* TAMP_POLL_CONTINUE if caller should proceed to normal pattern matching,
|
|
401
435
|
* other tamp_res on error.
|
|
402
436
|
*/
|
|
403
437
|
static TAMP_NOINLINE TAMP_OPTIMIZE_SIZE tamp_res poll_extended_handling(TampCompressor* compressor,
|
|
404
438
|
unsigned char** output, size_t* output_size,
|
|
405
|
-
size_t* output_written_size
|
|
439
|
+
size_t* output_written_size,
|
|
440
|
+
uint16_t* match_index, uint8_t* match_size) {
|
|
406
441
|
// Handle extended match continuation
|
|
407
442
|
if (compressor->extended_match_count) {
|
|
408
443
|
const uint8_t max_ext_match = compressor->min_pattern_size + 11 + EXTENDED_MATCH_MAX_EXTRA;
|
|
@@ -460,6 +495,9 @@ static TAMP_NOINLINE TAMP_OPTIMIZE_SIZE tamp_res poll_extended_handling(TampComp
|
|
|
460
495
|
|
|
461
496
|
if (pattern_size > total_rle) {
|
|
462
497
|
compressor->rle_count = 0;
|
|
498
|
+
// Hand the match to the caller so it doesn't repeat the search.
|
|
499
|
+
*match_index = pattern_index;
|
|
500
|
+
*match_size = pattern_size;
|
|
463
501
|
return TAMP_POLL_CONTINUE; // Proceed to pattern matching
|
|
464
502
|
}
|
|
465
503
|
}
|
|
@@ -471,7 +509,18 @@ static TAMP_NOINLINE TAMP_OPTIMIZE_SIZE tamp_res poll_extended_handling(TampComp
|
|
|
471
509
|
return TAMP_OK;
|
|
472
510
|
}
|
|
473
511
|
|
|
474
|
-
if (
|
|
512
|
+
if (TAMP_UNLIKELY(compressor->rle_count == 1)) {
|
|
513
|
+
// A lone run byte was consumed into rle_count by a previous poll and the
|
|
514
|
+
// run then ended; RLE tokens require count >= 2 and the byte is no longer
|
|
515
|
+
// in the input buffer, so re-emit it as a literal (mirrors the
|
|
516
|
+
// rle_count == 1 drain in tamp_compressor_flush).
|
|
517
|
+
const uint16_t window_mask = (1 << compressor->conf.window) - 1;
|
|
518
|
+
write_to_bit_buffer(compressor, IS_LITERAL_FLAG | last_byte, compressor->conf.literal + 1);
|
|
519
|
+
compressor->window[compressor->window_pos] = last_byte;
|
|
520
|
+
compressor->window_pos = (compressor->window_pos + 1) & window_mask;
|
|
521
|
+
compressor->rle_count = 0;
|
|
522
|
+
return TAMP_OK;
|
|
523
|
+
}
|
|
475
524
|
return TAMP_POLL_CONTINUE; // Proceed to pattern matching
|
|
476
525
|
}
|
|
477
526
|
#endif // TAMP_EXTENDED_COMPRESS
|
|
@@ -509,9 +558,17 @@ TAMP_NOINLINE tamp_res tamp_compressor_poll(TampCompressor* compressor, unsigned
|
|
|
509
558
|
|
|
510
559
|
#if TAMP_EXTENDED_COMPRESS
|
|
511
560
|
if (TAMP_UNLIKELY(compressor->conf.extended)) {
|
|
512
|
-
// Handle extended match continuation + RLE (outlined for code size)
|
|
513
|
-
|
|
514
|
-
|
|
561
|
+
// Handle extended match continuation + RLE (outlined for code size).
|
|
562
|
+
// May pre-compute match_index/match_size (match_size becomes non-zero).
|
|
563
|
+
res = poll_extended_handling(compressor, &output, &output_size, output_written_size, &match_index, &match_size);
|
|
564
|
+
if (res != TAMP_POLL_CONTINUE) {
|
|
565
|
+
#if TAMP_LAZY_MATCHING
|
|
566
|
+
// Extended handling consumed input and/or mutated the window, so any
|
|
567
|
+
// match cached by lazy matching no longer matches the current input.
|
|
568
|
+
compressor->cached_match_index = -1;
|
|
569
|
+
#endif
|
|
570
|
+
return res;
|
|
571
|
+
}
|
|
515
572
|
// TAMP_POLL_CONTINUE: proceed to pattern matching below
|
|
516
573
|
}
|
|
517
574
|
#endif // TAMP_EXTENDED_COMPRESS
|
|
@@ -523,7 +580,7 @@ TAMP_NOINLINE tamp_res tamp_compressor_poll(TampCompressor* compressor, unsigned
|
|
|
523
580
|
match_index = compressor->cached_match_index;
|
|
524
581
|
match_size = compressor->cached_match_size;
|
|
525
582
|
compressor->cached_match_index = -1; // Clear cache after using
|
|
526
|
-
} else {
|
|
583
|
+
} else if (match_size == 0) { // No match pre-computed by extended handling
|
|
527
584
|
find_best_match(compressor, &match_index, &match_size);
|
|
528
585
|
}
|
|
529
586
|
|
|
@@ -557,11 +614,11 @@ TAMP_NOINLINE tamp_res tamp_compressor_poll(TampCompressor* compressor, unsigned
|
|
|
557
614
|
} else {
|
|
558
615
|
compressor->cached_match_index = -1; // Clear cache
|
|
559
616
|
}
|
|
560
|
-
} else {
|
|
617
|
+
} else if (match_size == 0) { // No match pre-computed by extended handling
|
|
561
618
|
find_best_match(compressor, &match_index, &match_size);
|
|
562
619
|
}
|
|
563
620
|
#else
|
|
564
|
-
find_best_match(compressor, &match_index, &match_size);
|
|
621
|
+
if (match_size == 0) find_best_match(compressor, &match_index, &match_size);
|
|
565
622
|
#endif
|
|
566
623
|
|
|
567
624
|
// Shared token/literal writing logic
|
|
@@ -825,6 +882,12 @@ TAMP_OPTIMIZE_SIZE tamp_res tamp_compressor_reset_dictionary(TampCompressor* com
|
|
|
825
882
|
|
|
826
883
|
#if TAMP_STREAM
|
|
827
884
|
|
|
885
|
+
#if TAMP_EXTENDED_COMPRESS && (TAMP_STREAM_WORK_BUFFER_SIZE / 2) < EXTENDED_MATCH_MIN_OUTPUT_BYTES
|
|
886
|
+
// A smaller buffer deadlocks tamp_compress_stream: write_extended_match_token
|
|
887
|
+
// returns TAMP_OUTPUT_FULL forever because the token can never fit.
|
|
888
|
+
#error "TAMP_STREAM_WORK_BUFFER_SIZE must be at least 12 bytes when extended compression is enabled"
|
|
889
|
+
#endif
|
|
890
|
+
|
|
828
891
|
TAMP_OPTIMIZE_SIZE tamp_res tamp_compress_stream(TampCompressor* compressor, tamp_read_t read_cb, void* read_handle,
|
|
829
892
|
tamp_write_t write_cb, void* write_handle, size_t* input_consumed_size,
|
|
830
893
|
size_t* output_written_size, tamp_callback_t callback,
|
|
@@ -88,8 +88,12 @@ static inline void find_best_match(TampCompressor *compressor, uint16_t *match_i
|
|
|
88
88
|
const uint8_t max_pattern_size = MIN(compressor->input_size, MAX_PATTERN_SIZE);
|
|
89
89
|
const unsigned char *window = compressor->window;
|
|
90
90
|
|
|
91
|
-
// Pre-load input bytes into linear array to avoid repeated modular arithmetic
|
|
92
|
-
|
|
91
|
+
// Pre-load input bytes into linear array to avoid repeated modular arithmetic.
|
|
92
|
+
// Zero-initialized: entries at max_pattern_size and beyond are read when
|
|
93
|
+
// assembling input_word_ext below. Their values never affect the result (the
|
|
94
|
+
// max_pattern_size >= 10 guard in EXTEND_MATCH sees to that), but reading
|
|
95
|
+
// indeterminate bytes is UB (MSan finding, -Werror=maybe-uninitialized).
|
|
96
|
+
uint8_t input_bytes[16] = {0};
|
|
93
97
|
for (uint8_t i = 0; i < max_pattern_size && i < 16; i++) {
|
|
94
98
|
input_bytes[i] = read_input(i);
|
|
95
99
|
}
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
#include "common.h"
|
|
4
4
|
|
|
5
|
+
/* Copy primitives for the hot paths. Platform-specific implementations live
|
|
6
|
+
* with their platform component; TAMP_ESP32 builds already require the espidf
|
|
7
|
+
* component sources on the include path (same contract as the extern
|
|
8
|
+
* find_best_match in compressor.c). On generic targets the macros expand to
|
|
9
|
+
* the plain byte loops / tamp_window_copy call they replaced (macros, not
|
|
10
|
+
* inline functions: Cortex-M0+ codegen is sensitive to function boundaries). */
|
|
11
|
+
#if TAMP_ESP32
|
|
12
|
+
#include "private/tamp_copy.h"
|
|
13
|
+
#else
|
|
14
|
+
|
|
15
|
+
/* Copy count bytes from src to the output cursor and advance it. */
|
|
16
|
+
#define TAMP_COPY_TO_OUTPUT(out, src, count) \
|
|
17
|
+
do { \
|
|
18
|
+
for (uint8_t _tamp_i = 0; _tamp_i < (count); _tamp_i++) { \
|
|
19
|
+
*(out)++ = (src)[_tamp_i]; \
|
|
20
|
+
} \
|
|
21
|
+
} while (0)
|
|
22
|
+
|
|
23
|
+
#define TAMP_WINDOW_COPY(window, window_pos, window_offset, match_size, window_mask) \
|
|
24
|
+
tamp_window_copy((window), (window_pos), (window_offset), (match_size), (window_mask))
|
|
25
|
+
|
|
26
|
+
#endif /* TAMP_ESP32 */
|
|
27
|
+
|
|
5
28
|
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
|
|
6
29
|
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
|
7
30
|
|
|
@@ -233,9 +256,7 @@ static tamp_res decode_extended_match(TampDecompressor* d, unsigned char** outpu
|
|
|
233
256
|
|
|
234
257
|
/* Copy from window to output */
|
|
235
258
|
uint16_t src_offset = window_offset + skip;
|
|
236
|
-
|
|
237
|
-
*(*output)++ = d->window[src_offset + i];
|
|
238
|
-
}
|
|
259
|
+
TAMP_COPY_TO_OUTPUT(*output, d->window + src_offset, to_write);
|
|
239
260
|
*output_written_size += to_write;
|
|
240
261
|
|
|
241
262
|
/* Update window only on complete decode.
|
|
@@ -244,7 +265,7 @@ static tamp_res decode_extended_match(TampDecompressor* d, unsigned char** outpu
|
|
|
244
265
|
uint16_t wp = d->window_pos;
|
|
245
266
|
uint16_t remaining = window_size - wp;
|
|
246
267
|
uint8_t window_write = (match_size < remaining) ? match_size : remaining;
|
|
247
|
-
|
|
268
|
+
TAMP_WINDOW_COPY(d->window, &wp, window_offset, window_write, window_size - 1);
|
|
248
269
|
d->window_pos = wp;
|
|
249
270
|
}
|
|
250
271
|
|
|
@@ -288,6 +309,12 @@ static TAMP_OPTIMIZE_SIZE tamp_res tamp_decompressor_populate_from_conf(TampDeco
|
|
|
288
309
|
if (conf_window < 8 || conf_window > 15) return TAMP_INVALID_CONF;
|
|
289
310
|
if (conf_literal < 5 || conf_literal > 8) return TAMP_INVALID_CONF;
|
|
290
311
|
if (conf_window > decompressor->window_bits_max) return TAMP_INVALID_CONF;
|
|
312
|
+
#if !TAMP_EXTENDED_DECOMPRESS
|
|
313
|
+
// Reject before committing any state: marking the decompressor configured and
|
|
314
|
+
// then returning an error would let a retrying caller decode the extended
|
|
315
|
+
// stream as classic format.
|
|
316
|
+
if (conf_extended) return TAMP_INVALID_CONF; // Extended stream but extended support not compiled in
|
|
317
|
+
#endif
|
|
291
318
|
if (!conf_use_custom_dictionary)
|
|
292
319
|
tamp_initialize_dictionary(decompressor->window, (size_t)1 << conf_window, conf_extended ? conf_literal : 8);
|
|
293
320
|
|
|
@@ -297,9 +324,6 @@ static TAMP_OPTIMIZE_SIZE tamp_res tamp_decompressor_populate_from_conf(TampDeco
|
|
|
297
324
|
decompressor->configured = true;
|
|
298
325
|
decompressor->conf_extended = conf_extended;
|
|
299
326
|
decompressor->conf_dictionary_reset = conf_dictionary_reset;
|
|
300
|
-
#if !TAMP_EXTENDED_DECOMPRESS
|
|
301
|
-
if (conf_extended) return TAMP_INVALID_CONF; // Extended stream but extended support not compiled in
|
|
302
|
-
#endif
|
|
303
327
|
|
|
304
328
|
return TAMP_OK;
|
|
305
329
|
}
|
|
@@ -538,14 +562,12 @@ tamp_res tamp_decompressor_decompress_cb(TampDecompressor* decompressor, unsigne
|
|
|
538
562
|
}
|
|
539
563
|
|
|
540
564
|
// Copy pattern to output
|
|
541
|
-
|
|
542
|
-
*output++ = decompressor->window[window_offset_skip + i];
|
|
543
|
-
}
|
|
565
|
+
TAMP_COPY_TO_OUTPUT(output, decompressor->window + window_offset_skip, match_size_skip);
|
|
544
566
|
(*output_written_size) += match_size_skip;
|
|
545
567
|
|
|
546
568
|
if (TAMP_LIKELY(decompressor->skip_bytes == 0)) {
|
|
547
569
|
uint16_t wp = decompressor->window_pos;
|
|
548
|
-
|
|
570
|
+
TAMP_WINDOW_COPY(decompressor->window, &wp, window_offset, match_size, window_mask);
|
|
549
571
|
decompressor->window_pos = wp;
|
|
550
572
|
}
|
|
551
573
|
}
|
|
@@ -7,8 +7,30 @@ from typing import Annotated, Optional
|
|
|
7
7
|
from cyclopts import Parameter, validators
|
|
8
8
|
|
|
9
9
|
import tamp
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
|
|
11
|
+
_MISSING_KERNELS_MESSAGE = (
|
|
12
|
+
"Building dictionaries requires tamp's compiled C extensions, which are not available in this installation."
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from tamp._c_build_dictionary import score_and_multi_frag, select_candidates
|
|
17
|
+
from tamp._c_build_dictionary import score_substrings as _c_score_substrings
|
|
18
|
+
|
|
19
|
+
_KERNELS_AVAILABLE = True
|
|
20
|
+
except ImportError as e:
|
|
21
|
+
# C extensions unavailable (e.g. TAMP_BUILD_C_EXTENSIONS=0). Importing this
|
|
22
|
+
# module must still succeed so the rest of the tamp CLI works; only
|
|
23
|
+
# dictionary building itself requires the compiled kernels.
|
|
24
|
+
_KERNELS_AVAILABLE = False
|
|
25
|
+
_IMPORT_ERROR = e
|
|
26
|
+
|
|
27
|
+
def _missing_kernels(*args, **kwargs):
|
|
28
|
+
raise RuntimeError(_MISSING_KERNELS_MESSAGE) from _IMPORT_ERROR
|
|
29
|
+
|
|
30
|
+
score_and_multi_frag = _missing_kernels
|
|
31
|
+
select_candidates = _missing_kernels
|
|
32
|
+
_c_score_substrings = _missing_kernels
|
|
33
|
+
|
|
12
34
|
from tamp.compressor import (
|
|
13
35
|
_EXTENDED_MATCH_SYMBOL,
|
|
14
36
|
_LEADING_EXTENDED_MATCH_HUFFMAN_BITS,
|
|
@@ -760,6 +782,9 @@ def build_dictionary_cli(
|
|
|
760
782
|
quiet: bool
|
|
761
783
|
Suppress the tradeoff table and summary output.
|
|
762
784
|
"""
|
|
785
|
+
if not _KERNELS_AVAILABLE:
|
|
786
|
+
raise RuntimeError(_MISSING_KERNELS_MESSAGE)
|
|
787
|
+
|
|
763
788
|
dictionary_size = 1 << window
|
|
764
789
|
|
|
765
790
|
# Materialize corpus for reuse in compression analysis.
|
|
@@ -19,7 +19,7 @@ try:
|
|
|
19
19
|
except ImportError:
|
|
20
20
|
sz = None
|
|
21
21
|
|
|
22
|
-
from . import ExcessBitsError,
|
|
22
|
+
from . import ExcessBitsError, compute_min_pattern_size, initialize_dictionary
|
|
23
23
|
|
|
24
24
|
# encodes [0, 14] pattern lengths
|
|
25
25
|
_huffman_codes = b"\x00\x03\x08\x0b\x14$&+KT\x94\x95\xaa'\xab"
|
|
@@ -199,7 +199,9 @@ class Compressor:
|
|
|
199
199
|
close_f_on_close = False
|
|
200
200
|
|
|
201
201
|
self._bit_writer = _BitWriter(f, close_f_on_close=close_f_on_close)
|
|
202
|
-
if dictionary and
|
|
202
|
+
if dictionary is not None and len(dictionary) != (1 << window):
|
|
203
|
+
# The exact size is required: a too-small buffer used as the window
|
|
204
|
+
# would be indexed out of bounds.
|
|
203
205
|
raise ValueError("Dictionary-window size mismatch.")
|
|
204
206
|
|
|
205
207
|
if self.extended:
|
|
@@ -244,7 +246,7 @@ class Compressor:
|
|
|
244
246
|
# Write header
|
|
245
247
|
self._bit_writer.write(window - 8, 3, flush=False)
|
|
246
248
|
self._bit_writer.write(literal - 5, 2, flush=False)
|
|
247
|
-
self._bit_writer.write(
|
|
249
|
+
self._bit_writer.write(dictionary is not None, 1, flush=False)
|
|
248
250
|
self._bit_writer.write(self.extended, 1, flush=False)
|
|
249
251
|
self._bit_writer.write(
|
|
250
252
|
1 if dictionary_reset else 0, 1, flush=False
|
|
@@ -258,7 +260,7 @@ class Compressor:
|
|
|
258
260
|
self._window_buffer = _RingBuffer(
|
|
259
261
|
buffer=(
|
|
260
262
|
dictionary
|
|
261
|
-
if dictionary
|
|
263
|
+
if dictionary is not None
|
|
262
264
|
else initialize_dictionary(1 << self.window_bits, literal=self.literal_bits if self.extended else 8)
|
|
263
265
|
),
|
|
264
266
|
)
|
|
@@ -287,6 +289,9 @@ class Compressor:
|
|
|
287
289
|
self._last_was_flush = False
|
|
288
290
|
|
|
289
291
|
if self._extended_match_count:
|
|
292
|
+
# This block consumes input and/or mutates the window, so any match
|
|
293
|
+
# cached by lazy matching no longer matches the current input.
|
|
294
|
+
self._cached_match_index = -1
|
|
290
295
|
while self._input_buffer:
|
|
291
296
|
if (self._extended_match_position + self._extended_match_count) >= self._window_buffer.size:
|
|
292
297
|
# Reached window boundary - emit match (no wrap-around, only 0.02% compression loss)
|
|
@@ -332,6 +337,7 @@ class Compressor:
|
|
|
332
337
|
|
|
333
338
|
# If RLE hasn't ended and we haven't hit max, consume and wait for more
|
|
334
339
|
if not rle_ended and total_rle > 0:
|
|
340
|
+
self._cached_match_index = -1 # Input consumed; cached lazy match is stale.
|
|
335
341
|
self._rle_count = total_rle
|
|
336
342
|
for _ in range(rle_available):
|
|
337
343
|
self._input_buffer.popleft()
|
|
@@ -353,6 +359,7 @@ class Compressor:
|
|
|
353
359
|
|
|
354
360
|
if not use_pattern:
|
|
355
361
|
# Use RLE - consume bytes and write token
|
|
362
|
+
self._cached_match_index = -1 # Input consumed; cached lazy match is stale.
|
|
356
363
|
for _ in range(rle_available):
|
|
357
364
|
self._input_buffer.popleft()
|
|
358
365
|
self._rle_count = total_rle
|
|
@@ -360,7 +367,15 @@ class Compressor:
|
|
|
360
367
|
return bytes_written
|
|
361
368
|
self._rle_count = 0
|
|
362
369
|
elif total_rle == 1:
|
|
363
|
-
|
|
370
|
+
if self._rle_count == 1:
|
|
371
|
+
# The lone run byte was consumed into _rle_count by a previous
|
|
372
|
+
# cycle and the run then ended; it is no longer in the input
|
|
373
|
+
# buffer, so emit it (_write_rle writes a count-1 run as a literal).
|
|
374
|
+
self._cached_match_index = -1
|
|
375
|
+
bytes_written += self._write_rle()
|
|
376
|
+
return bytes_written
|
|
377
|
+
# Single byte still in the input buffer - not worth RLE, will be
|
|
378
|
+
# handled as literal/pattern
|
|
364
379
|
self._rle_count = 0
|
|
365
380
|
|
|
366
381
|
# Normal pattern matching
|
|
@@ -5,7 +5,7 @@ from io import BytesIO
|
|
|
5
5
|
import micropython
|
|
6
6
|
from micropython import const
|
|
7
7
|
|
|
8
|
-
from . import ExcessBitsError,
|
|
8
|
+
from . import ExcessBitsError, compute_min_pattern_size, initialize_dictionary
|
|
9
9
|
|
|
10
10
|
# encodes [2, 15] pattern lengths
|
|
11
11
|
_HUFFMAN_CODES = b"\x00\x03\x08\x0b\x14$&+KT\x94\x95\xaa'"
|
|
@@ -38,9 +38,9 @@ class Compressor:
|
|
|
38
38
|
self.min_pattern_size = compute_min_pattern_size(window, literal)
|
|
39
39
|
|
|
40
40
|
# Window Buffer
|
|
41
|
-
if dictionary:
|
|
42
|
-
if
|
|
43
|
-
raise ValueError
|
|
41
|
+
if dictionary is not None:
|
|
42
|
+
if len(dictionary) != (1 << window):
|
|
43
|
+
raise ValueError("Dictionary-window size mismatch.")
|
|
44
44
|
self.window_buf = dictionary
|
|
45
45
|
else:
|
|
46
46
|
self.window_buf = initialize_dictionary(1 << window)
|
|
@@ -53,7 +53,7 @@ class Compressor:
|
|
|
53
53
|
|
|
54
54
|
# Write header
|
|
55
55
|
self.f = f
|
|
56
|
-
self.f_buf = ((window - 8) << 5 | (literal - 5) << 3 | int(
|
|
56
|
+
self.f_buf = ((window - 8) << 5 | (literal - 5) << 3 | int(dictionary is not None) << 2) << (22)
|
|
57
57
|
self.f_pos = 8
|
|
58
58
|
|
|
59
59
|
@micropython.viper
|
|
@@ -95,9 +95,14 @@ class Compressor:
|
|
|
95
95
|
continue # Small Speed short-cut
|
|
96
96
|
|
|
97
97
|
current_match_size = int(2)
|
|
98
|
-
|
|
98
|
+
# Clamp to the window edge so the ptr8 read below can't go
|
|
99
|
+
# out of bounds; keeps the hot loop to a single comparison.
|
|
100
|
+
max_k = window_size - window_index
|
|
101
|
+
if max_k > input_size:
|
|
102
|
+
max_k = input_size
|
|
103
|
+
for k in range(current_match_size, max_k):
|
|
99
104
|
input_index = (input_pos + k) & 0xF
|
|
100
|
-
if input_buf[input_index] != window_buf[window_index + k]
|
|
105
|
+
if input_buf[input_index] != window_buf[window_index + k]:
|
|
101
106
|
break
|
|
102
107
|
current_match_size = k + 1
|
|
103
108
|
if current_match_size > match_size:
|
|
@@ -111,9 +111,10 @@ class _BitReader:
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
class _RingBuffer:
|
|
114
|
-
def __init__(self, buffer):
|
|
114
|
+
def __init__(self, buffer, size=None):
|
|
115
115
|
self.buffer = buffer
|
|
116
|
-
|
|
116
|
+
# The window may occupy only a prefix of an oversized buffer.
|
|
117
|
+
self.size = len(buffer) if size is None else size
|
|
117
118
|
self.pos = 0 # Always pointing to the byte-to-be-overwritten
|
|
118
119
|
self.index = self.buffer.index
|
|
119
120
|
|
|
@@ -163,7 +164,10 @@ class Decompressor:
|
|
|
163
164
|
dictionary: Optional[bytearray]
|
|
164
165
|
Use the given **initialized** buffer inplace.
|
|
165
166
|
At compression time, the same initialized buffer must be provided.
|
|
166
|
-
|
|
167
|
+
Must be at least ``2**window`` bytes long for the stream's window
|
|
168
|
+
(read from the header). The first ``2**window`` bytes are used in
|
|
169
|
+
place as the decompression window; bytes past that are never read
|
|
170
|
+
or written.
|
|
167
171
|
If providing a pre-allocated buffer, but with default initialization, it must
|
|
168
172
|
first be initialized with :func:`~tamp.initialize_dictionary`
|
|
169
173
|
"""
|
|
@@ -193,12 +197,27 @@ class Decompressor:
|
|
|
193
197
|
if uses_custom_dictionary and dictionary is None:
|
|
194
198
|
raise ValueError
|
|
195
199
|
|
|
200
|
+
window_size = 1 << self.window_bits
|
|
201
|
+
if dictionary is not None:
|
|
202
|
+
if len(dictionary) < window_size:
|
|
203
|
+
raise ValueError("Dictionary-window size mismatch.")
|
|
204
|
+
if not uses_custom_dictionary:
|
|
205
|
+
# Stream does not use a custom dictionary: initialize the
|
|
206
|
+
# supplied buffer's window region in place instead of using its
|
|
207
|
+
# contents verbatim, matching the C implementation.
|
|
208
|
+
literal = self.literal_bits if self.extended else 8
|
|
209
|
+
if len(dictionary) == window_size:
|
|
210
|
+
initialize_dictionary(dictionary, literal=literal)
|
|
211
|
+
else:
|
|
212
|
+
dictionary[:window_size] = initialize_dictionary(window_size, literal=literal)
|
|
213
|
+
|
|
196
214
|
self._window_buffer = _RingBuffer(
|
|
197
215
|
buffer=(
|
|
198
216
|
dictionary
|
|
199
|
-
if dictionary
|
|
200
|
-
else initialize_dictionary(
|
|
217
|
+
if dictionary is not None
|
|
218
|
+
else initialize_dictionary(window_size, literal=self.literal_bits if self.extended else 8)
|
|
201
219
|
),
|
|
220
|
+
size=window_size,
|
|
202
221
|
)
|
|
203
222
|
|
|
204
223
|
self.min_pattern_size = compute_min_pattern_size(self.window_bits, self.literal_bits)
|
|
@@ -335,7 +354,10 @@ class Decompressor:
|
|
|
335
354
|
read_size = self.readinto(buf)
|
|
336
355
|
if size > 0:
|
|
337
356
|
# Read the entire contents in one go.
|
|
338
|
-
|
|
357
|
+
# Trim unwritten zero-padding when the stream ended early.
|
|
358
|
+
# Slice instead of ``del buf[read_size:]``: MicroPython
|
|
359
|
+
# bytearrays don't support item deletion.
|
|
360
|
+
out.append(buf if read_size == len(buf) else buf[:read_size])
|
|
339
361
|
break
|
|
340
362
|
else:
|
|
341
363
|
if read_size < len(buf):
|
|
@@ -35,6 +35,10 @@ class Decompressor:
|
|
|
35
35
|
if uses_custom_dictionary:
|
|
36
36
|
if not dictionary:
|
|
37
37
|
raise ValueError
|
|
38
|
+
if len(dictionary) != (1 << self.w_bits):
|
|
39
|
+
# A wrong-size buffer used as the window would make the unchecked
|
|
40
|
+
# ptr8 window writes below go out of bounds.
|
|
41
|
+
raise ValueError("Dictionary-window size mismatch.")
|
|
38
42
|
self.w_buf = dictionary
|
|
39
43
|
else:
|
|
40
44
|
self.w_buf = initialize_dictionary(1 << self.w_bits)
|
|
@@ -140,6 +144,10 @@ class Decompressor:
|
|
|
140
144
|
|
|
141
145
|
match_size += min_pattern_size
|
|
142
146
|
index = (f_buf >> (30 - token_bits)) & w_mask
|
|
147
|
+
if index + match_size > w_mask + 1:
|
|
148
|
+
# Corrupt stream: the match would read past the window
|
|
149
|
+
# buffer (the C implementation returns TAMP_OOB here).
|
|
150
|
+
raise ValueError("out-of-bounds window reference")
|
|
143
151
|
f_buf = (f_buf << token_bits) & full_mask
|
|
144
152
|
f_pos -= token_bits
|
|
145
153
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|