1
-#include "rrdpush.h"
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
3
-#ifdef ENABLE_RRDPUSH_COMPRESSION
4
-#include "lz4.h"
3
+#include "compression.h"
4
6
-#define STREAM_COMPRESSION_MSG "STREAM_COMPRESSION"
5
+#include "compression_gzip.h"
6
8
-/*
9
- * Reset compressor state for a new stream
10
- */
11
-void rrdpush_compressor_reset(struct compressor_state *state) {
12
- if(!state->initialized) {
13
- state->initialized = true;
7
+#ifdef ENABLE_LZ4
8
+#include "compression_lz4.h"
9
+#endif
10
15
- state->stream.lz4_stream = LZ4_createStream();
16
- state->stream.input_ring_buffer_size = LZ4_DECODER_RING_BUFFER_SIZE(COMPRESSION_MAX_MSG_SIZE * 2);
17
- state->stream.input_ring_buffer = callocz(1, state->stream.input_ring_buffer_size);
18
- state->compression_result_buffer_size = 0;
19
- }
11
+#ifdef ENABLE_ZSTD
12
+#include "compression_zstd.h"
13
+#endif
14
+
15
+// ----------------------------------------------------------------------------
16
+// compressor public API
17
21
- LZ4_resetStream_fast(state->stream.lz4_stream);
18
+void rrdpush_compressor_init(struct compressor_state *state) {
19
+ switch(state->algorithm) {
20
+#ifdef ENABLE_ZSTD
21
+ case COMPRESSION_ALGORITHM_ZSTD:
22
+ rrdpush_compressor_init_zstd(state);
23
+ break;
24
+#endif
25
+
26
+#ifdef ENABLE_LZ4
27
+ case COMPRESSION_ALGORITHM_LZ4:
28
+ rrdpush_compressor_init_lz4(state);
29
+ break;
30
+#endif
31
23
- state->stream.input_ring_buffer_pos = 0;
32
+ default:
33
+ case COMPRESSION_ALGORITHM_GZIP:
34
+ rrdpush_compressor_init_gzip(state);
35
+ break;
36
+ }
37
+
38
+ simple_ring_buffer_reset(&state->input);
39
+ simple_ring_buffer_reset(&state->output);
40
}
41
26
-/*
27
- * Destroy compressor state and all related data
28
- */
42
void rrdpush_compressor_destroy(struct compressor_state *state) {
30
- if (state->stream.lz4_stream) {
31
- LZ4_freeStream(state->stream.lz4_stream);
32
- state->stream.lz4_stream = NULL;
33
- }
43
+ switch(state->algorithm) {
44
+#ifdef ENABLE_ZSTD
45
+ case COMPRESSION_ALGORITHM_ZSTD:
46
+ rrdpush_compressor_destroy_zstd(state);
47
+ break;
48
+#endif
49
35
- freez(state->stream.input_ring_buffer);
36
- state->stream.input_ring_buffer = NULL;
50
+#ifdef ENABLE_LZ4
51
+ case COMPRESSION_ALGORITHM_LZ4:
52
+ rrdpush_compressor_destroy_lz4(state);
53
+ break;
54
+#endif
55
38
- freez(state->compression_result_buffer);
39
- state->compression_result_buffer = NULL;
56
+ default:
57
+ case COMPRESSION_ALGORITHM_GZIP:
58
+ rrdpush_compressor_destroy_gzip(state);
59
+ break;
60
+ }
61
62
state->initialized = false;
63
+
64
+ simple_ring_buffer_destroy(&state->input);
65
+ simple_ring_buffer_destroy(&state->output);
66
}
67
44
-/*
45
- * Compress the given block of data
46
- * Compressed data will remain in the internal buffer until the next invocation
47
- * Return the size of compressed data block as result and the pointer to internal buffer using the last argument
48
- * or 0 in case of error
49
- */
50
-size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t size, char **out) {
51
- if(unlikely(!state || !size || !out))
52
- return 0;
68
+size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t size, const char **out) {
69
+ size_t ret = 0;
70
54
- if(unlikely(size > COMPRESSION_MAX_MSG_SIZE)) {
55
- netdata_log_error("RRDPUSH COMPRESS: Compression Failed - Message size %lu above compression buffer limit: %d",
56
- (long unsigned int)size, COMPRESSION_MAX_MSG_SIZE);
57
- return 0;
58
- }
71
+ switch(state->algorithm) {
72
+#ifdef ENABLE_ZSTD
73
+ case COMPRESSION_ALGORITHM_ZSTD:
74
+ ret = rrdpush_compress_zstd(state, data, size, out);
75
+ break;
76
+#endif
77
60
- size_t max_dst_size = LZ4_COMPRESSBOUND(size);
61
- size_t data_size = max_dst_size + RRDPUSH_COMPRESSION_SIGNATURE_SIZE;
78
+#ifdef ENABLE_LZ4
79
+ case COMPRESSION_ALGORITHM_LZ4:
80
+ ret = rrdpush_compress_lz4(state, data, size, out);
81
+ break;
82
+#endif
83
63
- if (!state->compression_result_buffer) {
64
- state->compression_result_buffer = mallocz(data_size);
65
- state->compression_result_buffer_size = data_size;
66
- }
67
- else if(unlikely(state->compression_result_buffer_size < data_size)) {
68
- state->compression_result_buffer = reallocz(state->compression_result_buffer, data_size);
69
- state->compression_result_buffer_size = data_size;
84
+ default:
85
+ case COMPRESSION_ALGORITHM_GZIP:
86
+ ret = rrdpush_compress_gzip(state, data, size, out);
87
+ break;
88
}
89
72
- // the ring buffer always has space for LZ4_MAX_MSG_SIZE
73
- memcpy(state->stream.input_ring_buffer + state->stream.input_ring_buffer_pos, data, size);
74
-
75
- // this call needs the last 64K of our previous data
76
- // they are available in the ring buffer
77
- long int compressed_data_size = LZ4_compress_fast_continue(
78
- state->stream.lz4_stream,
79
- state->stream.input_ring_buffer + state->stream.input_ring_buffer_pos,
80
- state->compression_result_buffer + RRDPUSH_COMPRESSION_SIGNATURE_SIZE,
81
- (int)size,
82
- (int)max_dst_size,
83
- 1);
84
-
85
- if (compressed_data_size < 0) {
86
- netdata_log_error("Data compression error: %ld", compressed_data_size);
90
+ if(unlikely(ret >= COMPRESSION_MAX_CHUNK)) {
91
+ netdata_log_error("RRDPUSH_COMPRESS: compressed data is %zu bytes, which is >= than the max chunk size %zu",
92
+ ret, COMPRESSION_MAX_CHUNK);
93
return 0;
94
}
95
90
- // update the next writing position of the ring buffer
91
- state->stream.input_ring_buffer_pos += size;
92
- if(unlikely(state->stream.input_ring_buffer_pos >= state->stream.input_ring_buffer_size - COMPRESSION_MAX_MSG_SIZE))
93
- state->stream.input_ring_buffer_pos = 0;
94
-
95
- // update the signature header
96
- uint32_t len = ((compressed_data_size & 0x7f) | 0x80 | (((compressed_data_size & (0x7f << 7)) << 1) | 0x8000)) << 8;
97
- *(uint32_t *)state->compression_result_buffer = len | RRDPUSH_COMPRESSION_SIGNATURE;
98
- *out = state->compression_result_buffer;
99
- netdata_log_debug(D_STREAM, "%s: Compressed data header: %ld", STREAM_COMPRESSION_MSG, compressed_data_size);
100
- return compressed_data_size + RRDPUSH_COMPRESSION_SIGNATURE_SIZE;
96
+ return ret;
97
}
98
103
-/*
104
- * Decompress the compressed data in the internal buffer
105
- * Return the size of uncompressed data or 0 for error
106
- */
107
-size_t rrdpush_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) {
108
- if (unlikely(!state || !compressed_data || !compressed_size))
109
- return 0;
99
+// ----------------------------------------------------------------------------
100
+// decompressor public API
101
111
- if(unlikely(state->stream.read_at != state->stream.write_at))
112
- fatal("RRDPUSH_DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!");
102
+void rrdpush_decompressor_destroy(struct decompressor_state *state) {
103
+ if(unlikely(!state->initialized))
104
+ return;
105
114
- if (unlikely(state->stream.write_at >= state->stream.size / 2)) {
115
- state->stream.write_at = 0;
116
- state->stream.read_at = 0;
117
- }
106
+ switch(state->algorithm) {
107
+#ifdef ENABLE_ZSTD
108
+ case COMPRESSION_ALGORITHM_ZSTD:
109
+ rrdpush_decompressor_destroy_zstd(state);
110
+ break;
111
+#endif
112
119
- long int decompressed_size = LZ4_decompress_safe_continue(
120
- state->stream.lz4_stream
121
- , compressed_data
122
- , state->stream.buffer + state->stream.write_at
123
- , (int)compressed_size
124
- , (int)(state->stream.size - state->stream.write_at)
125
- );
113
+#ifdef ENABLE_LZ4
114
+ case COMPRESSION_ALGORITHM_LZ4:
115
+ rrdpush_decompressor_destroy_lz4(state);
116
+ break;
117
+#endif
118
127
- if (unlikely(decompressed_size < 0)) {
128
- netdata_log_error("RRDPUSH DECOMPRESS: decompressor returned negative decompressed bytes: %ld", decompressed_size);
129
- return 0;
119
+ default:
120
+ case COMPRESSION_ALGORITHM_GZIP:
121
+ rrdpush_decompressor_destroy_gzip(state);
122
+ break;
123
}
124
132
- if(unlikely(decompressed_size + state->stream.write_at > state->stream.size))
133
- fatal("RRDPUSH DECOMPRESS: decompressor overflown the stream_buffer. size: %zu, pos: %zu, added: %ld, "
134
- "exceeding the buffer by %zu"
135
- , state->stream.size
136
- , state->stream.write_at
137
- , decompressed_size
138
- , (size_t)(state->stream.write_at + decompressed_size - state->stream.size)
139
- );
125
+ simple_ring_buffer_destroy(&state->output);
126
141
- state->stream.write_at += decompressed_size;
127
+ state->initialized = false;
128
+}
129
143
- // statistics
144
- state->total_compressed += compressed_size + RRDPUSH_COMPRESSION_SIGNATURE_SIZE;
145
- state->total_uncompressed += decompressed_size;
146
- state->packet_count++;
130
+void rrdpush_decompressor_init(struct decompressor_state *state) {
131
+ switch(state->algorithm) {
132
+#ifdef ENABLE_ZSTD
133
+ case COMPRESSION_ALGORITHM_ZSTD:
134
+ rrdpush_decompressor_init_zstd(state);
135
+ break;
136
+#endif
137
148
- return decompressed_size;
149
-}
138
+#ifdef ENABLE_LZ4
139
+ case COMPRESSION_ALGORITHM_LZ4:
140
+ rrdpush_decompressor_init_lz4(state);
141
+ break;
142
+#endif
143
151
-void rrdpush_decompressor_reset(struct decompressor_state *state) {
152
- if(!state->initialized) {
153
- state->initialized = true;
154
- state->stream.lz4_stream = LZ4_createStreamDecode();
155
- state->stream.size = LZ4_decoderRingBufferSize(COMPRESSION_MAX_MSG_SIZE) * 2;
156
- state->stream.buffer = mallocz(state->stream.size);
144
+ default:
145
+ case COMPRESSION_ALGORITHM_GZIP:
146
+ rrdpush_decompressor_init_gzip(state);
147
+ break;
148
}
149
159
- LZ4_setStreamDecode(state->stream.lz4_stream, NULL, 0);
160
-
150
state->signature_size = RRDPUSH_COMPRESSION_SIGNATURE_SIZE;
162
- state->stream.write_at = 0;
163
- state->stream.read_at = 0;
151
+ simple_ring_buffer_reset(&state->output);
152
}
153
166
-void rrdpush_decompressor_destroy(struct decompressor_state *state) {
167
- if(unlikely(!state->initialized))
168
- return;
154
+size_t rrdpush_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) {
155
+ if (unlikely(state->output.read_pos != state->output.write_pos))
156
+ fatal("RRDPUSH_DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!");
157
+
158
+ size_t ret = 0;
159
170
- if (state->stream.lz4_stream) {
171
- LZ4_freeStreamDecode(state->stream.lz4_stream);
172
- state->stream.lz4_stream = NULL;
160
+ switch(state->algorithm) {
161
+#ifdef ENABLE_ZSTD
162
+ case COMPRESSION_ALGORITHM_ZSTD:
163
+ ret = rrdpush_decompress_zstd(state, compressed_data, compressed_size);
164
+ break;
165
+#endif
166
+
167
+#ifdef ENABLE_LZ4
168
+ case COMPRESSION_ALGORITHM_LZ4:
169
+ ret = rrdpush_decompress_lz4(state, compressed_data, compressed_size);
170
+ break;
171
+#endif
172
+
173
+ default:
174
+ case COMPRESSION_ALGORITHM_GZIP:
175
+ ret = rrdpush_decompress_gzip(state, compressed_data, compressed_size);
176
+ break;
177
}
178
175
- freez(state->stream.buffer);
176
- state->stream.buffer = NULL;
179
+ // for backwards compatibility we cannot check for COMPRESSION_MAX_MSG_SIZE,
180
+ // because old children may send this big payloads.
181
+ if(unlikely(ret > COMPRESSION_MAX_CHUNK)) {
182
+ netdata_log_error("RRDPUSH_DECOMPRESS: decompressed data is %zu bytes, which is bigger than the max msg size %zu",
183
+ ret, COMPRESSION_MAX_CHUNK);
184
+ return 0;
185
+ }
186
178
- state->initialized = false;
187
+ return ret;
188
}
189
181
-#endif
190
+// ----------------------------------------------------------------------------
191
+// unit test
192
+
193
+int unittest_rrdpush_compression(compression_algorithm_t algorithm, const char *name) {
194
+ fprintf(stderr, "\nTesting streaming compression with %s\n", name);
195
+
196
+ struct compressor_state cctx = {
197
+ .initialized = false,
198
+ .algorithm = algorithm,
199
+ };
200
+ struct decompressor_state dctx = {
201
+ .initialized = false,
202
+ .algorithm = algorithm,
203
+ };
204
+
205
+ char txt[COMPRESSION_MAX_MSG_SIZE];
206
+
207
+ rrdpush_compressor_init(&cctx);
208
+ rrdpush_decompressor_init(&dctx);
209
+
210
+ int errors = 0;
211
+
212
+ memset(txt, '=', COMPRESSION_MAX_MSG_SIZE);
213
+
214
+ for(int i = 0; i < COMPRESSION_MAX_MSG_SIZE ;i++) {
215
+ txt[i] = 'A' + (i % 26);
216
+ size_t txt_len = i + 1;
217
+
218
+ const char *out;
219
+ size_t size = rrdpush_compress(&cctx, txt, txt_len, &out);
220
+
221
+ if(size >= COMPRESSION_MAX_CHUNK) {
222
+ fprintf(stderr, "iteration %d: compressed size %zu exceeds max allowed size\n",
223
+ i, size);
224
+ errors++;
225
+ goto cleanup;
226
+ }
227
+ else {
228
+ size_t dtxt_len = rrdpush_decompress(&dctx, out, size);
229
+ char *dtxt = (char *) &dctx.output.data[dctx.output.read_pos];
230
+
231
+ if(rrdpush_decompressed_bytes_in_buffer(&dctx) != dtxt_len) {
232
+ fprintf(stderr, "iteration %d: decompressed size %zu does not rrdpush_decompressed_bytes_in_buffer() %zu\n",
233
+ i, dtxt_len, rrdpush_decompressed_bytes_in_buffer(&dctx)
234
+ );
235
+ errors++;
236
+ goto cleanup;
237
+ }
238
+
239
+ if(dtxt_len != txt_len) {
240
+ fprintf(stderr, "iteration %d: decompressed size %zu does not match original size %zu\n",
241
+ i, dtxt_len, txt_len
242
+ );
243
+ errors++;
244
+ goto cleanup;
245
+ }
246
+ else {
247
+ if(memcmp(txt, dtxt, txt_len) != 0) {
248
+ txt[txt_len] = '\0';
249
+ dtxt[txt_len + 5] = '\0';
250
+
251
+ fprintf(stderr, "iteration %d: decompressed data '%s' do not match original data '%s' of length %zu\n",
252
+ i, dtxt, txt, txt_len);
253
+ errors++;
254
+ goto cleanup;
255
+ }
256
+ }
257
+ }
258
+
259
+ // fill the compressed buffer with garbage
260
+ memset((void *)out, 'x', size);
261
+
262
+ // here we are supposed to copy the data and advance the position
263
+ dctx.output.read_pos += rrdpush_decompressed_bytes_in_buffer(&dctx);
264
+ }
265
+
266
+cleanup:
267
+ rrdpush_compressor_destroy(&cctx);
268
+ rrdpush_decompressor_destroy(&dctx);
269
+
270
+ if(errors)
271
+ fprintf(stderr, "Compression with %s: FAILED (%d errors)\n", name, errors);
272
+ else
273
+ fprintf(stderr, "Compression with %s: OK\n", name);
274
+
275
+ return errors;
276
+}
277
+
278
+int unittest_rrdpush_compressions(void) {
279
+ int ret = 0;
280
+
281
+ ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_GZIP, "GZIP");
282
+ ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_LZ4, "LZ4");
283
+ ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_ZSTD, "ZSTD");
284
+
285
+ return ret;
286
+}