summaryrefslogtreecommitdiffstats
path: root/epan/tvbuff_zstd.c
blob: 0ce65c03eee82ef802668de4ad5a90210d11df19 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/* tvbuff_zstd.c
 * Copyright 2022, Kevin Albertson <kevin.eric.albertson [AT] gmail.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*
 * Decompress ZSTD: http://facebook.github.io/zstd/
 */

#include "config.h"

#ifdef HAVE_ZSTD
#include <zstd.h>
#endif

#include "proto.h" // DISSECTOR_ASSERT_HINT
#include "tvbuff.h"

#include "tvbuff-int.h" // tvb_add_to_chain

#define MAX_LOOP_ITERATIONS 100

tvbuff_t *tvb_uncompress_zstd(tvbuff_t *tvb, const int offset, int comprlen)
{
#ifndef HAVE_ZSTD
    // Cast to void to silence unused warnings.
    (void)tvb;
    (void)offset;
    (void)comprlen;
    return NULL;
#else
    ZSTD_inBuffer input = {tvb_memdup(NULL, tvb, offset, comprlen), comprlen, 0};
    ZSTD_DStream *zds = ZSTD_createDStream();
    size_t rc = 0;
    uint8_t *uncompr = NULL;
    size_t uncompr_len = 0;
    bool ok = false;
    int count = 0;

    // ZSTD does not consume the last byte of the frame until it has flushed all of the decompressed data of the frame.
    // Therefore, loop while there is more input.
    ZSTD_outBuffer output = {g_malloc(ZSTD_DStreamOutSize()), ZSTD_DStreamOutSize(), 0};
    while (input.pos < input.size && count < MAX_LOOP_ITERATIONS)
    {
        rc = ZSTD_decompressStream(zds, &output, &input);
        if (ZSTD_isError(rc))
        {
            goto end;
        }

        if (output.pos > 0)
        {
            if (!uncompr)
            {
                DISSECTOR_ASSERT (uncompr_len == 0);
                uncompr = g_malloc(output.pos);
            } else {
                uncompr = g_realloc(uncompr, uncompr_len + output.pos);
            }
            memcpy (uncompr + uncompr_len, output.dst, output.pos);
            uncompr_len += output.pos;
            // Reset the output buffer.
            output.pos = 0;
        }
        count++;
        DISSECTOR_ASSERT_HINT(count < MAX_LOOP_ITERATIONS, "MAX_LOOP_ITERATIONS exceeded");
    }
    if (rc > 0)
    {
        // There is extra data that was not decompressed.
        goto end;
    }

    ok = true;
end:
    g_free((void *)output.dst);
    wmem_free(NULL, (void *)input.src);
    ZSTD_freeDStream(zds);
    if (ok)
    {
        tvbuff_t *uncompr_tvb;
        uncompr_tvb = tvb_new_real_data (uncompr, (guint)uncompr_len, (guint)uncompr_len);
        tvb_set_free_cb (uncompr_tvb, g_free);
        return uncompr_tvb;
    }

    if (uncompr)
    {
        g_free (uncompr);
    }

    return NULL;
#endif /* HAVE_ZSTD */
}

tvbuff_t *tvb_child_uncompress_zstd(tvbuff_t *parent, tvbuff_t *tvb, const int offset, int comprlen)
{
    tvbuff_t *uncompressed = tvb_uncompress_zstd(tvb, offset, comprlen);
    if (!uncompressed)
    {
        return uncompressed;
    }
    tvb_add_to_chain(parent, uncompressed);
    return uncompressed;
}