Coverage Report

Created: 2026-04-30 06:11

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
12.5k
#define PPMD_PRESET_DEFAULT 9  // Should match default in 7-Zip
30
31
// Return values from Ppmd8_DecodeSymbol
32
8.70k
#define PPMD_RESULT_EOF   (-1)
33
7.43k
#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
11.3k
static void *mz_ppmd_alloc_func(const ISzAlloc *p, size_t size) {
101
11.3k
    MZ_UNUSED(p);
102
11.3k
    return malloc(size);
103
11.3k
}
104
105
/* free wrapper for PPMD library */
106
22.6k
static void mz_ppmd_free_func(const ISzAlloc *p, void *address) {
107
22.6k
    MZ_UNUSED(p);
108
22.6k
    free(address);
109
22.6k
}
110
111
#ifndef MZ_ZIP_NO_COMPRESSION
112
113
90.7M
static void writer(void *p, Byte b) {
114
90.7M
    mz_buffer_writer *buffer_writer = (mz_buffer_writer *)p;
115
90.7M
    if (buffer_writer->out_buffer->size == buffer_writer->out_buffer->pos) {
116
282
        return;
117
282
    }
118
90.7M
    *((Byte *)buffer_writer->out_buffer->dst + buffer_writer->out_buffer->pos++) = b;
119
90.7M
}
120
121
8.61k
static int32_t mz_stream_ppmd_flush(void *stream) {
122
8.61k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
123
124
8.61k
    if (ppmd->out.pos) {
125
8.56k
        if (mz_stream_write(ppmd->stream.base, ppmd->out.dst, ppmd->out.pos) != ppmd->out.pos)
126
0
            return MZ_WRITE_ERROR;
127
8.56k
        ppmd->total_out += ppmd->out.pos;
128
8.56k
        ppmd->out.pos = 0;
129
8.56k
    }
130
131
8.61k
    return MZ_OK;
132
8.61k
}
133
134
6.06k
static void mz_setup_buffered_writer(mz_stream_ppmd *ppmd) {
135
6.06k
    ppmd->out.dst = ppmd->buffer;
136
6.06k
    ppmd->out.size = sizeof(ppmd->buffer);  // INT16_MAX;
137
6.06k
    ppmd->out.pos = 0;
138
139
6.06k
    ppmd->writer.write = writer;
140
6.06k
    ppmd->writer.out_buffer = &ppmd->out;
141
6.06k
    ppmd->ppmd8.Stream.Out = (IByteOut *)&ppmd->writer;
142
6.06k
}
143
#endif
144
145
#ifndef MZ_ZIP_NO_DECOMPRESSION
146
147
1.55M
static Byte reader(void *p) {
148
1.55M
    mz_buffer_reader *buffer_reader = (mz_buffer_reader *)p;
149
1.55M
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)buffer_reader->t;
150
1.55M
    uint8_t b;
151
1.55M
    int32_t status;
152
153
1.55M
    if (ppmd->max_total_in > 0 && ppmd->total_in >= ppmd->max_total_in) {
154
71
        ppmd->error = MZ_STREAM_ERROR;
155
71
        return 0;
156
71
    }
157
158
1.55M
    if ((status = mz_stream_read_uint8((mz_stream_ppmd *)ppmd->stream.base, &b)) != MZ_OK) {
159
1.03k
        ppmd->error = status;
160
1.03k
        b = 0;
161
1.03k
    } else
162
1.55M
        ++ppmd->total_in;
163
164
1.55M
    return (Byte)b;
165
1.55M
}
166
167
9.34k
static void mz_setup_buffered_reader(mz_stream_ppmd *ppmd) {
168
9.34k
    ppmd->in.src = ppmd->buffer;
169
9.34k
    ppmd->in.size = sizeof(ppmd->buffer);  // INT16_MAX;
170
9.34k
    ppmd->in.pos = 0;
171
172
9.34k
    ppmd->reader.read = reader;
173
9.34k
    ppmd->reader.in_buffer = &ppmd->in;
174
9.34k
    ppmd->ppmd8.Stream.In = (IByteIn *)&ppmd->reader;
175
176
9.34k
    ppmd->reader.t = ppmd;
177
9.34k
}
178
179
#endif
180
181
11.4k
int32_t mz_stream_ppmd_open(void *stream, const char *path, int32_t mode) {
182
11.4k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
183
184
11.4k
    MZ_UNUSED(path);
185
186
11.4k
    Ppmd8_Construct(&ppmd->ppmd8);
187
188
11.4k
    if (mode & MZ_OPEN_MODE_WRITE) {
189
#ifdef MZ_ZIP_NO_COMPRESSION
190
        MZ_UNUSED(stream);
191
        return MZ_SUPPORT_ERROR;
192
#else
193
        /* PPMD8_MIN_ORDER (= 2) <= order <= PPMD8_MAX_ORDER (= 16)
194
         * 2MB (= 2^ 1) <= memSize <= 128MB (= 2^ 7)   (M = 2^ 20)
195
         * restor = 0 (PPMD8_RESTORE_METHOD_RESTART),
196
         *          1 (PPMD8_RESTORE_METHOD_CUT_OFF)
197
         *
198
         * Currently using 7-Zip-compatible values:
199
         *    order = 3 + level
200
         *    memory size = 2^ (level- 1) (MB)  (level 9 treated as level 8.)
201
         *    restoration method = 0 for level <= 6, 1 for level >= 7.
202
         *
203
         */
204
205
        /* PPMd parameters. */
206
2.07k
        unsigned order = ppmd->preset; /* 2, 3, ..., 16. */
207
2.07k
        uint32_t mem_size;
208
2.07k
        unsigned restor;
209
2.07k
        uint16_t ppmd_param_word;
210
211
2.07k
        if (order < PPMD8_MIN_ORDER || order > PPMD8_MAX_ORDER)
212
48
            return MZ_OPEN_ERROR;
213
214
2.02k
        mz_setup_buffered_writer(ppmd);
215
216
2.02k
        mem_size = 1 << ((order < 8 ? order : 8) - 1); /* 2MB, 4MB, ..., 128MB. */
217
218
2.02k
        restor = (order <= 6 ? 0 : 1);
219
220
2.02k
        mem_size <<= 20; /* Convert B to MB. */
221
222
2.02k
        if (!Ppmd8_Alloc(&ppmd->ppmd8, mem_size, &ppmd->allocator)) {
223
0
            return MZ_MEM_ERROR;
224
0
        }
225
226
2.02k
        Ppmd8_Init_RangeEnc(&ppmd->ppmd8);
227
2.02k
        Ppmd8_Init(&ppmd->ppmd8, order, restor);
228
229
        /* wPPMd = (Model order - 1) +
230
         *         ((Sub-allocator size - 1) << 4) +
231
         *         (Model restoration method << 12)
232
         *
233
         *  15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
234
         *  Mdl_Res_Mth ___Sub-allocator_size-1 Mdl_Order-1
235
         */
236
237
        /* Form the PPMd properties word.  Put out the bytes. */
238
2.02k
        ppmd_param_word = ((order - 1) & 0xf) + ((((mem_size >> 20) - 1) & 0xff) << 4) + ((restor & 0xf) << 12);
239
240
        // write header bytes directly to output buffer, bypassing the compression code
241
        // These bytes will be included in the compressed size stored in the zip metadata.
242
243
2.02k
        ((uint8_t *)ppmd->out.dst)[0] = (uint8_t)(ppmd_param_word & 0xff);
244
2.02k
        ((uint8_t *)ppmd->out.dst)[1] = (uint8_t)(ppmd_param_word >> 8);
245
2.02k
        ppmd->out.pos += 2;
246
2.02k
        mz_stream_ppmd_flush(ppmd);
247
2.02k
#endif
248
9.34k
    } else if (mode & MZ_OPEN_MODE_READ) {
249
#ifdef MZ_ZIP_NO_DECOMPRESSION
250
        MZ_UNUSED(stream);
251
        return MZ_SUPPORT_ERROR;
252
#else
253
254
9.34k
        uint8_t ppmd_props[2];   /* PPMd properties. */
255
9.34k
        uint16_t ppmd_prop_word; /* PPMd properties. */
256
257
        /* Initialize the 7-Zip I/O structure. */
258
9.34k
        mz_setup_buffered_reader(ppmd);
259
260
        /* Read & parse the 2 PPMD header bytes into a 16-bit word */
261
9.34k
        if (mz_stream_read_uint8(ppmd->stream.base, &ppmd_props[0]) != MZ_OK ||
262
9.33k
            mz_stream_read_uint8(ppmd->stream.base, &ppmd_props[1]) != MZ_OK)
263
24
            return MZ_STREAM_ERROR;
264
9.32k
        ppmd_prop_word = ppmd_props[0] | (ppmd_props[1] << 8);
265
9.32k
        ppmd->total_in += 2;
266
267
        /* wPPMd = (Model order - 1) +
268
         *         ((Sub-allocator size - 1) << 4) +
269
         *         (Model restoration method << 12)
270
         *
271
         *  15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
272
         *  Mdl_Res_Mth ___Sub-allocator_size-1 Mdl_Order-1
273
         */
274
9.32k
        unsigned order = (ppmd_prop_word & 0xf) + 1;
275
9.32k
        uint32_t mem_size = ((ppmd_prop_word >> 4) & 0xff) + 1;
276
9.32k
        unsigned restor = (ppmd_prop_word >> 12);
277
278
        /* Convert archive MB value into raw byte value. */
279
9.32k
        mem_size <<= 20;
280
281
9.32k
        if ((order < PPMD8_MIN_ORDER) || (order > PPMD8_MAX_ORDER))
282
7
            return MZ_STREAM_ERROR;
283
284
9.31k
        if (!Ppmd8_Alloc(&ppmd->ppmd8, mem_size, &ppmd->allocator))
285
0
            return MZ_STREAM_ERROR;
286
287
9.31k
        if (!Ppmd8_Init_RangeDec(&ppmd->ppmd8))
288
2
            return MZ_STREAM_ERROR;
289
290
9.31k
        Ppmd8_Init(&ppmd->ppmd8, order, restor);
291
9.31k
#endif
292
9.31k
    }
293
294
11.3k
    ppmd->initialized = 1;
295
11.3k
    ppmd->mode = mode;
296
297
11.3k
    return MZ_OK;
298
11.4k
}
299
300
22.0k
int32_t mz_stream_ppmd_is_open(void *stream) {
301
22.0k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
302
22.0k
    if (ppmd->initialized != 1)
303
0
        return MZ_OPEN_ERROR;
304
22.0k
    return MZ_OK;
305
22.0k
}
306
307
8.70k
int32_t mz_stream_ppmd_read(void *stream, void *buf, int32_t size) {
308
#ifdef MZ_ZIP_NO_DECOMPRESSION
309
    MZ_UNUSED(stream);
310
    MZ_UNUSED(buf);
311
    MZ_UNUSED(size);
312
    return MZ_SUPPORT_ERROR;
313
#else
314
8.70k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
315
8.70k
    uint8_t *next_out = buf;
316
8.70k
    int32_t avail_out;
317
8.70k
    int64_t start_in = ppmd->total_in;
318
8.70k
    int64_t avail_in = (ppmd->max_total_in > 0 ? ppmd->max_total_in : INT64_MAX) - start_in;
319
8.70k
    int sym = 0;
320
8.70k
    int32_t written = 0;
321
322
8.70k
    if (ppmd->end_stream)
323
0
        return MZ_OK;
324
325
    /* Decode input to fill the output buffer. */
326
7.09M
    for (avail_out = size; avail_out > 0 && avail_in > (ppmd->total_in - start_in); avail_out--) {
327
7.09M
        sym = Ppmd8_DecodeSymbol(&ppmd->ppmd8);
328
329
        /* There are two ways to terminate the loop early:
330
           1. Ppmd8_DecodeSymbol returns a negative number to flag EOF or stream error.
331
           2. ppmd->error gets set to true here when the call to mz_stream_read_uint8
332
              in reader() does not return MZ_OK.
333
        */
334
7.09M
        if (sym < 0 || ppmd->error)
335
2.15k
            break;
336
337
7.08M
        *(next_out++) = sym;
338
7.08M
        ++written;
339
7.08M
    }
340
341
    // sym contains the return code from  Ppmd8_DecodeSymbol
342
8.70k
    if (sym == PPMD_RESULT_EOF) {
343
1.27k
        ppmd->end_stream = 1;
344
345
        // Drop through and return written bytes
346
7.43k
    } else if (sym == PPMD_RESULT_ERROR) {
347
        /* Insufficient input data. */
348
196
        return MZ_STREAM_ERROR;
349
7.23k
    } else if (ppmd->error) {
350
        /* Invalid end of input data */
351
711
        return ppmd->error;
352
711
    }
353
354
7.79k
    ppmd->total_out += written;
355
7.79k
    return written;
356
8.70k
#endif
357
8.70k
}
358
359
2.01k
int32_t mz_stream_ppmd_write(void *stream, const void *buf, int32_t size) {
360
#ifdef MZ_ZIP_NO_COMPRESSION
361
    MZ_UNUSED(stream);
362
    MZ_UNUSED(buf);
363
    MZ_UNUSED(size);
364
    return MZ_SUPPORT_ERROR;
365
#else
366
2.01k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
367
2.01k
    const uint8_t *buf_ptr = (const uint8_t *)buf;
368
2.01k
    int32_t bytes_written = 0;
369
370
2.01k
    mz_setup_buffered_writer(ppmd);
371
108M
    for (bytes_written = 0; bytes_written < size; bytes_written++) {
372
108M
        if (ppmd->out.pos == ppmd->out.size) {
373
2.54k
            if (mz_stream_ppmd_flush(ppmd) != MZ_OK)
374
0
                return MZ_WRITE_ERROR;
375
2.54k
        }
376
377
108M
        Ppmd8_EncodeSymbol(&ppmd->ppmd8, buf_ptr[bytes_written]);
378
108M
    }
379
2.01k
    ppmd->total_in += size;
380
381
2.01k
    if (mz_stream_ppmd_flush(stream) != MZ_OK)
382
0
        return MZ_WRITE_ERROR;
383
384
2.01k
    return size;
385
2.01k
#endif
386
2.01k
}
387
388
0
int64_t mz_stream_ppmd_tell(void *stream) {
389
0
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
390
0
    return ppmd->total_in;
391
0
}
392
393
0
int32_t mz_stream_ppmd_seek(void *stream, int64_t offset, int32_t origin) {
394
0
    MZ_UNUSED(stream);
395
0
    MZ_UNUSED(offset);
396
0
    MZ_UNUSED(origin);
397
398
0
    return MZ_SEEK_ERROR;
399
0
}
400
401
11.3k
int32_t mz_stream_ppmd_close(void *stream) {
402
11.3k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
403
404
11.3k
#ifndef MZ_ZIP_NO_COMPRESSION
405
11.3k
    if (ppmd->mode & MZ_OPEN_MODE_WRITE) {
406
2.02k
        mz_setup_buffered_writer(ppmd);
407
408
        /* Encode end marker */
409
2.02k
        Ppmd8_EncodeSymbol(&ppmd->ppmd8, -1);
410
411
2.02k
        Ppmd8_Flush_RangeEnc(&ppmd->ppmd8);
412
413
        /* Flush any remaining buffered output */
414
2.02k
        mz_stream_ppmd_flush(stream);
415
2.02k
    }
416
11.3k
#endif
417
418
11.3k
    Ppmd8_Free(&ppmd->ppmd8, &ppmd->allocator);
419
420
11.3k
    ppmd->initialized = 0;
421
422
11.3k
    return MZ_OK;
423
11.3k
}
424
425
0
int32_t mz_stream_ppmd_error(void *stream) {
426
0
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
427
0
    return ppmd->error;
428
0
}
429
430
13.3k
int32_t mz_stream_ppmd_get_prop_int64(void *stream, int32_t prop, int64_t *value) {
431
13.3k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
432
433
13.3k
    switch (prop) {
434
11.3k
    case MZ_STREAM_PROP_TOTAL_IN:
435
11.3k
        *value = ppmd->total_in;
436
11.3k
        return MZ_OK;
437
2.02k
    case MZ_STREAM_PROP_TOTAL_OUT:
438
2.02k
        *value = ppmd->total_out;
439
2.02k
        return MZ_OK;
440
0
    case MZ_STREAM_PROP_TOTAL_IN_MAX:
441
0
        *value = ppmd->max_total_in;
442
0
        return MZ_OK;
443
13.3k
    }
444
445
0
    return MZ_PARAM_ERROR;
446
13.3k
}
447
448
17.5k
int32_t mz_stream_ppmd_set_prop_int64(void *stream, int32_t prop, int64_t value) {
449
17.5k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)stream;
450
451
17.5k
    switch (prop) {
452
15.4k
    case MZ_STREAM_PROP_TOTAL_IN_MAX:
453
15.4k
        ppmd->max_total_in = value;
454
15.4k
        return MZ_OK;
455
2.07k
    case MZ_STREAM_PROP_COMPRESS_LEVEL:
456
2.07k
        if (value == MZ_COMPRESS_LEVEL_DEFAULT)
457
1.07k
            ppmd->preset = PPMD_PRESET_DEFAULT;
458
996
        else
459
996
            ppmd->preset = (int16_t)value;
460
2.07k
        return MZ_OK;
461
17.5k
    }
462
463
0
    return MZ_PARAM_ERROR;
464
17.5k
}
465
466
11.4k
void *mz_stream_ppmd_create(void) {
467
11.4k
    mz_stream_ppmd *ppmd = (mz_stream_ppmd *)calloc(1, sizeof(mz_stream_ppmd));
468
11.4k
    if (ppmd) {
469
11.4k
        ppmd->stream.vtbl = &mz_stream_ppmd_vtbl;
470
11.4k
        ppmd->allocator.Alloc = mz_ppmd_alloc_func;
471
11.4k
        ppmd->allocator.Free = mz_ppmd_free_func;
472
11.4k
        ppmd->preset = PPMD_PRESET_DEFAULT;
473
11.4k
    }
474
11.4k
    return ppmd;
475
11.4k
}
476
477
11.4k
void mz_stream_ppmd_delete(void **stream) {
478
11.4k
    mz_stream_ppmd *ppmd = NULL;
479
11.4k
    if (!stream)
480
0
        return;
481
11.4k
    ppmd = (mz_stream_ppmd *)*stream;
482
11.4k
    free(ppmd);
483
11.4k
    *stream = NULL;
484
11.4k
}
485
486
0
void *mz_stream_ppmd_get_interface(void) {
487
0
    return (void *)&mz_stream_ppmd_vtbl;
488
0
}
489
490
/***************************************************************************/