1 /**
2  * @file
3  * Zstandard (zstd) compression
4  *
5  * @authors
6  * Copyright (C) 2019-2020 Tino Reichardt <milky-neomutt@mcmilk.de>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page compress_zstd Zstandard (zstd) compression
25  *
26  * Zstandard (zstd) compression.
27  * https://www.zstd.net
28  */
29 
30 #include "config.h"
31 #include <stdio.h>
32 #include <zstd.h>
33 #include "private.h"
34 #include "mutt/lib.h"
35 #include "lib.h"
36 
37 #define MIN_COMP_LEVEL 1  ///< Minimum compression level for zstd
38 #define MAX_COMP_LEVEL 22 ///< Maximum compression level for zstd
39 
40 /**
41  * struct ComprZstdCtx - Private Zstandard Compression Context
42  */
43 struct ComprZstdCtx
44 {
45   void *buf;   ///< Temporary buffer
46   short level; ///< Compression Level to be used
47 
48   ZSTD_CCtx *cctx; ///< Compression context
49   ZSTD_DCtx *dctx; ///< Decompression context
50 };
51 
52 /**
53  * compr_zstd_open - Implements ComprOps::open() - @ingroup compress_open
54  */
compr_zstd_open(short level)55 static void *compr_zstd_open(short level)
56 {
57   struct ComprZstdCtx *ctx = mutt_mem_malloc(sizeof(struct ComprZstdCtx));
58 
59   ctx->buf = mutt_mem_malloc(ZSTD_compressBound(1024 * 128));
60   ctx->cctx = ZSTD_createCCtx();
61   ctx->dctx = ZSTD_createDCtx();
62 
63   if (!ctx->cctx || !ctx->dctx)
64   {
65     // LCOV_EXCL_START
66     ZSTD_freeCCtx(ctx->cctx);
67     ZSTD_freeDCtx(ctx->dctx);
68     FREE(&ctx->buf);
69     FREE(&ctx);
70     return NULL;
71     // LCOV_EXCL_STOP
72   }
73 
74   if ((level < MIN_COMP_LEVEL) || (level > MAX_COMP_LEVEL))
75   {
76     mutt_debug(LL_DEBUG1, "The compression level for %s should be between %d and %d",
77                compr_zstd_ops.name, MIN_COMP_LEVEL, MAX_COMP_LEVEL);
78     level = MIN_COMP_LEVEL;
79   }
80 
81   ctx->level = level;
82 
83   return ctx;
84 }
85 
86 /**
87  * compr_zstd_compress - Implements ComprOps::compress() - @ingroup compress_compress
88  */
compr_zstd_compress(void * cctx,const char * data,size_t dlen,size_t * clen)89 static void *compr_zstd_compress(void *cctx, const char *data, size_t dlen, size_t *clen)
90 {
91   if (!cctx)
92     return NULL;
93 
94   struct ComprZstdCtx *ctx = cctx;
95 
96   size_t len = ZSTD_compressBound(dlen);
97   mutt_mem_realloc(&ctx->buf, len);
98 
99   size_t ret = ZSTD_compressCCtx(ctx->cctx, ctx->buf, len, data, dlen, ctx->level);
100   if (ZSTD_isError(ret))
101     return NULL; // LCOV_EXCL_LINE
102 
103   *clen = ret;
104 
105   return ctx->buf;
106 }
107 
108 /**
109  * compr_zstd_decompress - Implements ComprOps::decompress() - @ingroup compress_decompress
110  */
compr_zstd_decompress(void * cctx,const char * cbuf,size_t clen)111 static void *compr_zstd_decompress(void *cctx, const char *cbuf, size_t clen)
112 {
113   struct ComprZstdCtx *ctx = cctx;
114 
115   if (!cctx)
116     return NULL;
117 
118   unsigned long long len = ZSTD_getFrameContentSize(cbuf, clen);
119   if (len == ZSTD_CONTENTSIZE_UNKNOWN)
120     return NULL; // LCOV_EXCL_LINE
121   else if (len == ZSTD_CONTENTSIZE_ERROR)
122     return NULL;
123   else if (len == 0)
124     return NULL; // LCOV_EXCL_LINE
125   mutt_mem_realloc(&ctx->buf, len);
126 
127   size_t ret = ZSTD_decompressDCtx(ctx->dctx, ctx->buf, len, cbuf, clen);
128   if (ZSTD_isError(ret))
129     return NULL; // LCOV_EXCL_LINE
130 
131   return ctx->buf;
132 }
133 
134 /**
135  * compr_zstd_close - Implements ComprOps::close() - @ingroup compress_close
136  */
compr_zstd_close(void ** cctx)137 static void compr_zstd_close(void **cctx)
138 {
139   if (!cctx || !*cctx)
140     return;
141 
142   struct ComprZstdCtx *ctx = *cctx;
143 
144   if (ctx->cctx)
145     ZSTD_freeCCtx(ctx->cctx);
146 
147   if (ctx->dctx)
148     ZSTD_freeDCtx(ctx->dctx);
149 
150   FREE(&ctx->buf);
151   FREE(cctx);
152 }
153 
154 COMPRESS_OPS(zstd, MIN_COMP_LEVEL, MAX_COMP_LEVEL)
155