4040
4141#include " brotli/decode.h"
4242#include " brotli/encode.h"
43+ #include " brotli/shared_dictionary.h"
4344#include " zlib.h"
4445#include " zstd.h"
4546#include " zstd_errors.h"
@@ -256,7 +257,7 @@ class BrotliEncoderContext final : public BrotliContext {
256257 public:
257258 void Close ();
258259 void DoThreadPoolWork ();
259- CompressionError Init ();
260+ CompressionError Init (std::vector< uint8_t >&& dictionary = {} );
260261 CompressionError ResetStream ();
261262 CompressionError SetParams (int key, uint32_t value);
262263 CompressionError GetErrorInfo () const ;
@@ -268,13 +269,18 @@ class BrotliEncoderContext final : public BrotliContext {
268269 private:
269270 bool last_result_ = false ;
270271 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_;
271277};
272278
273279class BrotliDecoderContext final : public BrotliContext {
274280 public:
275281 void Close ();
276282 void DoThreadPoolWork ();
277- CompressionError Init ();
283+ CompressionError Init (std::vector< uint8_t >&& dictionary = {} );
278284 CompressionError ResetStream ();
279285 CompressionError SetParams (int key, uint32_t value);
280286 CompressionError GetErrorInfo () const ;
@@ -288,6 +294,8 @@ class BrotliDecoderContext final : public BrotliContext {
288294 BrotliDecoderErrorCode error_ = BROTLI_DECODER_NO_ERROR;
289295 std::string error_string_;
290296 DeleteFnPtr<BrotliDecoderState, BrotliDecoderDestroyInstance> state_;
297+ // Dictionary data must remain valid for the lifetime of the decoder.
298+ std::vector<uint8_t > dictionary_;
291299};
292300
293301class ZstdContext : public MemoryRetainer {
@@ -830,7 +838,8 @@ class BrotliCompressionStream final :
830838 static void Init (const FunctionCallbackInfo<Value>& args) {
831839 BrotliCompressionStream* wrap;
832840 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])" );
834843
835844 CHECK (args[1 ]->IsUint32Array ());
836845 CHECK_GE (args[1 ].As <Uint32Array>()->Length (), 2 );
@@ -841,7 +850,18 @@ class BrotliCompressionStream final :
841850 wrap->InitStream (write_result, write_js_callback);
842851
843852 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));
845865 if (err.IsError ()) {
846866 wrap->EmitError (err);
847867 // TODO(addaleax): Sometimes we generate better error codes in C++ land,
@@ -1387,23 +1407,57 @@ void BrotliEncoderContext::DoThreadPoolWork() {
13871407
13881408void BrotliEncoderContext::Close () {
13891409 state_.reset ();
1410+ prepared_dictionary_.reset ();
1411+ dictionary_.clear ();
13901412 mode_ = NONE;
13911413}
13921414
1393- CompressionError BrotliEncoderContext::Init () {
1415+ CompressionError BrotliEncoderContext::Init (std::vector< uint8_t >&& dictionary ) {
13941416 brotli_alloc_func alloc = CompressionStreamMemoryOwner::AllocForBrotli;
13951417 brotli_free_func free = CompressionStreamMemoryOwner::FreeForZlib;
13961418 void * opaque =
13971419 CompressionStream<BrotliEncoderContext>::AllocatorOpaquePointerForContext (
13981420 this );
1421+
1422+ // Clean up any previous dictionary state before re-initializing.
1423+ prepared_dictionary_.reset ();
1424+ dictionary_.clear ();
1425+
13991426 state_.reset (BrotliEncoderCreateInstance (alloc, free, opaque));
14001427 if (!state_) {
14011428 return CompressionError (" Could not initialize Brotli instance" ,
14021429 " ERR_ZLIB_INITIALIZATION_FAILED" ,
14031430 -1 );
1404- } else {
1405- return CompressionError {};
14061431 }
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{};
14071461}
14081462
14091463CompressionError BrotliEncoderContext::ResetStream () {
@@ -1435,6 +1489,7 @@ CompressionError BrotliEncoderContext::GetErrorInfo() const {
14351489
14361490void BrotliDecoderContext::Close () {
14371491 state_.reset ();
1492+ dictionary_.clear ();
14381493 mode_ = NONE;
14391494}
14401495
@@ -1455,20 +1510,39 @@ void BrotliDecoderContext::DoThreadPoolWork() {
14551510 }
14561511}
14571512
1458- CompressionError BrotliDecoderContext::Init () {
1513+ CompressionError BrotliDecoderContext::Init (std::vector< uint8_t >&& dictionary ) {
14591514 brotli_alloc_func alloc = CompressionStreamMemoryOwner::AllocForBrotli;
14601515 brotli_free_func free = CompressionStreamMemoryOwner::FreeForZlib;
14611516 void * opaque =
14621517 CompressionStream<BrotliDecoderContext>::AllocatorOpaquePointerForContext (
14631518 this );
1519+
1520+ // Clean up any previous dictionary state before re-initializing.
1521+ dictionary_.clear ();
1522+
14641523 state_.reset (BrotliDecoderCreateInstance (alloc, free, opaque));
14651524 if (!state_) {
14661525 return CompressionError (" Could not initialize Brotli instance" ,
14671526 " ERR_ZLIB_INITIALIZATION_FAILED" ,
14681527 -1 );
1469- } else {
1470- return CompressionError {};
14711528 }
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{};
14721546}
14731547
14741548CompressionError BrotliDecoderContext::ResetStream () {
0 commit comments