Coverage Report

Created: 2026-02-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/minizip-ng/mz_strm_ppmd.c
Line
Count
Source
1
/* mz_strm_ppmd.c -- Stream for PPMd compress/decompress
2
   part of the minizip-ng project
3
4
   Copyright (C) Nathan Moinvaziri
5
      https://github.com/zlib-ng/minizip-ng
6
7
   This program is distributed under the terms of the same license as zlib.
8
   See the accompanying LICENSE file for the full text of the license.
9
*/
10
11
#include <assert.h>
12
13
#include "mz.h"
14
#include "mz_strm.h"
15
#include "mz_strm_ppmd.h"
16
17
#include "C/Ppmd8.h"
18
#include "C/7zTypes.h"
19
20
/***************************************************************************/
21
22
static mz_stream_vtbl mz_stream_ppmd_vtbl = {
23
    mz_stream_ppmd_open,   mz_stream_ppmd_is_open, mz_stream_ppmd_read,           mz_stream_ppmd_write,
24
    mz_stream_ppmd_tell,   mz_stream_ppmd_seek,    mz_stream_ppmd_close,          mz_stream_ppmd_error,
25
    mz_stream_ppmd_create, mz_stream_ppmd_delete,  mz_stream_ppmd_get_prop_int64, mz_stream_ppmd_set_prop_int64};
26
27
/***************************************************************************/
28
29
2.72k
#define PPMD_PRESET_DEFAULT 9  // Should match default in 7-Zip
30
31
// Return values from Ppmd8_DecodeSymbol
32
0
#define PPMD_RESULT_EOF   (-1)
33
0
#define PPMD_RESULT_ERROR (-2)
34
35
/***************************************************************************/
36
37
typedef struct mz_in_buffer_s {
38
    const void *src;
39
    size_t size;
40
    size_t pos;
41
} mz_in_buffer;
42
43
typedef struct mz_out_buffer_s {
44
    void *dst;
45
    size_t size;
46
    size_t pos;
47
} mz_out_buffer;
48
49
typedef struct mz_ppmd_info_s {
50
    /* hold CPpmd8 or CPpmd7 struct pointer */
51
    void *cPpmd;
52
    void *rc;
53
    mz_in_buffer *in;
54
    mz_out_buffer *out;
55
    int max_length;
56
    int result;
57
    void *t;
58
} mz_ppmd_info;
59
60
typedef struct {
61
    /* Inherits from IByteIn */
62
    Byte (*read)(void *p);
63
    mz_in_buffer *in_buffer;
64
    void *t;
65
} mz_buffer_reader;
66
67
typedef struct {
68
    /* Inherits from IByteOut */
69
    void (*write)(void *p, Byte b);
70
    mz_out_buffer *out_buffer;
71
    mz_ppmd_info *t;
72
} mz_buffer_writer;
73
74
typedef struct mz_stream_ppmd_s {
75
    mz_stream stream;
76
    CPpmd8 ppmd8;
77
    uint8_t buffer[INT16_MAX];
78
    int64_t total_in;
79
    int64_t total_out;
80
    int64_t max_total_in;
81
    int32_t mode;
82
    int32_t error;
83
    int8_t initialized;
84
    ISzAlloc allocator;
85
86
    // Write specific
87
    mz_buffer_writer writer;
88
    mz_out_buffer out;
89
    int32_t preset;  // PPMD uses the term level for this
90
91
    // Read Specific
92
    mz_buffer_reader reader;
93
    mz_in_buffer in;
94
    int8_t end_stream;
95
} mz_stream_ppmd;
96
97
/***************************************************************************/
98
99
/* malloc wrapper for PPMD library */
100
1.70k
static void *mz_ppmd_alloc_func(const ISzAlloc *p, size_t size) {
101
1.70k
    MZ_UNUSED(p);
102
1.70k
    return malloc(size);
103
1.70k
}
104
105
/* free wrapper for PPMD library */
106
3.40k
static void mz_ppmd_free_func(const ISzAlloc *p, void *address) {
107
3.40k
    MZ_UNUSED(p);
108
3.40k
    free(address);
109
3.40k
}
110
111
#ifndef MZ_ZIP_NO_COMPRESSION
112
113
88.4M
static void writer(void *p, Byte b) {
114
88.4M
    mz_buffer_writer *buffer_writer = (mz_buffer_writer *)p;
115
88.4M
    if (buffer_writer->out_buffer->size == buffer_writer->out_buffer->pos) {
116
251
        return;
117
251
    }
118
88.4M
    *((Byte *)buffer_writer->out_buffer->dst + buffer_writer->out_buffer->pos++) = b;
119
88.4M
}
120
121
7.60k
static int32_t mz_stream_ppmd_flush(void *stream) {
122
7.60k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
123
124
7.60k
    if (ppmd->out.pos) {
125
7.57k
        if (mz_stream_write(ppmd->stream.base, ppmd->out.dst, ppmd->out.pos) != ppmd->out.pos)
126
0
            return MZ_WRITE_ERROR;
127
7.57k
        ppmd->total_out += ppmd->out.pos;
128
7.57k
        ppmd->out.pos = 0;
129
7.57k
    }
130
131
7.60k
    return MZ_OK;
132
7.60k
}
133
134
5.09k
static void mz_setup_buffered_writer(mz_stream_ppmd *ppmd) {
135
5.09k
    ppmd->out.dst = ppmd->buffer;
136
5.09k
    ppmd->out.size = sizeof(ppmd->buffer);  // INT16_MAX;
137
5.09k
    ppmd->out.pos = 0;
138
139
5.09k
    ppmd->writer.write = writer;
140
5.09k
    ppmd->writer.out_buffer = &ppmd->out;
141
5.09k
    ppmd->ppmd8.Stream.Out = (IByteOut *)&ppmd->writer;
142
5.09k
}
143
#endif
144
145
#ifndef MZ_ZIP_NO_DECOMPRESSION
146
147
0
static Byte reader(void *p) {
148
0
    mz_buffer_reader *buffer_reader = (mz_buffer_reader *)p;
149
0
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)buffer_reader->t;
150
0
    uint8_t b;
151
0
    int32_t status;
152
153
0
    if ((status = mz_stream_read_uint8((mz_stream_ppmd *)ppmd->stream.base, &b)) != MZ_OK) {
154
0
        ppmd->error = status;
155
0
        b = 0;
156
0
    } else
157
0
        ++ppmd->total_in;
158
159
0
    return (Byte)b;
160
0
}
161
162
0
static void mz_setup_buffered_reader(mz_stream_ppmd *ppmd) {
163
0
    ppmd->in.src = ppmd->buffer;
164
0
    ppmd->in.size = sizeof(ppmd->buffer);  // INT16_MAX;
165
0
    ppmd->in.pos = 0;
166
167
0
    ppmd->reader.read = reader;
168
0
    ppmd->reader.in_buffer = &ppmd->in;
169
0
    ppmd->ppmd8.Stream.In = (IByteIn *)&ppmd->reader;
170
171
0
    ppmd->reader.t = ppmd;
172
0
}
173
174
#endif
175
176
1.74k
int32_t mz_stream_ppmd_open(void *stream, const char *path, int32_t mode) {
177
1.74k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
178
179
1.74k
    MZ_UNUSED(path);
180
181
1.74k
    Ppmd8_Construct(&ppmd->ppmd8);
182
183
1.74k
    if (mode & MZ_OPEN_MODE_WRITE) {
184
#ifdef MZ_ZIP_NO_COMPRESSION
185
        MZ_UNUSED(stream);
186
        return MZ_SUPPORT_ERROR;
187
#else
188
        /* PPMD8_MIN_ORDER (= 2) <= order <= PPMD8_MAX_ORDER (= 16)
189
         * 2MB (= 2^ 1) <= memSize <= 128MB (= 2^ 7)   (M = 2^ 20)
190
         * restor = 0 (PPMD8_RESTORE_METHOD_RESTART),
191
         *          1 (PPMD8_RESTORE_METHOD_CUT_OFF)
192
         *
193
         * Currently using 7-Zip-compatible values:
194
         *    order = 3 + level
195
         *    memory size = 2^ (level- 1) (MB)  (level 9 treated as level 8.)
196
         *    restoration method = 0 for level <= 6, 1 for level >= 7.
197
         *
198
         */
199
200
        /* PPMd parameters. */
201
1.74k
        unsigned order = ppmd->preset; /* 2, 3, ..., 16. */
202
1.74k
        uint32_t mem_size;
203
1.74k
        unsigned restor;
204
1.74k
        uint16_t ppmd_param_word;
205
206
1.74k
        if (order < PPMD8_MIN_ORDER || order > PPMD8_MAX_ORDER)
207
44
            return MZ_OPEN_ERROR;
208
209
1.70k
        mz_setup_buffered_writer(ppmd);
210
211
1.70k
        mem_size = 1 << ((order < 8 ? order : 8) - 1); /* 2MB, 4MB, ..., 128MB. */
212
213
1.70k
        restor = (order <= 6 ? 0 : 1);
214
215
1.70k
        mem_size <<= 20; /* Convert B to MB. */
216
217
1.70k
        if (!Ppmd8_Alloc(&ppmd->ppmd8, mem_size, &ppmd->allocator)) {
218
0
            return MZ_MEM_ERROR;
219
0
        }
220
221
1.70k
        Ppmd8_Init_RangeEnc(&ppmd->ppmd8);
222
1.70k
        Ppmd8_Init(&ppmd->ppmd8, order, restor);
223
224
        /* wPPMd = (Model order - 1) +
225
         *         ((Sub-allocator size - 1) << 4) +
226
         *         (Model restoration method << 12)
227
         *
228
         *  15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
229
         *  Mdl_Res_Mth ___Sub-allocator_size-1 Mdl_Order-1
230
         */
231
232
        /* Form the PPMd properties word.  Put out the bytes. */
233
1.70k
        ppmd_param_word = ((order - 1) & 0xf) + ((((mem_size >> 20) - 1) & 0xff) << 4) + ((restor & 0xf) << 12);
234
235
        // write header bytes directly to output buffer, bypassing the compression code
236
        // These bytes will be included in the compressed size stored in the zip metadata.
237
238
1.70k
        ((uint8_t *)ppmd->out.dst)[0] = (uint8_t)(ppmd_param_word & 0xff);
239
1.70k
        ((uint8_t *)ppmd->out.dst)[1] = (uint8_t)(ppmd_param_word >> 8);
240
1.70k
        ppmd->out.pos += 2;
241
1.70k
        mz_stream_ppmd_flush(ppmd);
242
1.70k
#endif
243
1.70k
    } else if (mode & MZ_OPEN_MODE_READ) {
244
#ifdef MZ_ZIP_NO_DECOMPRESSION
245
        MZ_UNUSED(stream);
246
        return MZ_SUPPORT_ERROR;
247
#else
248
249
0
        uint8_t ppmd_props[2];   /* PPMd properties. */
250
0
        uint16_t ppmd_prop_word; /* PPMd properties. */
251
252
        /* Initialize the 7-Zip I/O structure. */
253
0
        mz_setup_buffered_reader(ppmd);
254
255
        /* Read & parse the 2 PPMD header bytes into a 16-bit word */
256
0
        if (mz_stream_read_uint8(ppmd->stream.base, &ppmd_props[0]) != MZ_OK ||
257
0
            mz_stream_read_uint8(ppmd->stream.base, &ppmd_props[1]) != MZ_OK)
258
0
            return MZ_STREAM_ERROR;
259
0
        ppmd_prop_word = ppmd_props[0] | (ppmd_props[1] << 8);
260
0
        ppmd->total_in += 2;
261
262
        /* wPPMd = (Model order - 1) +
263
         *         ((Sub-allocator size - 1) << 4) +
264
         *         (Model restoration method << 12)
265
         *
266
         *  15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
267
         *  Mdl_Res_Mth ___Sub-allocator_size-1 Mdl_Order-1
268
         */
269
0
        unsigned order = (ppmd_prop_word & 0xf) + 1;
270
0
        uint32_t mem_size = ((ppmd_prop_word >> 4) & 0xff) + 1;
271
0
        unsigned restor = (ppmd_prop_word >> 12);
272
273
        /* Convert archive MB value into raw byte value. */
274
0
        mem_size <<= 20;
275
276
0
        if ((order < PPMD8_MIN_ORDER) || (order > PPMD8_MAX_ORDER))
277
0
            return MZ_STREAM_ERROR;
278
279
0
        if (!Ppmd8_Alloc(&ppmd->ppmd8, mem_size, &ppmd->allocator))
280
0
            return MZ_STREAM_ERROR;
281
282
0
        if (!Ppmd8_Init_RangeDec(&ppmd->ppmd8))
283
0
            return MZ_STREAM_ERROR;
284
285
0
        Ppmd8_Init(&ppmd->ppmd8, order, restor);
286
0
#endif
287
0
    }
288
289
1.70k
    ppmd->initialized = 1;
290
1.70k
    ppmd->mode = mode;
291
292
1.70k
    return MZ_OK;
293
1.74k
}
294
295
3.39k
int32_t mz_stream_ppmd_is_open(void *stream) {
296
3.39k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
297
3.39k
    if (ppmd->initialized != 1)
298
0
        return MZ_OPEN_ERROR;
299
3.39k
    return MZ_OK;
300
3.39k
}
301
302
0
int32_t mz_stream_ppmd_read(void *stream, void *buf, int32_t size) {
303
#ifdef MZ_ZIP_NO_DECOMPRESSION
304
    MZ_UNUSED(stream);
305
    MZ_UNUSED(buf);
306
    MZ_UNUSED(size);
307
    return MZ_SUPPORT_ERROR;
308
#else
309
0
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
310
0
    uint8_t *next_out = buf;
311
0
    int32_t avail_out;
312
0
    int32_t avail_in = sizeof(ppmd->buffer);
313
0
    int sym = 0;
314
0
    int32_t written = 0;
315
316
0
    if (ppmd->end_stream)
317
0
        return MZ_OK;
318
319
0
    if (ppmd->max_total_in > 0 && avail_in > (ppmd->max_total_in - ppmd->total_in))
320
0
        avail_in = (int32_t)(ppmd->max_total_in - ppmd->total_in);
321
322
    /* Decode input to fill the output buffer. */
323
0
    for (avail_out = size; avail_out > 0 && avail_in > 0; avail_out--, avail_in--) {
324
0
        sym = Ppmd8_DecodeSymbol(&ppmd->ppmd8);
325
326
        /* There are two ways to terminate the loop early:
327
           1. Ppmd8_DecodeSymbol returns a negative number to flag EOF or stream error.
328
           2. ppmd->error gets set to true here when the call to mz_stream_read_uint8
329
              in reader() does not return MZ_OK.
330
        */
331
0
        if (sym < 0 || ppmd->error)
332
0
            break;
333
334
0
        *(next_out++) = sym;
335
0
        ++written;
336
0
    }
337
338
    // sym contains the return code from  Ppmd8_DecodeSymbol
339
0
    if (sym == PPMD_RESULT_EOF) {
340
0
        ppmd->end_stream = 1;
341
342
        // Drop through and return written bytes
343
0
    } else if (sym == PPMD_RESULT_ERROR) {
344
        /* Insufficient input data. */
345
0
        return MZ_STREAM_ERROR;
346
0
    } else if (ppmd->error) {
347
        /* Invalid end of input data */
348
0
        return ppmd->error;
349
0
    }
350
351
0
    ppmd->total_out += written;
352
0
    return written;
353
0
#endif
354
0
}
355
356
1.69k
int32_t mz_stream_ppmd_write(void *stream, const void *buf, int32_t size) {
357
#ifdef MZ_ZIP_NO_COMPRESSION
358
    MZ_UNUSED(stream);
359
    MZ_UNUSED(buf);
360
    MZ_UNUSED(size);
361
    return MZ_SUPPORT_ERROR;
362
#else
363
1.69k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
364
1.69k
    const uint8_t *buf_ptr = (const uint8_t *)buf;
365
1.69k
    int32_t bytes_written = 0;
366
367
1.69k
    mz_setup_buffered_writer(ppmd);
368
113M
    for (bytes_written = 0; bytes_written < size; bytes_written++) {
369
113M
        if (ppmd->out.pos == ppmd->out.size) {
370
2.50k
            if (mz_stream_ppmd_flush(ppmd) != MZ_OK)
371
0
                return MZ_WRITE_ERROR;
372
2.50k
        }
373
374
113M
        Ppmd8_EncodeSymbol(&ppmd->ppmd8, buf_ptr[bytes_written]);
375
113M
    }
376
1.69k
    ppmd->total_in += size;
377
378
1.69k
    if (mz_stream_ppmd_flush(stream) != MZ_OK)
379
0
        return MZ_WRITE_ERROR;
380
381
1.69k
    return size;
382
1.69k
#endif
383
1.69k
}
384
385
0
int64_t mz_stream_ppmd_tell(void *stream) {
386
0
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
387
0
    return ppmd->total_in;
388
0
}
389
390
0
int32_t mz_stream_ppmd_seek(void *stream, int64_t offset, int32_t origin) {
391
0
    MZ_UNUSED(stream);
392
0
    MZ_UNUSED(offset);
393
0
    MZ_UNUSED(origin);
394
395
0
    return MZ_SEEK_ERROR;
396
0
}
397
398
1.70k
int32_t mz_stream_ppmd_close(void *stream) {
399
1.70k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
400
401
1.70k
#ifndef MZ_ZIP_NO_COMPRESSION
402
1.70k
    if (ppmd->mode & MZ_OPEN_MODE_WRITE) {
403
1.70k
        mz_setup_buffered_writer(ppmd);
404
405
        /* Encode end marker */
406
1.70k
        Ppmd8_EncodeSymbol(&ppmd->ppmd8, -1);
407
408
1.70k
        Ppmd8_Flush_RangeEnc(&ppmd->ppmd8);
409
410
        /* Flush any remaining buffered output */
411
1.70k
        mz_stream_ppmd_flush(stream);
412
1.70k
    }
413
1.70k
#endif
414
415
1.70k
    Ppmd8_Free(&ppmd->ppmd8, &ppmd->allocator);
416
417
1.70k
    ppmd->initialized = 0;
418
419
1.70k
    return MZ_OK;
420
1.70k
}
421
422
0
int32_t mz_stream_ppmd_error(void *stream) {
423
0
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
424
0
    return ppmd->error;
425
0
}
426
427
3.40k
int32_t mz_stream_ppmd_get_prop_int64(void *stream, int32_t prop, int64_t *value) {
428
3.40k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
429
430
3.40k
    switch (prop) {
431
1.70k
    case MZ_STREAM_PROP_TOTAL_IN:
432
1.70k
        *value = ppmd->total_in;
433
1.70k
        return MZ_OK;
434
1.70k
    case MZ_STREAM_PROP_TOTAL_OUT:
435
1.70k
        *value = ppmd->total_out;
436
1.70k
        return MZ_OK;
437
0
    case MZ_STREAM_PROP_TOTAL_IN_MAX:
438
0
        *value = ppmd->max_total_in;
439
0
        return MZ_OK;
440
3.40k
    }
441
442
0
    return MZ_PARAM_ERROR;
443
3.40k
}
444
445
3.49k
int32_t mz_stream_ppmd_set_prop_int64(void *stream, int32_t prop, int64_t value) {
446
3.49k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
447
448
3.49k
    switch (prop) {
449
1.74k
    case MZ_STREAM_PROP_TOTAL_IN_MAX:
450
1.74k
        ppmd->max_total_in = value;
451
1.74k
        return MZ_OK;
452
1.74k
    case MZ_STREAM_PROP_COMPRESS_LEVEL:
453
1.74k
        if (value == MZ_COMPRESS_LEVEL_DEFAULT)
454
974
            ppmd->preset = PPMD_PRESET_DEFAULT;
455
773
        else
456
773
            ppmd->preset = (int16_t)value;
457
1.74k
        return MZ_OK;
458
3.49k
    }
459
460
0
    return MZ_PARAM_ERROR;
461
3.49k
}
462
463
1.74k
void *mz_stream_ppmd_create(void) {
464
1.74k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)calloc(1, sizeof(mz_stream_ppmd));
465
1.74k
    if (ppmd) {
466
1.74k
        ppmd->stream.vtbl = &mz_stream_ppmd_vtbl;
467
1.74k
        ppmd->allocator.Alloc = mz_ppmd_alloc_func;
468
1.74k
        ppmd->allocator.Free = mz_ppmd_free_func;
469
1.74k
        ppmd->preset = PPMD_PRESET_DEFAULT;
470
1.74k
    }
471
1.74k
    return ppmd;
472
1.74k
}
473
474
1.74k
void mz_stream_ppmd_delete(void **stream) {
475
1.74k
    mz_stream_ppmd *ppmd = NULL;
476
1.74k
    if (!stream)
477
0
        return;
478
1.74k
    ppmd = (mz_stream_ppmd *)*stream;
479
1.74k
    free(ppmd);
480
1.74k
    *stream = NULL;
481
1.74k
}
482
483
0
void *mz_stream_ppmd_get_interface(void) {
484
0
    return (void *)&mz_stream_ppmd_vtbl;
485
0
}
486
487
/***************************************************************************/