Skip to content

Commit 1a113fc

Browse files
committed
Add Zstandard (zstd) compression support to the compress plugin
1 parent 3116856 commit 1a113fc

File tree

8 files changed

+133
-4
lines changed

8 files changed

+133
-4
lines changed

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ set(TS_USE_MALLOC_ALLOCATOR ${ENABLE_MALLOC_ALLOCATOR})
338338
set(TS_USE_ALLOCATOR_METRICS ${ENABLE_ALLOCATOR_METRICS})
339339
find_package(ZLIB REQUIRED)
340340

341+
find_package(zstd)
342+
if(zstd_FOUND)
343+
set(HAVE_ZSTD_H TRUE)
344+
endif()
345+
341346
# ncurses is used in traffic_top
342347
find_package(Curses)
343348
set(HAVE_CURSES_H ${CURSES_HAVE_CURSES_H})

plugins/compress/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,10 @@ target_link_libraries(compress PRIVATE libswoc::libswoc)
2020
if(HAVE_BROTLI_ENCODE_H)
2121
target_link_libraries(compress PRIVATE brotli::brotlienc)
2222
endif()
23+
24+
if(HAVE_ZSTD_H)
25+
target_link_libraries(compress PRIVATE zstd)
26+
endif()
27+
2328
verify_global_plugin(compress)
2429
verify_remap_plugin(compress)

plugins/compress/compress.cc

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @file
22
3-
Transforms content using gzip, deflate or brotli
3+
Transforms content using gzip, deflate, brotli or zstd
44
55
@section license License
66
@@ -23,6 +23,9 @@
2323

2424
#include <cstring>
2525
#include <zlib.h>
26+
#if HAVE_ZSTD_F
27+
#include <zstd.h>
28+
#endif
2629

2730
#include "ts/apidefs.h"
2831
#include "tscore/ink_config.h"
@@ -71,6 +74,10 @@ const int BROTLI_COMPRESSION_LEVEL = 6;
7174
const int BROTLI_LGW = 16;
7275
#endif
7376

77+
#if HAVE_ZSTD_H
78+
const int ZSTD_COMPRESSION_LEVEL = 6;
79+
#endif
80+
7481
static const char *global_hidden_header_name = nullptr;
7582

7683
static TSMutex compress_config_mutex = nullptr;
@@ -191,6 +198,16 @@ data_alloc(int compression_type, int compression_algorithms)
191198
data->bstrm.avail_out = 0;
192199
data->bstrm.total_out = 0;
193200
}
201+
#endif
202+
#if HAVE_ZSTD_H
203+
data->zstd_ctx = nullptr;
204+
if (compression_type & COMPRESSION_TYPE_ZSTD) {
205+
debug("zstd compression. Create Zstd Compression Context.");
206+
data->zstd_ctx = ZSTD_createCCtx();
207+
if (!data->zstd_ctx) {
208+
fatal("Zstd Compression Context Creation Failed");
209+
}
210+
}
194211
#endif
195212
return data;
196213
}
@@ -212,6 +229,11 @@ data_destroy(Data *data)
212229
#if HAVE_BROTLI_ENCODE_H
213230
BrotliEncoderDestroyInstance(data->bstrm.br);
214231
#endif
232+
#if HAVE_ZSTD_H
233+
if (data->zstd_ctx) {
234+
ZSTD_freeCCtx(data->zstd_ctx);
235+
}
236+
#endif
215237

216238
TSfree(data);
217239
}
@@ -233,6 +255,9 @@ content_encoding_header(TSMBuffer bufp, TSMLoc hdr_loc, const int compression_ty
233255
} else if (compression_type & COMPRESSION_TYPE_DEFLATE && (algorithm & ALGORITHM_DEFLATE)) {
234256
value = TS_HTTP_VALUE_DEFLATE;
235257
value_len = TS_HTTP_LEN_DEFLATE;
258+
} else if (compression_type & COMPRESSION_TYPE_ZSTD && (algorithm & ALGORITHM_ZSTD)) {
259+
value = "zstd";
260+
value_len = strlen("zstd");
236261
}
237262

238263
if (value_len == 0) {
@@ -357,6 +382,16 @@ compress_transform_init(TSCont contp, Data *data)
357382
data->downstream_vio = TSVConnWrite(downstream_conn, contp, data->downstream_reader, INT64_MAX);
358383
}
359384

385+
#if HAVE_ZSTD_H
386+
if (data->compression_type & COMPRESSION_TYPE_ZSTD) {
387+
zstd_compress_init(data);
388+
if (!data->zstd_cctx) {
389+
TSError("Failed to create Zstandard compression context");
390+
return;
391+
}
392+
}
393+
#endif
394+
360395
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
361396
}
362397

@@ -462,6 +497,39 @@ brotli_transform_one(Data *data, const char *upstream_buffer, int64_t upstream_l
462497
}
463498
#endif
464499

500+
#if HAVE_ZSTD_H
501+
static void
502+
zstd_compress_init(Data *data)
503+
{
504+
data->zstd_cctx = ZSTD_createCCtx();
505+
if (!data->zstd_cctx) {
506+
error("Failed to initialize Zstd compression context");
507+
}
508+
}
509+
510+
static void
511+
zstd_compress_finish(Data *data)
512+
{
513+
if (data->zstd_cctx) {
514+
ZSTD_freeCCtx(data->zstd_cctx);
515+
data->zstd_cctx = nullptr;
516+
}
517+
}
518+
519+
static void
520+
zstd_compress_one(Data *data, const char *upstream_buffer, int64_t upstream_length)
521+
{
522+
char output_buffer[ZSTD_CStreamOutSize()];
523+
size_t compressed_size = ZSTD_compressCCtx(data->zstd_cctx, output_buffer, sizeof(output_buffer), upstream_buffer, upstream_length, ZSTD_COMPRESSION_LEVEL);
524+
if (ZSTD_isError(compressed_size)) {
525+
error("Zstd compression failed: %s", ZSTD_getErrorName(compressed_size));
526+
return;
527+
}
528+
TSIOBufferWrite(data->downstream_buffer, output_buffer, compressed_size);
529+
data->downstream_length += compressed_size;
530+
}
531+
#endif
532+
465533
static void
466534
compress_transform_one(Data *data, TSIOBufferReader upstream_reader, int amount)
467535
{
@@ -488,6 +556,11 @@ compress_transform_one(Data *data, TSIOBufferReader upstream_reader, int amount)
488556
if (data->compression_type & COMPRESSION_TYPE_BROTLI && (data->compression_algorithms & ALGORITHM_BROTLI)) {
489557
brotli_transform_one(data, upstream_buffer, upstream_length);
490558
} else
559+
#endif
560+
#if HAVE_ZSTD_H
561+
if (data->compression_type & COMPRESSION_TYPE_ZSTD && (data->compression_algorithms & ALGORITHM_ZSTD)) {
562+
zstd_compress_one(data, upstream_buffer, upstream_length);
563+
} else
491564
#endif
492565
if ((data->compression_type & (COMPRESSION_TYPE_GZIP | COMPRESSION_TYPE_DEFLATE)) &&
493566
(data->compression_algorithms & (ALGORITHM_GZIP | ALGORITHM_DEFLATE))) {
@@ -576,6 +649,12 @@ compress_transform_finish(Data *data)
576649
brotli_transform_finish(data);
577650
debug("compress_transform_finish: brotli compression finish");
578651
} else
652+
#endif
653+
#if HAVE_ZSTD_H
654+
if (data->compression_type & COMPRESSION_TYPE_ZSTD && data->compression_algorithms & ALGORITHM_ZSTD) {
655+
zstd_compress_finish(data);
656+
debug("compress_transform_finish: zstd compression finish");
657+
} else
579658
#endif
580659
if ((data->compression_type & (COMPRESSION_TYPE_GZIP | COMPRESSION_TYPE_DEFLATE)) &&
581660
(data->compression_algorithms & (ALGORITHM_GZIP | ALGORITHM_DEFLATE))) {
@@ -771,6 +850,11 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
771850
compression_acceptable = 1;
772851
}
773852
*compress_type |= COMPRESSION_TYPE_GZIP;
853+
} else if (strncasecmp(value, "zstd", sizeof("zstd") - 1) == 0) {
854+
if (*algorithms & ALGORITHM_ZSTD) {
855+
compression_acceptable = 1;
856+
}
857+
*compress_type |= COMPRESSION_TYPE_ZSTD;
774858
}
775859
}
776860

plugins/compress/configuration.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ HostConfiguration::add_compression_algorithms(string &line)
229229
string token = extractFirstToken(line, isCommaOrSpace);
230230
if (token.empty()) {
231231
break;
232+
} else if (token == "zstd") {
233+
#ifdef HAVE_ZSTD_H
234+
compression_algorithms_ |= ALGORITHM_ZSTD;
235+
#else
236+
error("supported-algorithms: zstd support not compiled in.");
237+
#endif
232238
} else if (token == "br") {
233239
#ifdef HAVE_BROTLI_ENCODE_H
234240
compression_algorithms_ |= ALGORITHM_BROTLI;
@@ -334,6 +340,10 @@ Configuration::Parse(const char *path)
334340
continue;
335341
}
336342

343+
if (strstr(line.c_str(), "zstd")) {
344+
current_host_configuration->add_compression_algorithms(line);
345+
}
346+
337347
for (;;) {
338348
string token = extractFirstToken(line, isspace);
339349

plugins/compress/configuration.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ enum CompressionAlgorithm {
3838
ALGORITHM_DEFAULT = 0,
3939
ALGORITHM_DEFLATE = 1,
4040
ALGORITHM_GZIP = 2,
41-
ALGORITHM_BROTLI = 4 // For bit manipulations
41+
ALGORITHM_BROTLI = 4, // For bit manipulations
42+
ALGORITHM_ZSTD = 8
4243
};
4344

4445
enum class RangeRequestCtrl : int {

plugins/compress/misc.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ normalize_accept_encoding(TSHttpTxn /* txnp ATS_UNUSED */, TSMBuffer reqp, TSMLo
8484
bool deflate = false;
8585
bool gzip = false;
8686
bool br = false;
87+
bool zstd = false;
8788
// remove the accept encoding field(s),
8889
// while finding out if gzip or deflate is supported.
8990
while (field) {
@@ -100,6 +101,8 @@ normalize_accept_encoding(TSHttpTxn /* txnp ATS_UNUSED */, TSMBuffer reqp, TSMLo
100101
br = true;
101102
} else if (strcasecmp("deflate", next) == 0) {
102103
deflate = true;
104+
} else if (strcasecmp("zstd", next) == 0) {
105+
zstd = true;
103106
}
104107
}
105108
}
@@ -111,9 +114,13 @@ normalize_accept_encoding(TSHttpTxn /* txnp ATS_UNUSED */, TSMBuffer reqp, TSMLo
111114
}
112115

113116
// append a new accept-encoding field in the header
114-
if (deflate || gzip || br) {
117+
if (deflate || gzip || br || zstd) {
115118
TSMimeHdrFieldCreate(reqp, hdr_loc, &field);
116119
TSMimeHdrFieldNameSet(reqp, hdr_loc, field, TS_MIME_FIELD_ACCEPT_ENCODING, TS_MIME_LEN_ACCEPT_ENCODING);
120+
if (zstd) {
121+
TSMimeHdrFieldValueStringInsert(reqp, hdr_loc, field, -1, "zstd", strlen("zstd"));
122+
info("normalized accept encoding to zstd");
123+
}
117124
if (br) {
118125
TSMimeHdrFieldValueStringInsert(reqp, hdr_loc, field, -1, "br", strlen("br"));
119126
info("normalized accept encoding to br");

plugins/compress/misc.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ enum CompressionType {
4444
COMPRESSION_TYPE_DEFAULT = 0,
4545
COMPRESSION_TYPE_DEFLATE = 1,
4646
COMPRESSION_TYPE_GZIP = 2,
47-
COMPRESSION_TYPE_BROTLI = 4
47+
COMPRESSION_TYPE_BROTLI = 4,
48+
COMPRESSION_TYPE_ZSTD = 8,
4849
};
4950

5051
// this one is used to rename the accept encoding header
@@ -84,13 +85,21 @@ using Data = struct {
8485
#if HAVE_BROTLI_ENCODE_H
8586
b_stream bstrm;
8687
#endif
88+
#if HAVE_ZSTD_H
89+
ZSTD_CCtx *zstd_cctx;
90+
#endif
8791
};
8892

8993
voidpf gzip_alloc(voidpf opaque, uInt items, uInt size);
9094
void gzip_free(voidpf opaque, voidpf address);
9195
void normalize_accept_encoding(TSHttpTxn txnp, TSMBuffer reqp, TSMLoc hdr_loc);
9296
void hide_accept_encoding(TSHttpTxn txnp, TSMBuffer reqp, TSMLoc hdr_loc, const char *hidden_header_name);
9397
void restore_accept_encoding(TSHttpTxn txnp, TSMBuffer reqp, TSMLoc hdr_loc, const char *hidden_header_name);
98+
#if HAVE_ZSTD_H
99+
void zstd_compress_init(Data *data);
100+
void zstd_compress_finish(Data *data);
101+
void zstd_compress_one(Data *data, const char *upstream_buffer, int64_t upstream_length);
102+
#endif
94103
const char *init_hidden_header_name();
95104
int register_plugin();
96105
void log_compression_ratio(int64_t in, int64_t out);

tests/gold_tests/pluginTest/compress/compress.test.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ def get_verify_command(out_path, decrompressor):
170170
tr.ReturnCode = 0
171171
tr.Processes.Default.Command = f"diff {out_path} {orig_path}"
172172

173+
tr = Test.AddTestRun(f'zstd: {i}')
174+
tr.Processes.Default.ReturnCode = 0
175+
out_path = get_out_path()
176+
tr.MakeCurlCommand(curl(ts, i, "zstd", out_path))
177+
tr = Test.AddTestRun(f'verify zstd: {i}')
178+
tr.ReturnCode = 0
179+
tr.Processes.Default.Command = get_verify_command(out_path, "zstd -d")
180+
173181
# Test Accept-Encoding normalization.
174182

175183
tr = Test.AddTestRun()

0 commit comments

Comments
 (0)