Skip to content

Commit 6c927c0

Browse files
xia-chaoaduh95
authored andcommitted
zlib: preserve brotli params and dictionary on reset
ResetStream currently calls Init() with no arguments, which drops the stored dictionary and never replays SetParams. Remember each successful parameter and replay both the dictionary and the params when the stream is reset. Fixes: #66156 Signed-off-by: Xia Chao <shapirolutts@gmail.com> PR-URL: #66157 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent 54e9330 commit 6c927c0

3 files changed

Lines changed: 105 additions & 2 deletions

File tree

‎doc/api/zlib.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,11 +2185,18 @@ Only applicable to deflate algorithm.
21852185

21862186
<!-- YAML
21872187
added: v0.7.0
2188+
changes:
2189+
- version: REPLACEME
2190+
pr-url: https://github.com/nodejs/node/pull/66157
2191+
description: Brotli streams preserve parameters and dictionary on reset.
21882192
-->
21892193

21902194
For inflate and deflate streams, reset the compressor/decompressor to factory
21912195
defaults.
21922196

2197+
For Brotli streams, start a new compression or decompression session while
2198+
preserving the configured parameters and dictionary.
2199+
21932200
For Zstd streams, cancel the current frame and start a new session while
21942201
preserving the configured parameters and dictionary. If `pledgedSrcSize` was
21952202
configured for a Zstd compressor, it applies again to the next frame.

‎src/node_zlib.cc‎

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
#include <cstdlib>
5353
#include <cstring>
5454
#include <optional>
55+
#include <utility>
56+
#include <vector>
5557

5658
namespace node {
5759

@@ -178,6 +180,18 @@ struct CompressionError {
178180
inline bool IsError() const { return code != nullptr; }
179181
};
180182

183+
void RecordBrotliParam(std::vector<std::pair<int, uint32_t>>* params,
184+
int key,
185+
uint32_t value) {
186+
for (auto& entry : *params) {
187+
if (entry.first == key) {
188+
entry.second = value;
189+
return;
190+
}
191+
}
192+
params->emplace_back(key, value);
193+
}
194+
181195
class ZlibContext final : public MemoryRetainer {
182196
public:
183197
ZlibContext() = default;
@@ -286,6 +300,8 @@ class BrotliEncoderContext final : public BrotliContext {
286300
prepared_dictionary_;
287301
// Dictionary data must remain valid while the prepared dictionary is alive.
288302
std::vector<uint8_t> dictionary_;
303+
// Last successful parameters, replayed by ResetStream.
304+
std::vector<std::pair<int, uint32_t>> params_;
289305
};
290306

291307
class BrotliDecoderContext final : public BrotliContext {
@@ -308,6 +324,8 @@ class BrotliDecoderContext final : public BrotliContext {
308324
DeleteFnPtr<BrotliDecoderState, BrotliDecoderDestroyInstance> state_;
309325
// Dictionary data must remain valid for the lifetime of the decoder.
310326
std::vector<uint8_t> dictionary_;
327+
// Last successful parameters, replayed by ResetStream.
328+
std::vector<std::pair<int, uint32_t>> params_;
311329
};
312330

313331
class ZstdContext : public MemoryRetainer {
@@ -1505,6 +1523,7 @@ void BrotliEncoderContext::Close() {
15051523
state_.reset();
15061524
prepared_dictionary_.reset();
15071525
dictionary_.clear();
1526+
params_.clear();
15081527
mode_ = NONE;
15091528
}
15101529

@@ -1518,6 +1537,7 @@ CompressionError BrotliEncoderContext::Init(std::vector<uint8_t>&& dictionary) {
15181537
// Clean up any previous dictionary state before re-initializing.
15191538
prepared_dictionary_.reset();
15201539
dictionary_.clear();
1540+
params_.clear();
15211541

15221542
state_.reset(BrotliEncoderCreateInstance(alloc, free, opaque));
15231543
if (!state_) {
@@ -1557,7 +1577,19 @@ CompressionError BrotliEncoderContext::Init(std::vector<uint8_t>&& dictionary) {
15571577
}
15581578

15591579
CompressionError BrotliEncoderContext::ResetStream() {
1560-
return Init();
1580+
std::vector<uint8_t> dictionary = dictionary_;
1581+
const auto params = params_;
1582+
CompressionError err = Init(std::move(dictionary));
1583+
if (err.IsError()) {
1584+
return err;
1585+
}
1586+
for (const auto& entry : params) {
1587+
err = SetParams(entry.first, entry.second);
1588+
if (err.IsError()) {
1589+
return err;
1590+
}
1591+
}
1592+
return CompressionError{};
15611593
}
15621594

15631595
CompressionError BrotliEncoderContext::SetParams(int key, uint32_t value) {
@@ -1568,6 +1600,7 @@ CompressionError BrotliEncoderContext::SetParams(int key, uint32_t value) {
15681600
"ERR_BROTLI_PARAM_SET_FAILED",
15691601
-1);
15701602
} else {
1603+
RecordBrotliParam(&params_, key, value);
15711604
return CompressionError {};
15721605
}
15731606
}
@@ -1586,6 +1619,7 @@ CompressionError BrotliEncoderContext::GetErrorInfo() const {
15861619
void BrotliDecoderContext::Close() {
15871620
state_.reset();
15881621
dictionary_.clear();
1622+
params_.clear();
15891623
mode_ = NONE;
15901624
}
15911625

@@ -1615,6 +1649,7 @@ CompressionError BrotliDecoderContext::Init(std::vector<uint8_t>&& dictionary) {
16151649

16161650
// Clean up any previous dictionary state before re-initializing.
16171651
dictionary_.clear();
1652+
params_.clear();
16181653

16191654
state_.reset(BrotliDecoderCreateInstance(alloc, free, opaque));
16201655
if (!state_) {
@@ -1642,7 +1677,19 @@ CompressionError BrotliDecoderContext::Init(std::vector<uint8_t>&& dictionary) {
16421677
}
16431678

16441679
CompressionError BrotliDecoderContext::ResetStream() {
1645-
return Init();
1680+
std::vector<uint8_t> dictionary = dictionary_;
1681+
const auto params = params_;
1682+
CompressionError err = Init(std::move(dictionary));
1683+
if (err.IsError()) {
1684+
return err;
1685+
}
1686+
for (const auto& entry : params) {
1687+
err = SetParams(entry.first, entry.second);
1688+
if (err.IsError()) {
1689+
return err;
1690+
}
1691+
}
1692+
return CompressionError{};
16461693
}
16471694

16481695
CompressionError BrotliDecoderContext::SetParams(int key, uint32_t value) {
@@ -1653,6 +1700,7 @@ CompressionError BrotliDecoderContext::SetParams(int key, uint32_t value) {
16531700
"ERR_BROTLI_PARAM_SET_FAILED",
16541701
-1);
16551702
} else {
1703+
RecordBrotliParam(&params_, key, value);
16561704
return CompressionError {};
16571705
}
16581706
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const { finished } = require('stream/promises');
6+
const test = require('node:test');
7+
const zlib = require('zlib');
8+
9+
const dictionary = Buffer.from(
10+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
11+
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
12+
);
13+
const input = Buffer.from(
14+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '.repeat(100),
15+
);
16+
17+
async function collect(stream, ...data) {
18+
const chunks = [];
19+
stream.on('data', (chunk) => chunks.push(chunk));
20+
for (let i = 0; i < data.length - 1; i++) {
21+
stream.write(data[i]);
22+
}
23+
stream.end(data[data.length - 1]);
24+
await finished(stream);
25+
return Buffer.concat(chunks);
26+
}
27+
28+
test('BrotliCompress reset preserves its initial options', async () => {
29+
const options = {
30+
dictionary,
31+
params: {
32+
[zlib.constants.BROTLI_PARAM_QUALITY]: 0,
33+
},
34+
};
35+
const expected = await collect(zlib.createBrotliCompress(options), input);
36+
const reset = zlib.createBrotliCompress(options);
37+
reset.reset();
38+
39+
assert.deepStrictEqual(await collect(reset, input), expected);
40+
});
41+
42+
test('BrotliDecompress reset preserves its dictionary', async () => {
43+
const compressed = zlib.brotliCompressSync(input, { dictionary });
44+
const decompress = zlib.createBrotliDecompress({ dictionary });
45+
decompress.reset();
46+
47+
assert.deepStrictEqual(await collect(decompress, compressed), input);
48+
});

0 commit comments

Comments
 (0)