node-liblzma 1.1.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.claude/settings.local.json +92 -0
  2. package/.gitattributes +3 -0
  3. package/.release-it.json +6 -0
  4. package/CHANGELOG.md +209 -0
  5. package/History.md +20 -0
  6. package/README.md +750 -30
  7. package/RELEASING.md +131 -0
  8. package/binding.gyp +159 -438
  9. package/biome.json +81 -0
  10. package/coverage/base.css +224 -0
  11. package/coverage/block-navigation.js +87 -0
  12. package/coverage/errors.ts.html +586 -0
  13. package/coverage/favicon.png +0 -0
  14. package/coverage/index.html +146 -0
  15. package/coverage/lcov-report/base.css +224 -0
  16. package/coverage/lcov-report/block-navigation.js +87 -0
  17. package/coverage/lcov-report/errors.ts.html +586 -0
  18. package/coverage/lcov-report/favicon.png +0 -0
  19. package/coverage/lcov-report/index.html +146 -0
  20. package/coverage/lcov-report/lzma.ts.html +2596 -0
  21. package/coverage/lcov-report/pool.ts.html +769 -0
  22. package/coverage/lcov-report/prettify.css +1 -0
  23. package/coverage/lcov-report/prettify.js +2 -0
  24. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  25. package/coverage/lcov-report/sorter.js +210 -0
  26. package/coverage/lcov.info +636 -0
  27. package/coverage/lzma.ts.html +2596 -0
  28. package/coverage/pool.ts.html +769 -0
  29. package/coverage/prettify.css +1 -0
  30. package/coverage/prettify.js +2 -0
  31. package/coverage/sort-arrow-sprite.png +0 -0
  32. package/coverage/sorter.js +210 -0
  33. package/coverage-reports/assets/monocart-coverage-app.js +2 -0
  34. package/coverage-reports/coverage-data.js +1 -0
  35. package/coverage-reports/index.html +48 -0
  36. package/err.log +26 -0
  37. package/index.d.ts +254 -0
  38. package/lib/errors.d.ts +72 -0
  39. package/lib/errors.d.ts.map +1 -0
  40. package/lib/errors.js +153 -0
  41. package/lib/errors.js.map +1 -0
  42. package/lib/lzma.d.ts +245 -0
  43. package/lib/lzma.d.ts.map +1 -0
  44. package/lib/lzma.js +626 -345
  45. package/lib/lzma.js.map +1 -0
  46. package/lib/pool.d.ts +123 -0
  47. package/lib/pool.d.ts.map +1 -0
  48. package/lib/pool.js +188 -0
  49. package/lib/pool.js.map +1 -0
  50. package/lib/types.d.ts +27 -0
  51. package/lib/types.d.ts.map +1 -0
  52. package/lib/types.js +5 -0
  53. package/lib/types.js.map +1 -0
  54. package/package.json +60 -21
  55. package/pnpm-workspace.yaml +3 -0
  56. package/scripts/analyze-coverage.js +132 -0
  57. package/scripts/build_xz_with_cmake.py +390 -0
  58. package/scripts/compare-coverage-tools.js +93 -0
  59. package/scripts/copy_dll.py +51 -0
  60. package/scripts/download_xz_from_github.py +375 -0
  61. package/src/bindings/node-liblzma.cpp +411 -229
  62. package/src/bindings/node-liblzma.hpp +101 -48
  63. package/src/errors.ts +167 -0
  64. package/src/lzma.ts +839 -0
  65. package/src/pool.ts +228 -0
  66. package/src/types.ts +30 -0
  67. package/tsconfig.json +50 -0
  68. package/vitest.config.istanbul.ts +29 -0
  69. package/vitest.config.monocart.ts +44 -0
  70. package/vitest.config.ts +44 -0
  71. package/xz-version.json +8 -0
  72. package/prebuilds/darwin-x64/node.napi.node +0 -0
  73. package/prebuilds/linux-x64/node.napi.node +0 -0
  74. package/prebuilds/win32-x64/node.napi.node +0 -0
  75. package/scripts/download_extract_deps.py +0 -29
  76. package/src/lzma.coffee +0 -344
@@ -1,34 +1,38 @@
1
1
  /**
2
- * node-liblzma - Node.js bindings for liblzma
3
- * Copyright (C) Olivier Orabona <olivier.orabona@gmail.com>
4
- *
5
- * This program is free software: you can redistribute it and/or modify
6
- * it under the terms of the GNU Lesser General Public License as published by
7
- * the Free Software Foundation, either version 3 of the License, or
8
- * (at your option) any later version.
9
- *
10
- * This program is distributed in the hope that it will be useful,
11
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
- * GNU General Public License for more details.
14
- *
15
- * You should have received a copy of the GNU Lesser General Public License
16
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
- **/
2
+ * node-liblzma - Node.js bindings for liblzma
3
+ * Copyright (C) Olivier Orabona <olivier.orabona@gmail.com>
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Lesser General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public License
16
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ **/
18
18
 
19
- #include "node-liblzma.hpp"
20
19
  #include <node_buffer.h>
20
+ #include <vector>
21
+ #include "node-liblzma.hpp"
21
22
 
22
- Napi::Value LZMA::Close(const Napi::CallbackInfo &info) {
23
+ Napi::Value LZMA::Close(const Napi::CallbackInfo &info)
24
+ {
23
25
  Napi::Env env = info.Env();
24
26
 
25
27
  return LZMA::Close(env);
26
28
  }
27
29
 
28
- Napi::Value LZMA::Close(const Napi::Env &env) {
30
+ Napi::Value LZMA::Close(const Napi::Env &env)
31
+ {
29
32
  Napi::MemoryManagement::AdjustExternalMemory(env, -int64_t(sizeof(LZMA)));
30
33
 
31
- if(_wip) {
34
+ if (_wip)
35
+ {
32
36
  _pending_close = true;
33
37
  return env.Undefined();
34
38
  }
@@ -40,301 +44,479 @@ Napi::Value LZMA::Close(const Napi::Env &env) {
40
44
  return env.Undefined();
41
45
  }
42
46
 
43
- void LZMA::Init(Napi::Env env, Napi::Object exports) {
47
+ void LZMA::Init(Napi::Env env, Napi::Object exports)
48
+ {
44
49
  Napi::Function func =
45
50
  DefineClass(env,
46
- "LZMA",
47
- {
48
- InstanceMethod("code", &LZMA::Code<true>),
49
- InstanceMethod("codeSync", &LZMA::Code<false>),
50
- InstanceMethod("close", &LZMA::Close)
51
- });
52
-
53
- Napi::FunctionReference* constructor = new Napi::FunctionReference();
51
+ "LZMA",
52
+ {InstanceMethod("code", &LZMA::Code<true>),
53
+ InstanceMethod("codeSync", &LZMA::Code<false>),
54
+ InstanceMethod("close", &LZMA::Close)});
55
+
56
+ auto constructor = std::make_unique<Napi::FunctionReference>();
54
57
  *constructor = Napi::Persistent(func);
55
- env.SetInstanceData(constructor);
58
+
59
+ // SetInstanceData with automatic cleanup (node-addon-api v8.5.0+)
60
+ env.SetInstanceData<Napi::FunctionReference>(constructor.release());
56
61
 
57
62
  exports.Set("LZMA", func);
58
63
  }
59
64
 
60
- LZMA::LZMA(const Napi::CallbackInfo& info) : Napi::ObjectWrap<LZMA>(info), _stream(LZMA_STREAM_INIT),
61
- _wip(false), _pending_close(false), _worker(nullptr)
65
+ LZMA::LZMA(const Napi::CallbackInfo &info) : Napi::ObjectWrap<LZMA>(info), _stream(LZMA_STREAM_INIT),
66
+ _wip(false), _pending_close(false), _worker(nullptr)
62
67
  {
63
68
  Napi::Env env = info.Env();
64
69
 
65
- if( info.Length() != 2 ) {
66
- Napi::TypeError::New(env, "Wrong number of arguments, expected mode(int) and opts(object)").ThrowAsJavaScriptException();
67
- return;
68
- }
69
-
70
- uint32_t mode = info[0].ToNumber().Uint32Value();
71
-
72
- if (!info[1].IsObject()) {
73
- Napi::TypeError::New(env, "Expected object as second argument").ThrowAsJavaScriptException();
70
+ // Validate constructor arguments
71
+ uint32_t mode;
72
+ Napi::Object opts;
73
+ if (!ValidateConstructorArgs(info, mode, opts))
74
+ {
74
75
  return;
75
76
  }
76
77
 
77
- Napi::Object opts = info[1].ToObject();
78
-
78
+ // Validate and extract options
79
79
  Napi::Value optsCheck = opts.Get("check");
80
- if (!optsCheck.IsNumber()) {
80
+ if (!optsCheck.IsNumber())
81
+ {
81
82
  Napi::TypeError::New(env, "Expected 'check' to be an integer").ThrowAsJavaScriptException();
82
83
  return;
83
84
  }
84
-
85
85
  lzma_check check = static_cast<lzma_check>(optsCheck.ToNumber().Uint32Value());
86
86
 
87
87
  Napi::Value optsPreset = opts.Get("preset");
88
- if (!optsPreset.IsNumber()) {
88
+ if (!optsPreset.IsNumber())
89
+ {
89
90
  Napi::TypeError::New(env, "Expected 'preset' to be an integer").ThrowAsJavaScriptException();
90
91
  return;
91
92
  }
92
-
93
93
  uint32_t preset = optsPreset.ToNumber().Uint32Value();
94
94
 
95
- Napi::Value optsFilters = opts.Get("filters");
96
- if (!optsFilters.IsArray()) {
97
- Napi::TypeError::New(env, "Expected 'filters' to be an array").ThrowAsJavaScriptException();
95
+ // Initialize filters
96
+ if (!InitializeFilters(opts, preset))
97
+ {
98
98
  return;
99
99
  }
100
100
 
101
- Napi::Array filters_handle = optsFilters.As<Napi::Array>();
102
-
103
- uint32_t filters_len = filters_handle.Length();
104
-
105
- // We will need to add LZMA_VLI_UNKNOWN after, so user defined filters may
106
- // not exceed LZMA_FILTERS_MAX - 1.
107
- if( filters_len > LZMA_FILTERS_MAX - 1) {
108
- Napi::RangeError::New(env, "More filters than allowed maximum").ThrowAsJavaScriptException();
101
+ // Initialize encoder or decoder based on mode
102
+ bool success = false;
103
+ switch (mode)
104
+ {
105
+ case STREAM_DECODE:
106
+ success = InitializeDecoder();
107
+ break;
108
+ case STREAM_ENCODE:
109
+ success = InitializeEncoder(opts, preset, check);
110
+ break;
111
+ default:
112
+ Napi::Error::New(env, "Invalid stream mode").ThrowAsJavaScriptException();
109
113
  return;
110
114
  }
111
115
 
112
- lzma_options_lzma opt_lzma2;
113
- if( lzma_lzma_preset(&opt_lzma2, preset) ) {
114
- Napi::Error::New(env, "Unsupported preset, possibly a bug").ThrowAsJavaScriptException();
116
+ if (!success)
117
+ {
115
118
  return;
116
119
  }
117
120
 
118
- // Add extra slot for LZMA_VLI_UNKNOWN.
119
- this->filters = new lzma_filter[filters_len + 1];
120
-
121
- for(uint32_t i = 0; i < filters_len; ++i) {
122
- Napi::Value filter = filters_handle.Get(i);
123
- if (!filter.IsNumber()) {
124
- Napi::Error::New(env, "Filter must be an integer").ThrowAsJavaScriptException();
125
- return;
126
- }
121
+ // Register external memory
122
+ Napi::MemoryManagement::AdjustExternalMemory(env, sizeof(LZMA));
123
+ }
127
124
 
128
- uint64_t current = filter.ToNumber().Uint32Value();
129
- filters[i].id = current;
130
- if( current == LZMA_FILTER_LZMA2 ) {
131
- filters[i].options = &opt_lzma2;
132
- } else {
133
- filters[i].options = nullptr;
134
- }
125
+ LZMA::~LZMA()
126
+ {
127
+ // Release any Buffer references
128
+ if (!_in_buf_ref.IsEmpty())
129
+ _in_buf_ref.Reset();
130
+ if (!_out_buf_ref.IsEmpty())
131
+ _out_buf_ref.Reset();
132
+
133
+ // Cleanup worker if still active
134
+ if (_worker != nullptr)
135
+ {
136
+ _worker = nullptr; // Worker will clean itself up
135
137
  }
136
138
 
137
- filters[filters_len] = { .id = LZMA_VLI_UNKNOWN, .options = nullptr };
139
+ // Smart pointer will automatically cleanup filter array
140
+ // filters.reset() is called automatically
138
141
 
139
- lzma_ret ret;
140
- switch(mode) {
141
- case STREAM_DECODE: {
142
- ret = lzma_stream_decoder(&this->_stream, UINT64_MAX, check);
143
- break;
144
- }
145
- case STREAM_ENCODE: {
146
- Napi::Value optsThreads = opts.Get("threads");
147
- if (!optsThreads.IsNumber()) {
148
- Napi::Error::New(env, "Threads must be an integer");
149
- return;
150
- }
142
+ // Ensure LZMA stream is properly cleaned up
143
+ lzma_end(&_stream);
144
+ }
151
145
 
152
- #ifdef ENABLE_THREAD_SUPPORT
153
- unsigned int threads = optsThreads.ToNumber().Uint32Value();
154
- #pragma GCC diagnostic push
155
- #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
146
+ /**<
147
+ * \brief Do the encoding/decoding with async support
148
+ *
149
+ * Function prototype is (sync):
150
+ * .codeSync flushFlag, input_buffer, input_offset, output_buffer, output_offset, callback
151
+ * Function prototype is (async):
152
+ * .code flushFlag, input_buffer, input_offset, availInBefore, output_buffer, output_offset, callback
153
+ *
154
+ * Where:
155
+ * flushFlag: type: Uint32
156
+ * input_buffer: type: Buffer
157
+ * input_offset: type: Uint32
158
+ * availInBefore: type: Uint32
159
+ * output_buffer: type: Buffer
160
+ * output_offset: type: Uint32
161
+ * callback: type: Function
162
+ */
163
+ template <bool async>
164
+ Napi::Value LZMA::Code(const Napi::CallbackInfo &info)
165
+ {
166
+ // Setup with manual guard (safer than RAII for JS exceptions)
167
+ this->_wip = true;
168
+ this->Ref();
156
169
 
157
- lzma_mt mt = {
158
- .flags = 0,
159
- .threads = threads,
160
- .block_size = 0,
161
- .timeout = 0,
162
- .preset = preset,
163
- .filters = filters,
164
- .check = check,
165
- };
170
+ // Validate and prepare buffers
171
+ BufferContext ctx;
172
+ if (!ValidateAndPrepareBuffers<async>(info, ctx))
173
+ {
174
+ this->_wip = false;
175
+ this->Unref();
176
+ return info.Env().Undefined();
177
+ }
166
178
 
167
- #pragma GCC diagnostic pop
179
+ // Configure stream with prepared buffers
180
+ this->_stream.next_in = ctx.in;
181
+ this->_stream.avail_in = ctx.in_len;
182
+ this->_stream.next_out = ctx.out;
183
+ this->_stream.avail_out = ctx.out_len;
168
184
 
169
- if( threads > 1 ) {
170
- ret = lzma_stream_encoder_mt(&this->_stream, &mt);
171
- } else {
172
- ret = lzma_stream_encoder(&this->_stream, filters, check);
173
- }
174
- #else
175
- ret = lzma_stream_encoder(&this->_stream, filters, check);
176
- #endif
177
- break;
178
- }
179
- default:
180
- ret = LZMA_OPTIONS_ERROR;
185
+ // Execute based on mode with minimal branching
186
+ if constexpr (async)
187
+ {
188
+ return StartAsyncWork(info);
181
189
  }
182
-
183
- if (ret != LZMA_OK) {
184
- Napi::Error::New(env, "LZMA failure, returned " + std::to_string(ret));
185
- return;
190
+ else
191
+ {
192
+ return ExecuteSyncWork(info);
186
193
  }
194
+ }
187
195
 
188
- Napi::MemoryManagement::AdjustExternalMemory(env, sizeof(LZMA));
196
+ void LZMA::Process(LZMA *obj)
197
+ {
198
+ // the real work is done here :)
199
+ // Note: _wip should already be set by the caller
200
+ obj->_ret = lzma_code(&(obj->_stream), obj->_action);
189
201
  }
190
202
 
191
- /**<
192
- * \brief Do the encoding/decoding with async support
193
- *
194
- * Function prototype is (sync):
195
- * .codeSync flushFlag, input_buffer, input_offset, output_buffer, output_offset, callback
196
- * Function prototype is (async):
197
- * .code flushFlag, input_buffer, input_offset, availInBefore, output_buffer, output_offset, callback
198
- *
199
- * Where:
200
- * flushFlag: type: Uint32
201
- * input_buffer: type: Buffer
202
- * input_offset: type: Uint32
203
- * availInBefore: type: Uint32
204
- * output_buffer: type: Buffer
205
- * output_offset: type: Uint32
206
- * callback: type: Function
207
- */
208
- template<bool async>
209
- Napi::Value LZMA::Code(const Napi::CallbackInfo &info) {
203
+ Napi::Value LZMA::AfterSync(const Napi::CallbackInfo &info, LZMA *obj)
204
+ {
210
205
  Napi::Env env = info.Env();
206
+ return obj->After(env);
207
+ }
211
208
 
212
- this->_wip = true;
213
- this->Ref();
214
-
215
- // Neat trick but that does the job :)
216
- if (info.Length() != 6+(int)async) {
217
- Napi::Error::New(env, "BUG?: LZMA::Code requires all these arguments: "
218
- "flushFlag, input_buffer, input_offset, availInBefore, "
219
- "output_buffer, output_offset, [callback]").ThrowAsJavaScriptException();
220
- return env.Undefined();
209
+ template <bool async>
210
+ bool LZMA::ValidateAndPrepareBuffers(const Napi::CallbackInfo &info, BufferContext &ctx)
211
+ {
212
+ Napi::Env env = info.Env();
213
+ constexpr int expected_args = async ? ASYNC_PARAM_COUNT : SYNC_PARAM_COUNT;
214
+ // Maximum buffer size: 512MB to prevent DoS via resource exhaustion
215
+ constexpr size_t MAX_BUFFER_SIZE = 512UL * 1024 * 1024;
216
+
217
+ // Validate parameter count
218
+ if (info.Length() != expected_args)
219
+ {
220
+ std::string msg = async
221
+ ? "Invalid arguments: LZMA::Code requires 7 arguments (flushFlag, input_buffer, input_offset, availInBefore, output_buffer, output_offset, callback)"
222
+ : "Invalid arguments: LZMA::Code requires 6 arguments (flushFlag, input_buffer, input_offset, availInBefore, output_buffer, output_offset)";
223
+ Napi::Error::New(env, msg).ThrowAsJavaScriptException();
224
+ return false;
221
225
  }
222
226
 
223
- if (!info[0].IsNumber()) {
224
- Napi::Error::New(env, "flushFlag must be an integer");
225
- return env.Undefined();
227
+ // Validate flush flag
228
+ if (!info[0].IsNumber())
229
+ {
230
+ Napi::Error::New(env, "flushFlag must be an integer").ThrowAsJavaScriptException();
231
+ return false;
226
232
  }
227
233
  this->_action = static_cast<lzma_action>(info[0].ToNumber().Uint32Value());
228
234
 
229
- // Evaluate parameters passed to us
230
- const uint8_t *in;
231
- uint8_t *out;
232
- size_t in_off, in_len, out_off, out_len;
233
-
234
- // If we do not have input buffer data
235
- if (info[1].IsNull()) {
236
- // just a flush
237
- // uint8_t nada[1] = { 0 };
238
- uint8_t nada = 0;
239
- in = &nada;
240
- in_len = 0;
241
- in_off = 0;
242
- } else {
243
- // if (!node::Buffer::HasInstance(info[1])) {
244
- if (!info[1].IsBuffer()) {
245
- Napi::TypeError::New(env, "BUG?: LZMA::Code 'input_buffer' argument must be a Buffer").ThrowAsJavaScriptException();
246
- return env.Undefined();
235
+ // Handle input buffer (can be null for flush operations)
236
+ if (info[1].IsNull())
237
+ {
238
+ ctx.in = nullptr;
239
+ ctx.in_len = 0;
240
+ ctx.in_off = 0;
241
+ }
242
+ else
243
+ {
244
+ if (!info[1].IsBuffer())
245
+ {
246
+ Napi::TypeError::New(env, "Invalid argument: 'input_buffer' must be a Buffer").ThrowAsJavaScriptException();
247
+ return false;
247
248
  }
248
249
 
249
250
  uint8_t *in_buf = info[1].As<Napi::Buffer<uint8_t>>().Data();
250
- in_off = info[2].ToNumber().Uint32Value();
251
- in_len = info[3].ToNumber().Uint32Value();
251
+ ctx.in_off = info[2].ToNumber().Uint32Value();
252
+ ctx.in_len = info[3].ToNumber().Uint32Value();
252
253
  size_t in_max = info[1].As<Napi::Buffer<uint8_t>>().Length();
253
254
 
254
- if(!node::Buffer::IsWithinBounds(in_off, in_len, in_max)) {
255
- Napi::Error::New(env, "Offset out of bounds!").ThrowAsJavaScriptException();
256
- return env.Undefined();
255
+ // Validate buffer size limit
256
+ if (in_max > MAX_BUFFER_SIZE)
257
+ {
258
+ Napi::RangeError::New(env, "Input buffer exceeds maximum size of 512MB").ThrowAsJavaScriptException();
259
+ return false;
260
+ }
261
+
262
+ if (!node::Buffer::IsWithinBounds(ctx.in_off, ctx.in_len, in_max))
263
+ {
264
+ Napi::Error::New(env, "Input offset out of bounds!").ThrowAsJavaScriptException();
265
+ return false;
257
266
  }
258
- in = in_buf + in_off;
267
+ ctx.in = in_buf + ctx.in_off;
259
268
  }
260
269
 
261
- // Check if output buffer is also a Buffer
262
- if( !info[4].IsBuffer() ) {
263
- Napi::TypeError::New(env, "BUG?: LZMA::Code 'output_buffer' argument must be a Buffer").ThrowAsJavaScriptException();
264
- return env.Undefined();
270
+ // Handle output buffer (required)
271
+ if (!info[4].IsBuffer())
272
+ {
273
+ Napi::TypeError::New(env, "Invalid argument: 'output_buffer' must be a Buffer").ThrowAsJavaScriptException();
274
+ return false;
265
275
  }
266
276
 
267
277
  uint8_t *out_buf = info[4].As<Napi::Buffer<uint8_t>>().Data();
268
- out_off = info[5].ToNumber().Uint32Value();
269
- out_len = info[4].As<Napi::Buffer<uint8_t>>().Length() - out_off;
270
- out = out_buf + out_off;
278
+ size_t out_max = info[4].As<Napi::Buffer<uint8_t>>().Length();
271
279
 
272
- // Only if async mode is enabled shall we need a callback function
273
- if(async) {
274
- this->_callback = Napi::Persistent(info[6].As<Napi::Function>());
280
+ // Validate output buffer size limit
281
+ if (out_max > MAX_BUFFER_SIZE)
282
+ {
283
+ Napi::RangeError::New(env, "Output buffer exceeds maximum size of 512MB").ThrowAsJavaScriptException();
284
+ return false;
275
285
  }
276
286
 
277
- this->_stream.next_in = in;
278
- this->_stream.avail_in = in_len;
279
- this->_stream.next_out = out;
280
- this->_stream.avail_out = out_len;
281
-
282
- // do it synchronously
283
- if(async) {
284
- this->_worker = new LZMAWorker(env, this);
285
- this->_worker->Queue();
286
- } else {
287
- Process(this);
288
- return AfterSync(info, this);
287
+ ctx.out_off = info[5].ToNumber().Uint32Value();
288
+ ctx.out_len = out_max - ctx.out_off;
289
+ ctx.out = out_buf + ctx.out_off;
290
+
291
+ // Validate callback for async mode
292
+ if constexpr (async)
293
+ {
294
+ if (!info[6].IsFunction())
295
+ {
296
+ Napi::TypeError::New(env, "Invalid argument: 'callback' must be a Function").ThrowAsJavaScriptException();
297
+ return false;
298
+ }
289
299
  }
290
300
 
291
- // otherwise queue work, make sure we get our work done by first calling Process and then After
292
- // napi_create_async_work(uv_default_loop(), &(this->_req), LZMA::Process, LZMA::After);
293
- return env.Undefined();
301
+ return true;
294
302
  }
295
303
 
296
- void LZMA::Process(LZMA* obj) {
297
- // the real work is done here :)
298
- obj->_wip = true;
299
- obj->_ret = lzma_code(&(obj->_stream), obj->_action);
304
+ Napi::Value LZMA::StartAsyncWork(const Napi::CallbackInfo &info)
305
+ {
306
+ // Persist buffers so V8 GC can't free them while the async worker runs
307
+ if (!info[1].IsNull() && info[1].IsBuffer())
308
+ {
309
+ this->_in_buf_ref = Napi::Persistent(info[1].As<Napi::Buffer<uint8_t>>());
310
+ }
311
+ this->_out_buf_ref = Napi::Persistent(info[4].As<Napi::Buffer<uint8_t>>());
312
+
313
+ // Use the real callback for the worker to own and call
314
+ this->_worker = new LZMAWorker(info.Env(), this, info[6].As<Napi::Function>());
315
+ this->_worker->Queue();
316
+
317
+ return info.Env().Undefined();
318
+ }
319
+
320
+ Napi::Value LZMA::ExecuteSyncWork(const Napi::CallbackInfo &info)
321
+ {
322
+ Process(this);
323
+ return AfterSync(info, this);
324
+ }
325
+
326
+ bool LZMA::ValidateConstructorArgs(const Napi::CallbackInfo &info, uint32_t &mode, Napi::Object &opts)
327
+ {
328
+ Napi::Env env = info.Env();
329
+
330
+ if (info.Length() != 2)
331
+ {
332
+ Napi::TypeError::New(env, "Wrong number of arguments, expected mode(int) and opts(object)").ThrowAsJavaScriptException();
333
+ return false;
334
+ }
335
+
336
+ if (!info[0].IsNumber())
337
+ {
338
+ Napi::TypeError::New(env, "Expected mode to be an integer").ThrowAsJavaScriptException();
339
+ return false;
340
+ }
341
+ mode = info[0].ToNumber().Uint32Value();
342
+
343
+ if (!info[1].IsObject())
344
+ {
345
+ Napi::TypeError::New(env, "Expected object as second argument").ThrowAsJavaScriptException();
346
+ return false;
347
+ }
348
+ opts = info[1].ToObject();
349
+
350
+ return true;
351
+ }
352
+
353
+ bool LZMA::InitializeFilters(const Napi::Object &opts, uint32_t preset)
354
+ {
355
+ Napi::Env env = opts.Env();
356
+
357
+ // Validate and get filters array
358
+ Napi::Value optsFilters = opts.Get("filters");
359
+ if (!optsFilters.IsArray())
360
+ {
361
+ Napi::TypeError::New(env, "Expected 'filters' to be an array").ThrowAsJavaScriptException();
362
+ return false;
363
+ }
364
+
365
+ Napi::Array filters_handle = optsFilters.As<Napi::Array>();
366
+ uint32_t filters_len = filters_handle.Length();
367
+
368
+ // Validate filter count
369
+ if (filters_len > LZMA_FILTERS_MAX - 1)
370
+ {
371
+ Napi::RangeError::New(env, "More filters than allowed maximum").ThrowAsJavaScriptException();
372
+ return false;
373
+ }
374
+
375
+ // Initialize LZMA2 options from preset
376
+ if (lzma_lzma_preset(&this->_opt_lzma2, preset))
377
+ {
378
+ Napi::Error::New(env, "Unsupported preset, possibly a bug").ThrowAsJavaScriptException();
379
+ return false;
380
+ }
381
+
382
+ // Allocate filter array with smart pointer
383
+ this->filters = std::make_unique<lzma_filter[]>(filters_len + 1);
384
+
385
+ // Configure filters
386
+ for (uint32_t i = 0; i < filters_len; ++i)
387
+ {
388
+ Napi::Value filter = filters_handle.Get(i);
389
+ if (!filter.IsNumber())
390
+ {
391
+ Napi::Error::New(env, "Filter must be an integer").ThrowAsJavaScriptException();
392
+ return false;
393
+ }
394
+
395
+ uint64_t current = filter.ToNumber().Uint32Value();
396
+ this->filters[i].id = current;
397
+ if (current == LZMA_FILTER_LZMA2)
398
+ {
399
+ this->filters[i].options = &this->_opt_lzma2;
400
+ }
401
+ else
402
+ {
403
+ this->filters[i].options = nullptr;
404
+ }
405
+ }
406
+
407
+ // Set terminator
408
+ this->filters[filters_len] = {.id = LZMA_VLI_UNKNOWN, .options = nullptr};
409
+
410
+ return true;
300
411
  }
301
412
 
302
- void LZMA::After(Napi::Env env, LZMA* obj /*, int status */) {
413
+ bool LZMA::InitializeEncoder(const Napi::Object &opts, uint32_t preset, lzma_check check)
414
+ {
415
+ Napi::Env env = opts.Env();
416
+
417
+ // Validate threads parameter
418
+ Napi::Value optsThreads = opts.Get("threads");
419
+ if (!optsThreads.IsNumber())
420
+ {
421
+ Napi::Error::New(env, "Threads must be an integer").ThrowAsJavaScriptException();
422
+ return false;
423
+ }
303
424
 
304
- Napi::Number ret_code = Napi::Number::New(env, obj->_ret);
305
- Napi::Number avail_in = Napi::Number::New(env, obj->_stream.avail_in);
306
- Napi::Number avail_out = Napi::Number::New(env, obj->_stream.avail_out);
425
+ #ifdef ENABLE_THREAD_SUPPORT
426
+ unsigned int threads = optsThreads.ToNumber().Uint32Value();
427
+ #pragma GCC diagnostic push
428
+ #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
307
429
 
308
- obj->_wip = false;
430
+ lzma_mt mt = {
431
+ .flags = 0,
432
+ .threads = threads,
433
+ .block_size = 0,
434
+ .timeout = 0,
435
+ .preset = preset,
436
+ .filters = this->filters.get(),
437
+ .check = check,
438
+ };
309
439
 
310
- obj->_callback.Call({ ret_code, avail_in, avail_out });
440
+ #pragma GCC diagnostic pop
311
441
 
312
- obj->Unref();
442
+ lzma_ret ret;
443
+ if (threads > 1)
444
+ {
445
+ ret = lzma_stream_encoder_mt(&this->_stream, &mt);
446
+ }
447
+ else
448
+ {
449
+ ret = lzma_stream_encoder(&this->_stream, this->filters.get(), check);
450
+ }
451
+ #else
452
+ lzma_ret ret = lzma_stream_encoder(&this->_stream, this->filters.get(), check);
453
+ #endif
313
454
 
314
- if(obj->_pending_close) {
315
- obj->Close(env);
455
+ if (ret != LZMA_OK)
456
+ {
457
+ Napi::Error::New(env, "LZMA encoder failure, returned " + std::to_string(ret)).ThrowAsJavaScriptException();
458
+ return false;
316
459
  }
460
+
461
+ return true;
317
462
  }
318
463
 
319
- Napi::Value LZMA::AfterSync(const Napi::CallbackInfo &info, LZMA* obj) {
320
- Napi::Env env = info.Env();
464
+ bool LZMA::InitializeDecoder()
465
+ {
466
+ lzma_ret ret = lzma_stream_decoder(&this->_stream, UINT64_MAX, LZMA_CONCATENATED);
467
+ return ret == LZMA_OK;
468
+ }
469
+
470
+ void LZMA::AfterCommon(const Napi::Env &env)
471
+ {
472
+ // Mark work done
473
+ this->_wip = false;
474
+
475
+ // Clear worker pointer if any
476
+ _worker = nullptr;
477
+
478
+ // Release any Buffer references (async path uses these)
479
+ if (!_in_buf_ref.IsEmpty())
480
+ _in_buf_ref.Reset();
481
+ if (!_out_buf_ref.IsEmpty())
482
+ _out_buf_ref.Reset();
483
+
484
+ // Balance reference taken in Code()
485
+ Unref();
486
+
487
+ // Honor pending close requests
488
+ if (_pending_close)
489
+ {
490
+ Close(env);
491
+ }
492
+ }
493
+
494
+ Napi::Array LZMA::After(const Napi::Env &env)
495
+ {
496
+ Napi::HandleScope scope(env);
497
+ Napi::Number ret = Napi::Number::New(env, this->_ret);
498
+ Napi::Number avail_in = Napi::Number::New(env, this->_stream.avail_in);
499
+ Napi::Number avail_out = Napi::Number::New(env, this->_stream.avail_out);
321
500
 
322
- Napi::Number ret_code = Napi::Number::New(env, obj->_ret);
323
- Napi::Number avail_in = Napi::Number::New(env, obj->_stream.avail_in);
324
- Napi::Number avail_out = Napi::Number::New(env, obj->_stream.avail_out);
325
501
  Napi::Array result = Napi::Array::New(env, 3);
502
+ result[(uint32_t)0] = ret;
503
+ result[(uint32_t)1] = avail_in;
504
+ result[(uint32_t)2] = avail_out;
326
505
 
327
- uint32_t i = 0;
328
- result[i++] = ret_code;
329
- result[i++] = avail_in;
330
- result[i++] = avail_out;
506
+ AfterCommon(env);
331
507
 
332
- obj->_wip = false;
508
+ return result;
509
+ }
333
510
 
334
- obj->Unref();
335
- if(obj->_pending_close) {
336
- obj->Close(info);
337
- }
511
+ void LZMA::After(const Napi::Env &env, const Napi::Function &cb)
512
+ {
513
+ Napi::HandleScope scope(env);
514
+ Napi::Number ret = Napi::Number::New(env, this->_ret);
515
+ Napi::Number avail_in = Napi::Number::New(env, this->_stream.avail_in);
516
+ Napi::Number avail_out = Napi::Number::New(env, this->_stream.avail_out);
517
+
518
+ // Call the provided JS callback with the three numeric results
519
+ cb.Call({ret, avail_in, avail_out});
338
520
 
339
- return result;
521
+ AfterCommon(env);
340
522
  }