lzma1 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -25,9 +25,9 @@ to read and maintain.
25
25
 
26
26
  - Encode and decode LZMA streams
27
27
  - Supports both string and Uint8Array data
28
- - Supports both browser and Node.js environments
29
- - Pure JavaScript implementation with TypeScript types
30
28
  - No dependencies on runtime-specific APIs
29
+ - Pure JavaScript implementation with TypeScript types
30
+ - Isomorphic browser- and runtime-independent implementation
31
31
 
32
32
  ## Installation
33
33
 
@@ -155,7 +155,14 @@ commonly used in the 7z archive format.
155
155
  The LZMA compressed data begins with a header that contains information needed
156
156
  for decompression:
157
157
 
158
- ![lzma](./docs/lzma.svg)
158
+ ```mermaid
159
+ packet-beta
160
+ 0-7: "lc (literal context)"
161
+ 8-11: "lp (literal pos)"
162
+ 12-15: "pb (position bits)"
163
+ 16-47: "Dictionary Size (32-bit, LE)"
164
+ 48-111: "Uncompressed Size (64-bit, LE)"
165
+ ```
159
166
 
160
167
  More [information][header_link] about the LZMA header structure.
161
168
 
package/lib/decoder.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { LzOutWindow } from "./lz-window.js";
2
2
  import { RangeDecoder } from "./range-decoder.js";
3
- import { add64, CHOICE_ARRAY_SIZE, compare64, createBitTree, DEFAULT_WINDOW_SIZE, getLenToPosState, initArray, initBitModels, LITERAL_DECODER_SIZE, lowBits64, MATCH_DECODERS_SIZE, POS_DECODERS_SIZE, REP_DECODERS_SIZE, stateUpdateChar, } from "./utils.js";
3
+ import { _MAX_UINT32, CHOICE_ARRAY_SIZE, createBitTree, DEFAULT_WINDOW_SIZE, getLenToPosState, initArray, initBitModels, LITERAL_DECODER_SIZE, MATCH_DECODERS_SIZE, POS_DECODERS_SIZE, REP_DECODERS_SIZE, stateUpdateChar, } from "./utils.js";
4
4
  export class Decoder {
5
5
  rangeDecoder;
6
6
  outWindow;
@@ -11,8 +11,8 @@ export class Decoder {
11
11
  rep2 = 0;
12
12
  rep3 = 0;
13
13
  prevByte = 0;
14
- nowPos64 = [0, 0];
15
- outSize = [0, 0];
14
+ nowPos64 = 0n;
15
+ outSize = 0n;
16
16
  // Decoder configuration
17
17
  posStateMask = 0;
18
18
  dictSizeCheck = 0;
@@ -34,11 +34,6 @@ export class Decoder {
34
34
  get literalCoder() {
35
35
  return this.literalDecoder;
36
36
  }
37
- // Chunker properties for compatibility
38
- decoder;
39
- encoder = null;
40
- alive = 0;
41
- inBytesProcessed = [0, 0];
42
37
  constructor() {
43
38
  // Initialize range decoder
44
39
  this.rangeDecoder = new RangeDecoder();
@@ -69,8 +64,79 @@ export class Decoder {
69
64
  this.repLenDecoder = this.createLenDecoder();
70
65
  // Initialize position alignment decoder
71
66
  this.posAlignDecoder = createBitTree(4);
72
- // Initialize self-reference for chunker compatibility
73
- this.decoder = this;
67
+ }
68
+ /**
69
+ * Read LZMA header, configure decoder, and prepare for decompression.
70
+ */
71
+ initDecompression(input, output) {
72
+ // Read 5-byte header properties
73
+ const properties = [];
74
+ for (let i = 0; i < 5; ++i) {
75
+ const r = input.readByte();
76
+ if (r === -1) {
77
+ throw new Error("truncated input");
78
+ }
79
+ properties[i] = r << 24 >> 24;
80
+ }
81
+ if (!this.setDecoderProperties(properties)) {
82
+ throw new Error("corrupted input");
83
+ }
84
+ // Read 8-byte uncompressed length from header
85
+ let outSize;
86
+ {
87
+ let value = 0n;
88
+ for (let i = 0; i < 8; i++) {
89
+ const r = input.readByte();
90
+ if (r === -1) {
91
+ throw new Error("truncated input");
92
+ }
93
+ value += BigInt(r & 0xFF) << BigInt(i * 8);
94
+ }
95
+ // Check for unknown size marker (all 0xFF bytes)
96
+ if (value === 0xffffffffffffffffn) {
97
+ outSize = -1n;
98
+ }
99
+ else if (value > BigInt(_MAX_UINT32)) {
100
+ outSize = -1n;
101
+ }
102
+ else {
103
+ outSize = value;
104
+ }
105
+ }
106
+ // Set up range decoder and output window
107
+ this.rangeDecoder.setStream(input);
108
+ this.flush();
109
+ this.outWindow.stream = null;
110
+ this.outWindow.stream = output;
111
+ // Initialize decoder state
112
+ this.init();
113
+ this.state = 0;
114
+ this.rep0 = 0;
115
+ this.rep1 = 0;
116
+ this.rep2 = 0;
117
+ this.rep3 = 0;
118
+ this.outSize = outSize;
119
+ this.nowPos64 = 0n;
120
+ this.prevByte = 0;
121
+ }
122
+ /**
123
+ * Full decompression: read header, decode all chunks, flush and cleanup.
124
+ */
125
+ decompress(input, output) {
126
+ this.initDecompression(input, output);
127
+ while (true) {
128
+ const result = this.codeOneChunk();
129
+ if (result === -1) {
130
+ throw new Error("corrupted input");
131
+ }
132
+ const isOutputComplete = (this.outSize >= 0n)
133
+ && (this.nowPos64 >= this.outSize);
134
+ if (result || isOutputComplete) {
135
+ this.flush();
136
+ this.cleanup();
137
+ return;
138
+ }
139
+ }
74
140
  }
75
141
  createLenDecoder() {
76
142
  const decoder = {
@@ -111,7 +177,7 @@ export class Decoder {
111
177
  // Initialize output window
112
178
  if (dictSize > 0) {
113
179
  this.outWindow.windowSize = Math.max(dictSize, 4096);
114
- this.outWindow.buffer = initArray(this.outWindow.windowSize);
180
+ this.outWindow.buffer = new Uint8Array(this.outWindow.windowSize);
115
181
  }
116
182
  // Initialize literal decoder coders
117
183
  const numStates = 1 << (this.literalDecoder.numPrevBits + this.literalDecoder.numPosBits);
@@ -295,9 +361,9 @@ export class Decoder {
295
361
  }
296
362
  codeOneChunk() {
297
363
  let decoder2, distance, len, numDirectBits, positionSlot;
298
- let posState = lowBits64(this.nowPos64) & this.posStateMask;
364
+ let posState = Number(this.nowPos64 & 0xffffffffn) & this.posStateMask;
299
365
  if (!this.decodeBit(this.matchDecoders, (this.state << 4) + posState)) {
300
- decoder2 = this.getDecoder(lowBits64(this.nowPos64), this.prevByte);
366
+ decoder2 = this.getDecoder(Number(this.nowPos64 & 0xffffffffn), this.prevByte);
301
367
  if (this.state < 7) {
302
368
  this.prevByte = this.decodeNormalWithRangeDecoder(decoder2);
303
369
  }
@@ -306,7 +372,7 @@ export class Decoder {
306
372
  }
307
373
  this.putByte(this.prevByte);
308
374
  this.state = stateUpdateChar(this.state);
309
- this.nowPos64 = add64(this.nowPos64, [1, 0]);
375
+ this.nowPos64 += 1n;
310
376
  }
311
377
  else {
312
378
  if (this.decodeBit(this.repDecoders, this.state)) {
@@ -367,76 +433,17 @@ export class Decoder {
367
433
  this.rep0 = positionSlot;
368
434
  }
369
435
  }
370
- if (compare64([this.rep0, 0], this.nowPos64) >= 0 || this.rep0 >= this.dictSizeCheck) {
436
+ if (BigInt(this.rep0) >= this.nowPos64 || this.rep0 >= this.dictSizeCheck) {
371
437
  return -1;
372
438
  }
373
439
  this.copyBlock(len);
374
- this.nowPos64 = add64(this.nowPos64, [len, 0]);
440
+ this.nowPos64 += BigInt(len);
375
441
  this.prevByte = this.getByte(0);
376
442
  }
377
443
  return 0;
378
444
  }
379
- // Setup decoder for chunk processing
380
- setupForDecoding(inStream, outSize, outputBuffer) {
381
- this.rangeDecoder.setStream(inStream);
382
- this.outSize = outSize;
383
- this.outWindowReleaseStream();
384
- this.outWindow.stream = outputBuffer;
385
- this.init();
386
- this.state = 0;
387
- this.rep0 = 0;
388
- this.rep1 = 0;
389
- this.rep2 = 0;
390
- this.rep3 = 0;
391
- this.outSize = outSize;
392
- this.nowPos64 = [0, 0];
393
- this.prevByte = 0;
394
- this.decoder = this;
395
- this.encoder = null;
396
- this.alive = 1;
397
- }
398
- // Process chunk and return alive status
399
- processChunk() {
400
- if (!this.alive) {
401
- throw new Error("Bad state");
402
- }
403
- if (this.encoder) {
404
- throw new Error("No encoding");
405
- }
406
- const result = this.codeOneChunk();
407
- if (result === -1) {
408
- throw new Error("Corrupted input");
409
- }
410
- const isOutputComplete = (compare64(this.outSize, [0, 0]) >= 0)
411
- && (compare64(this.nowPos64, this.outSize) >= 0);
412
- if (result || isOutputComplete) {
413
- this.flush();
414
- this.outWindowReleaseStream();
415
- this.rangeDecoder.setStream(null);
416
- this.alive = 0;
417
- }
418
- return this.alive;
419
- }
420
445
  writeToOutput(buffer, data, offset, length) {
421
- // Ensure buffer has enough capacity
422
- const requiredSize = buffer.count + length;
423
- if (requiredSize > buffer.buf.length) {
424
- const newSize = Math.max(buffer.buf.length * 2, requiredSize);
425
- const newBuf = new Array(newSize);
426
- for (let i = 0; i < buffer.count; i++) {
427
- newBuf[i] = buffer.buf[i];
428
- }
429
- buffer.buf = newBuf;
430
- }
431
- // Copy data
432
- for (let i = 0; i < length; i++) {
433
- buffer.buf[buffer.count + i] = data[offset + i];
434
- }
435
- buffer.count += length;
436
- }
437
- isBufferWithCount(x) {
438
- const s = x;
439
- return !!s && Array.isArray(s.buf) && typeof s.count === "number" && typeof s.write === "function";
446
+ buffer.writeBytes(data, offset, length);
440
447
  }
441
448
  flush() {
442
449
  const size = this.outWindow.pos - this.outWindow.streamPos;
@@ -444,15 +451,7 @@ export class Decoder {
444
451
  return;
445
452
  }
446
453
  if (this.outWindow.stream && this.outWindow.buffer) {
447
- const outputBuffer = this.outWindow.stream;
448
- if (this.isBufferWithCount(outputBuffer)) {
449
- this.writeToOutput(outputBuffer, this.outWindow.buffer, this.outWindow.streamPos, size);
450
- }
451
- else if (typeof outputBuffer.write === "function") {
452
- // Fallback: write directly if it's a plain Writer
453
- const slice = this.outWindow.buffer.slice(this.outWindow.streamPos, this.outWindow.streamPos + size);
454
- outputBuffer.write(slice);
455
- }
454
+ this.outWindow.stream.writeBytes(this.outWindow.buffer, this.outWindow.streamPos, size);
456
455
  }
457
456
  if (this.outWindow.pos >= this.outWindow.windowSize) {
458
457
  this.outWindow.pos = 0;