|
40 | 40 |
|
41 | 41 | #include "brotli/decode.h" |
42 | 42 | #include "brotli/encode.h" |
| 43 | +#include "brotli/shared_dictionary.h" |
43 | 44 | #include "zlib.h" |
44 | 45 | #include "zstd.h" |
45 | 46 | #include "zstd_errors.h" |
@@ -256,7 +257,7 @@ class BrotliEncoderContext final : public BrotliContext { |
256 | 257 | public: |
257 | 258 | void Close(); |
258 | 259 | void DoThreadPoolWork(); |
259 | | - CompressionError Init(); |
| 260 | + CompressionError Init(std::vector<uint8_t>&& dictionary = {}); |
260 | 261 | CompressionError ResetStream(); |
261 | 262 | CompressionError SetParams(int key, uint32_t value); |
262 | 263 | CompressionError GetErrorInfo() const; |
@@ -268,13 +269,18 @@ class BrotliEncoderContext final : public BrotliContext { |
268 | 269 | private: |
269 | 270 | bool last_result_ = false; |
270 | 271 | DeleteFnPtr<BrotliEncoderState, BrotliEncoderDestroyInstance> state_; |
| 272 | + DeleteFnPtr<BrotliEncoderPreparedDictionary, |
| 273 | + BrotliEncoderDestroyPreparedDictionary> |
| 274 | + prepared_dictionary_; |
| 275 | + // Dictionary data must remain valid while the prepared dictionary is alive. |
| 276 | + std::vector<uint8_t> dictionary_; |
271 | 277 | }; |
272 | 278 |
|
273 | 279 | class BrotliDecoderContext final : public BrotliContext { |
274 | 280 | public: |
275 | 281 | void Close(); |
276 | 282 | void DoThreadPoolWork(); |
277 | | - CompressionError Init(); |
| 283 | + CompressionError Init(std::vector<uint8_t>&& dictionary = {}); |
278 | 284 | CompressionError ResetStream(); |
279 | 285 | CompressionError SetParams(int key, uint32_t value); |
280 | 286 | CompressionError GetErrorInfo() const; |
@@ -288,6 +294,8 @@ class BrotliDecoderContext final : public BrotliContext { |
288 | 294 | BrotliDecoderErrorCode error_ = BROTLI_DECODER_NO_ERROR; |
289 | 295 | std::string error_string_; |
290 | 296 | DeleteFnPtr<BrotliDecoderState, BrotliDecoderDestroyInstance> state_; |
| 297 | + // Dictionary data must remain valid for the lifetime of the decoder. |
| 298 | + std::vector<uint8_t> dictionary_; |
291 | 299 | }; |
292 | 300 |
|
293 | 301 | class ZstdContext : public MemoryRetainer { |
@@ -830,7 +838,8 @@ class BrotliCompressionStream final : |
830 | 838 | static void Init(const FunctionCallbackInfo<Value>& args) { |
831 | 839 | BrotliCompressionStream* wrap; |
832 | 840 | ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); |
833 | | - CHECK(args.Length() == 3 && "init(params, writeResult, writeCallback)"); |
| 841 | + CHECK((args.Length() == 3 || args.Length() == 4) && |
| 842 | + "init(params, writeResult, writeCallback[, dictionary])"); |
834 | 843 |
|
835 | 844 | CHECK(args[1]->IsUint32Array()); |
836 | 845 | CHECK_GE(args[1].As<Uint32Array>()->Length(), 2); |
@@ -841,7 +850,18 @@ class BrotliCompressionStream final : |
841 | 850 | wrap->InitStream(write_result, write_js_callback); |
842 | 851 |
|
843 | 852 | AllocScope alloc_scope(wrap); |
844 | | - CompressionError err = wrap->context()->Init(); |
| 853 | + std::vector<uint8_t> dictionary; |
| 854 | + if (args.Length() == 4 && !args[3]->IsUndefined()) { |
| 855 | + if (!args[3]->IsArrayBufferView()) { |
| 856 | + THROW_ERR_INVALID_ARG_TYPE( |
| 857 | + wrap->env(), "dictionary must be an ArrayBufferView if provided"); |
| 858 | + return; |
| 859 | + } |
| 860 | + ArrayBufferViewContents<uint8_t> contents(args[3]); |
| 861 | + dictionary.assign(contents.data(), contents.data() + contents.length()); |
| 862 | + } |
| 863 | + |
| 864 | + CompressionError err = wrap->context()->Init(std::move(dictionary)); |
845 | 865 | if (err.IsError()) { |
846 | 866 | wrap->EmitError(err); |
847 | 867 | // TODO(addaleax): Sometimes we generate better error codes in C++ land, |
@@ -1387,23 +1407,57 @@ void BrotliEncoderContext::DoThreadPoolWork() { |
1387 | 1407 |
|
1388 | 1408 | void BrotliEncoderContext::Close() { |
1389 | 1409 | state_.reset(); |
| 1410 | + prepared_dictionary_.reset(); |
| 1411 | + dictionary_.clear(); |
1390 | 1412 | mode_ = NONE; |
1391 | 1413 | } |
1392 | 1414 |
|
1393 | | -CompressionError BrotliEncoderContext::Init() { |
| 1415 | +CompressionError BrotliEncoderContext::Init(std::vector<uint8_t>&& dictionary) { |
1394 | 1416 | brotli_alloc_func alloc = CompressionStreamMemoryOwner::AllocForBrotli; |
1395 | 1417 | brotli_free_func free = CompressionStreamMemoryOwner::FreeForZlib; |
1396 | 1418 | void* opaque = |
1397 | 1419 | CompressionStream<BrotliEncoderContext>::AllocatorOpaquePointerForContext( |
1398 | 1420 | this); |
| 1421 | + |
| 1422 | + // Clean up any previous dictionary state before re-initializing. |
| 1423 | + prepared_dictionary_.reset(); |
| 1424 | + dictionary_.clear(); |
| 1425 | + |
1399 | 1426 | state_.reset(BrotliEncoderCreateInstance(alloc, free, opaque)); |
1400 | 1427 | if (!state_) { |
1401 | 1428 | return CompressionError("Could not initialize Brotli instance", |
1402 | 1429 | "ERR_ZLIB_INITIALIZATION_FAILED", |
1403 | 1430 | -1); |
1404 | | - } else { |
1405 | | - return CompressionError {}; |
1406 | 1431 | } |
| 1432 | + |
| 1433 | + if (!dictionary.empty()) { |
| 1434 | + // The dictionary data must remain valid for the lifetime of the prepared |
| 1435 | + // dictionary, so take ownership via move. |
| 1436 | + dictionary_ = std::move(dictionary); |
| 1437 | + |
| 1438 | + prepared_dictionary_.reset( |
| 1439 | + BrotliEncoderPrepareDictionary(BROTLI_SHARED_DICTIONARY_RAW, |
| 1440 | + dictionary_.size(), |
| 1441 | + dictionary_.data(), |
| 1442 | + BROTLI_MAX_QUALITY, |
| 1443 | + alloc, |
| 1444 | + free, |
| 1445 | + opaque)); |
| 1446 | + if (!prepared_dictionary_) { |
| 1447 | + return CompressionError("Failed to prepare brotli dictionary", |
| 1448 | + "ERR_ZLIB_DICTIONARY_LOAD_FAILED", |
| 1449 | + -1); |
| 1450 | + } |
| 1451 | + |
| 1452 | + if (!BrotliEncoderAttachPreparedDictionary(state_.get(), |
| 1453 | + prepared_dictionary_.get())) { |
| 1454 | + return CompressionError("Failed to attach brotli dictionary", |
| 1455 | + "ERR_ZLIB_DICTIONARY_LOAD_FAILED", |
| 1456 | + -1); |
| 1457 | + } |
| 1458 | + } |
| 1459 | + |
| 1460 | + return CompressionError{}; |
1407 | 1461 | } |
1408 | 1462 |
|
1409 | 1463 | CompressionError BrotliEncoderContext::ResetStream() { |
@@ -1435,6 +1489,7 @@ CompressionError BrotliEncoderContext::GetErrorInfo() const { |
1435 | 1489 |
|
1436 | 1490 | void BrotliDecoderContext::Close() { |
1437 | 1491 | state_.reset(); |
| 1492 | + dictionary_.clear(); |
1438 | 1493 | mode_ = NONE; |
1439 | 1494 | } |
1440 | 1495 |
|
@@ -1455,20 +1510,39 @@ void BrotliDecoderContext::DoThreadPoolWork() { |
1455 | 1510 | } |
1456 | 1511 | } |
1457 | 1512 |
|
1458 | | -CompressionError BrotliDecoderContext::Init() { |
| 1513 | +CompressionError BrotliDecoderContext::Init(std::vector<uint8_t>&& dictionary) { |
1459 | 1514 | brotli_alloc_func alloc = CompressionStreamMemoryOwner::AllocForBrotli; |
1460 | 1515 | brotli_free_func free = CompressionStreamMemoryOwner::FreeForZlib; |
1461 | 1516 | void* opaque = |
1462 | 1517 | CompressionStream<BrotliDecoderContext>::AllocatorOpaquePointerForContext( |
1463 | 1518 | this); |
| 1519 | + |
| 1520 | + // Clean up any previous dictionary state before re-initializing. |
| 1521 | + dictionary_.clear(); |
| 1522 | + |
1464 | 1523 | state_.reset(BrotliDecoderCreateInstance(alloc, free, opaque)); |
1465 | 1524 | if (!state_) { |
1466 | 1525 | return CompressionError("Could not initialize Brotli instance", |
1467 | 1526 | "ERR_ZLIB_INITIALIZATION_FAILED", |
1468 | 1527 | -1); |
1469 | | - } else { |
1470 | | - return CompressionError {}; |
1471 | 1528 | } |
| 1529 | + |
| 1530 | + if (!dictionary.empty()) { |
| 1531 | + // The dictionary data must remain valid for the lifetime of the decoder, |
| 1532 | + // so take ownership via move. |
| 1533 | + dictionary_ = std::move(dictionary); |
| 1534 | + |
| 1535 | + if (!BrotliDecoderAttachDictionary(state_.get(), |
| 1536 | + BROTLI_SHARED_DICTIONARY_RAW, |
| 1537 | + dictionary_.size(), |
| 1538 | + dictionary_.data())) { |
| 1539 | + return CompressionError("Failed to attach brotli dictionary", |
| 1540 | + "ERR_ZLIB_DICTIONARY_LOAD_FAILED", |
| 1541 | + -1); |
| 1542 | + } |
| 1543 | + } |
| 1544 | + |
| 1545 | + return CompressionError{}; |
1472 | 1546 | } |
1473 | 1547 |
|
1474 | 1548 | CompressionError BrotliDecoderContext::ResetStream() { |
|