|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import * as xxhash from "./xxh32.js"; |
|
import * as util from "./util.js"; |
|
|
|
|
|
|
|
|
|
|
|
const minMatch = 4; |
|
const matchSearchLimit = 12; |
|
const minTrailingLitterals = 5; |
|
const skipTrigger = 6; |
|
const hashSize = 1 << 16; |
|
|
|
|
|
const mlBits = 4; |
|
const mlMask = (1 << mlBits) - 1; |
|
const runBits = 4; |
|
const runMask = (1 << runBits) - 1; |
|
|
|
|
|
const blockBuf = makeBuffer(5 << 20); |
|
const hashTable = makeHashTable(); |
|
|
|
|
|
const magicNum = 0x184d2204; |
|
|
|
|
|
const fdContentChksum = 0x4; |
|
const fdContentSize = 0x8; |
|
const fdBlockChksum = 0x10; |
|
|
|
const fdVersion = 0x40; |
|
const fdVersionMask = 0xc0; |
|
|
|
|
|
const bsUncompressed = 0x80000000; |
|
const bsDefault = 7; |
|
const bsShift = 4; |
|
const bsMask = 7; |
|
const bsMap: Record<number, number> = { |
|
4: 0x10000, |
|
5: 0x40000, |
|
6: 0x100000, |
|
7: 0x400000, |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function makeHashTable() { |
|
try { |
|
return new Uint32Array(hashSize); |
|
} catch (error) { |
|
const hashTable = new Array(hashSize); |
|
|
|
for (let i = 0; i < hashSize; i++) { |
|
hashTable[i] = 0; |
|
} |
|
|
|
return hashTable; |
|
} |
|
} |
|
|
|
|
|
function clearHashTable(table: Uint32Array | number[]) { |
|
for (let i = 0; i < hashSize; i++) { |
|
table[i] = 0; |
|
} |
|
} |
|
|
|
|
|
function makeBuffer(size: number) { |
|
return new Uint8Array(size); |
|
} |
|
|
|
function sliceArray(array: Uint8Array, start: number, end: number) { |
|
return array.slice(start, end); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function compressBound(n: number) { |
|
return (n + n / 255 + 16) | 0; |
|
} |
|
|
|
|
|
export function decompressBound(src: Uint8Array) { |
|
let sIndex = 0; |
|
|
|
|
|
if (util.readU32(src, sIndex) !== magicNum) { |
|
throw new Error("invalid magic number"); |
|
} |
|
|
|
sIndex += 4; |
|
|
|
|
|
const descriptor = src[sIndex++]; |
|
|
|
|
|
if ((descriptor & fdVersionMask) !== fdVersion) { |
|
throw new Error("incompatible descriptor version " + (descriptor & fdVersionMask)); |
|
} |
|
|
|
|
|
const useBlockSum = (descriptor & fdBlockChksum) !== 0; |
|
const useContentSize = (descriptor & fdContentSize) !== 0; |
|
|
|
|
|
const bsIdx = (src[sIndex++] >> bsShift) & bsMask; |
|
|
|
if (bsMap[bsIdx] === undefined) { |
|
throw new Error("invalid block size " + bsIdx); |
|
} |
|
|
|
const maxBlockSize = bsMap[bsIdx]; |
|
|
|
|
|
if (useContentSize) { |
|
return util.readU64(src, sIndex); |
|
} |
|
|
|
|
|
sIndex++; |
|
|
|
|
|
let maxSize = 0; |
|
while (true) { |
|
let blockSize = util.readU32(src, sIndex); |
|
sIndex += 4; |
|
|
|
if (blockSize & bsUncompressed) { |
|
blockSize &= ~bsUncompressed; |
|
maxSize += blockSize; |
|
} else if (blockSize > 0) { |
|
maxSize += maxBlockSize; |
|
} |
|
|
|
if (blockSize === 0) { |
|
return maxSize; |
|
} |
|
|
|
if (useBlockSum) { |
|
sIndex += 4; |
|
} |
|
|
|
sIndex += blockSize; |
|
} |
|
} |
|
|
|
|
|
export function decompressBlock(src: Uint8Array, dst: Uint8Array, sIndex: number, sLength: number, dIndex: number) { |
|
let mLength, mOffset, sEnd, n, i; |
|
const hasCopyWithin = dst.copyWithin !== undefined && dst.fill !== undefined; |
|
|
|
|
|
sEnd = sIndex + sLength; |
|
|
|
|
|
while (sIndex < sEnd) { |
|
const token = src[sIndex++]; |
|
|
|
|
|
let literalCount = token >> 4; |
|
if (literalCount > 0) { |
|
|
|
if (literalCount === 0xf) { |
|
while (true) { |
|
literalCount += src[sIndex]; |
|
if (src[sIndex++] !== 0xff) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
for (n = sIndex + literalCount; sIndex < n; ) { |
|
dst[dIndex++] = src[sIndex++]; |
|
} |
|
} |
|
|
|
if (sIndex >= sEnd) { |
|
break; |
|
} |
|
|
|
|
|
mLength = token & 0xf; |
|
|
|
|
|
mOffset = src[sIndex++] | (src[sIndex++] << 8); |
|
|
|
|
|
if (mLength === 0xf) { |
|
while (true) { |
|
mLength += src[sIndex]; |
|
if (src[sIndex++] !== 0xff) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
mLength += minMatch; |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (hasCopyWithin && mOffset === 1) { |
|
dst.fill(dst[dIndex - 1] | 0, dIndex, dIndex + mLength); |
|
dIndex += mLength; |
|
} else if (hasCopyWithin && mOffset > mLength && mLength > 31) { |
|
dst.copyWithin(dIndex, dIndex - mOffset, dIndex - mOffset + mLength); |
|
dIndex += mLength; |
|
} else { |
|
for (i = dIndex - mOffset, n = i + mLength; i < n; ) { |
|
dst[dIndex++] = dst[i++] | 0; |
|
} |
|
} |
|
} |
|
|
|
return dIndex; |
|
} |
|
|
|
|
|
export function compressBlock( |
|
src: Uint8Array, |
|
dst: Uint8Array, |
|
sIndex: number, |
|
sLength: number, |
|
hashTable: Uint32Array | number[] |
|
) { |
|
let mIndex, mAnchor, mLength, mOffset, mStep; |
|
let literalCount, dIndex, sEnd, n; |
|
|
|
|
|
dIndex = 0; |
|
sEnd = sLength + sIndex; |
|
mAnchor = sIndex; |
|
|
|
let searchMatchCount = (1 << skipTrigger) + 3; |
|
|
|
|
|
|
|
while (sIndex <= sEnd - matchSearchLimit) { |
|
const seq = util.readU32(src, sIndex); |
|
let hash = util.hashU32(seq) >>> 0; |
|
|
|
|
|
hash = (((hash >> 16) ^ hash) >>> 0) & 0xffff; |
|
|
|
|
|
mIndex = hashTable[hash] - 1; |
|
|
|
|
|
hashTable[hash] = sIndex + 1; |
|
|
|
|
|
if (mIndex < 0 || (sIndex - mIndex) >>> 16 > 0 || util.readU32(src, mIndex) !== seq) { |
|
mStep = searchMatchCount++ >> skipTrigger; |
|
sIndex += mStep; |
|
continue; |
|
} |
|
|
|
searchMatchCount = (1 << skipTrigger) + 3; |
|
|
|
|
|
literalCount = sIndex - mAnchor; |
|
mOffset = sIndex - mIndex; |
|
|
|
|
|
sIndex += minMatch; |
|
mIndex += minMatch; |
|
|
|
|
|
|
|
|
|
mLength = sIndex; |
|
while (sIndex < sEnd - minTrailingLitterals && src[sIndex] === src[mIndex]) { |
|
sIndex++; |
|
mIndex++; |
|
} |
|
mLength = sIndex - mLength; |
|
|
|
|
|
const token = mLength < mlMask ? mLength : mlMask; |
|
if (literalCount >= runMask) { |
|
dst[dIndex++] = (runMask << mlBits) + token; |
|
for (n = literalCount - runMask; n >= 0xff; n -= 0xff) { |
|
dst[dIndex++] = 0xff; |
|
} |
|
dst[dIndex++] = n; |
|
} else { |
|
dst[dIndex++] = (literalCount << mlBits) + token; |
|
} |
|
|
|
|
|
for (let i = 0; i < literalCount; i++) { |
|
dst[dIndex++] = src[mAnchor + i]; |
|
} |
|
|
|
|
|
dst[dIndex++] = mOffset; |
|
dst[dIndex++] = mOffset >> 8; |
|
|
|
|
|
if (mLength >= mlMask) { |
|
for (n = mLength - mlMask; n >= 0xff; n -= 0xff) { |
|
dst[dIndex++] = 0xff; |
|
} |
|
dst[dIndex++] = n; |
|
} |
|
|
|
|
|
mAnchor = sIndex; |
|
} |
|
|
|
|
|
if (mAnchor === 0) { |
|
return 0; |
|
} |
|
|
|
|
|
|
|
literalCount = sEnd - mAnchor; |
|
if (literalCount >= runMask) { |
|
dst[dIndex++] = runMask << mlBits; |
|
for (n = literalCount - runMask; n >= 0xff; n -= 0xff) { |
|
dst[dIndex++] = 0xff; |
|
} |
|
dst[dIndex++] = n; |
|
} else { |
|
dst[dIndex++] = literalCount << mlBits; |
|
} |
|
|
|
|
|
sIndex = mAnchor; |
|
while (sIndex < sEnd) { |
|
dst[dIndex++] = src[sIndex++]; |
|
} |
|
|
|
return dIndex; |
|
} |
|
|
|
|
|
export function decompressFrame(src: Uint8Array, dst: Uint8Array) { |
|
let useBlockSum, useContentSum, useContentSize, descriptor; |
|
let sIndex = 0; |
|
let dIndex = 0; |
|
|
|
|
|
if (util.readU32(src, sIndex) !== magicNum) { |
|
throw new Error("invalid magic number"); |
|
} |
|
|
|
sIndex += 4; |
|
|
|
|
|
descriptor = src[sIndex++]; |
|
|
|
|
|
if ((descriptor & fdVersionMask) !== fdVersion) { |
|
throw new Error("incompatible descriptor version"); |
|
} |
|
|
|
|
|
useBlockSum = (descriptor & fdBlockChksum) !== 0; |
|
useContentSum = (descriptor & fdContentChksum) !== 0; |
|
useContentSize = (descriptor & fdContentSize) !== 0; |
|
|
|
|
|
const bsIdx = (src[sIndex++] >> bsShift) & bsMask; |
|
|
|
if (bsMap[bsIdx] === undefined) { |
|
throw new Error("invalid block size"); |
|
} |
|
|
|
if (useContentSize) { |
|
|
|
sIndex += 8; |
|
} |
|
|
|
sIndex++; |
|
|
|
|
|
while (true) { |
|
var compSize; |
|
|
|
compSize = util.readU32(src, sIndex); |
|
sIndex += 4; |
|
|
|
if (compSize === 0) { |
|
break; |
|
} |
|
|
|
if (useBlockSum) { |
|
|
|
sIndex += 4; |
|
} |
|
|
|
|
|
if ((compSize & bsUncompressed) !== 0) { |
|
|
|
compSize &= ~bsUncompressed; |
|
|
|
|
|
for (let j = 0; j < compSize; j++) { |
|
dst[dIndex++] = src[sIndex++]; |
|
} |
|
} else { |
|
|
|
dIndex = decompressBlock(src, dst, sIndex, compSize, dIndex); |
|
sIndex += compSize; |
|
} |
|
} |
|
|
|
if (useContentSum) { |
|
|
|
sIndex += 4; |
|
} |
|
|
|
return dIndex; |
|
} |
|
|
|
|
|
export function compressFrame(src: Uint8Array, dst: Uint8Array) { |
|
let dIndex = 0; |
|
|
|
|
|
util.writeU32(dst, dIndex, magicNum); |
|
dIndex += 4; |
|
|
|
|
|
dst[dIndex++] = fdVersion; |
|
dst[dIndex++] = bsDefault << bsShift; |
|
|
|
|
|
dst[dIndex] = xxhash.hash(0, dst, 4, dIndex - 4) >> 8; |
|
dIndex++; |
|
|
|
|
|
const maxBlockSize = bsMap[bsDefault]; |
|
let remaining = src.length; |
|
let sIndex = 0; |
|
|
|
|
|
clearHashTable(hashTable); |
|
|
|
|
|
while (remaining > 0) { |
|
let compSize = 0; |
|
const blockSize = remaining > maxBlockSize ? maxBlockSize : remaining; |
|
|
|
compSize = compressBlock(src, blockBuf, sIndex, blockSize, hashTable); |
|
|
|
if (compSize > blockSize || compSize === 0) { |
|
|
|
util.writeU32(dst, dIndex, 0x80000000 | blockSize); |
|
dIndex += 4; |
|
|
|
for (let z = sIndex + blockSize; sIndex < z; ) { |
|
dst[dIndex++] = src[sIndex++]; |
|
} |
|
|
|
remaining -= blockSize; |
|
} else { |
|
|
|
util.writeU32(dst, dIndex, compSize); |
|
dIndex += 4; |
|
|
|
for (let j = 0; j < compSize; ) { |
|
dst[dIndex++] = blockBuf[j++]; |
|
} |
|
|
|
sIndex += blockSize; |
|
remaining -= blockSize; |
|
} |
|
} |
|
|
|
|
|
util.writeU32(dst, dIndex, 0); |
|
dIndex += 4; |
|
|
|
return dIndex; |
|
} |
|
|
|
|
|
|
|
|
|
export function decompress(src: Uint8Array, maxSize: number) { |
|
let dst, size; |
|
|
|
if (maxSize === undefined) { |
|
maxSize = decompressBound(src); |
|
} |
|
dst = makeBuffer(maxSize); |
|
size = decompressFrame(src, dst); |
|
|
|
if (size !== maxSize) { |
|
dst = sliceArray(dst, 0, size); |
|
} |
|
|
|
return dst; |
|
} |
|
|
|
|
|
|
|
|
|
export function compress(src: Uint8Array, maxSize: number) { |
|
let dst, size; |
|
|
|
if (maxSize === undefined) { |
|
maxSize = compressBound(src.length); |
|
} |
|
|
|
dst = makeBuffer(maxSize); |
|
size = compressFrame(src, dst); |
|
|
|
if (size !== maxSize) { |
|
dst = sliceArray(dst, 0, size); |
|
} |
|
|
|
return dst; |
|
} |
|
|