UNPKG

lz4

Version:

LZ4 streaming compression and decompression

228 lines (185 loc) 6.38 kB
var Transform = require('stream').Transform var inherits = require('util').inherits var lz4_static = require('./static') var utils = lz4_static.utils var lz4_binding = utils.bindings var lz4_jsbinding = require('./binding') var STATES = lz4_static.STATES var SIZES = lz4_static.SIZES var defaultOptions = { blockIndependence: true , blockChecksum: false , blockMaxSize: 4<<20 , streamSize: false , streamChecksum: true , dict: false , dictId: 0 , highCompression: false } function Encoder (options) { if ( !(this instanceof Encoder) ) return new Encoder(options) Transform.call(this, options) // Set the options var o = options || defaultOptions if (o !== defaultOptions) Object.keys(defaultOptions).forEach(function (p) { if ( !o.hasOwnProperty(p) ) o[p] = defaultOptions[p] }) this.options = o this.binding = this.options.useJS ? lz4_jsbinding : lz4_binding this.compress = o.highCompression ? this.binding.compressHC : this.binding.compress // Build the stream descriptor from the options // flags var descriptor_flg = 0 descriptor_flg = descriptor_flg | (lz4_static.VERSION << 6) // Version descriptor_flg = descriptor_flg | ((o.blockIndependence & 1) << 5) // Block independence descriptor_flg = descriptor_flg | ((o.blockChecksum & 1) << 4) // Block checksum descriptor_flg = descriptor_flg | ((o.streamSize & 1) << 3) // Stream size descriptor_flg = descriptor_flg | ((o.streamChecksum & 1) << 2) // Stream checksum // Reserved bit descriptor_flg = descriptor_flg | (o.dict & 1) // Preset dictionary // block maximum size var descriptor_bd = lz4_static.blockMaxSizes.indexOf(o.blockMaxSize) if (descriptor_bd < 0) throw new Error('Invalid blockMaxSize: ' + o.blockMaxSize) this.descriptor = { flg: descriptor_flg, bd: (descriptor_bd & 0x7) << 4 } // Data being processed this.buffer = [] this.length = 0 this.first = true this.checksum = null } inherits(Encoder, Transform) // Header = magic number + stream descriptor Encoder.prototype.headerSize = function () { var streamSizeSize = this.options.streamSize ? SIZES.DESCRIPTOR : 0 var dictSize = this.options.dict ? SIZES.DICTID : 0 return SIZES.MAGIC + 1 + 1 + streamSizeSize + dictSize + 1 } Encoder.prototype.header = function () { var headerSize = this.headerSize() var output = Buffer.alloc(headerSize) this.state = STATES.MAGIC output.writeInt32LE(lz4_static.MAGICNUMBER, 0) this.state = STATES.DESCRIPTOR var descriptor = output.slice(SIZES.MAGIC, output.length - 1) // Update the stream descriptor descriptor.writeUInt8(this.descriptor.flg, 0) descriptor.writeUInt8(this.descriptor.bd, 1) var pos = 2 this.state = STATES.SIZE if (this.options.streamSize) { //TODO only 32bits size supported descriptor.writeInt32LE(0, pos) descriptor.writeInt32LE(this.size, pos + 4) pos += SIZES.SIZE } this.state = STATES.DICTID if (this.options.dict) { descriptor.writeInt32LE(this.dictId, pos) pos += SIZES.DICTID } this.state = STATES.DESCRIPTOR_CHECKSUM output.writeUInt8( utils.descriptorChecksum( descriptor ) , SIZES.MAGIC + pos ) return output } Encoder.prototype.update_Checksum = function (data) { // Calculate the stream checksum this.state = STATES.CHECKSUM_UPDATE if (this.options.streamChecksum) { this.checksum = utils.streamChecksum(data, this.checksum) } } Encoder.prototype.compress_DataBlock = function (data) { this.state = STATES.DATABLOCK_COMPRESS var dbChecksumSize = this.options.blockChecksum ? SIZES.DATABLOCK_CHECKSUM : 0 var maxBufSize = this.binding.compressBound(data.length) var buf = Buffer.alloc( SIZES.DATABLOCK_SIZE + maxBufSize + dbChecksumSize ) var compressed = buf.slice(SIZES.DATABLOCK_SIZE, SIZES.DATABLOCK_SIZE + maxBufSize) var compressedSize = this.compress(data, compressed) // Set the block size this.state = STATES.DATABLOCK_SIZE // Block size shall never be larger than blockMaxSize // console.log("blockMaxSize", this.options.blockMaxSize, "compressedSize", compressedSize) if (compressedSize > 0 && compressedSize <= this.options.blockMaxSize) { // highest bit is 0 (compressed data) buf.writeUInt32LE(compressedSize, 0) buf = buf.slice(0, SIZES.DATABLOCK_SIZE + compressedSize + dbChecksumSize) } else { // Cannot compress the data, leave it as is // highest bit is 1 (uncompressed data) buf.writeInt32LE( 0x80000000 | data.length, 0) buf = buf.slice(0, SIZES.DATABLOCK_SIZE + data.length + dbChecksumSize) data.copy(buf, SIZES.DATABLOCK_SIZE); } // Set the block checksum this.state = STATES.DATABLOCK_CHECKSUM if (this.options.blockChecksum) { // xxHash checksum on undecoded data with a seed of 0 var checksum = buf.slice(-dbChecksumSize) checksum.writeInt32LE( utils.blockChecksum(compressed), 0 ) } // Update the stream checksum this.update_Checksum(data) this.size += data.length return buf } Encoder.prototype._transform = function (data, encoding, done) { if (data) { // Buffer the incoming data this.buffer.push(data) this.length += data.length } // Stream header if (this.first) { this.push( this.header() ) this.first = false } var blockMaxSize = this.options.blockMaxSize // Not enough data for a block if ( this.length < blockMaxSize ) return done() // Build the data to be compressed var buf = Buffer.concat(this.buffer, this.length) for (var j = 0, i = buf.length; i >= blockMaxSize; i -= blockMaxSize, j += blockMaxSize) { // Compress the block this.push( this.compress_DataBlock( buf.slice(j, j + blockMaxSize) ) ) } // Set the remaining data if (i > 0) { this.buffer = [ buf.slice(j) ] this.length = this.buffer[0].length } else { this.buffer = [] this.length = 0 } done() } Encoder.prototype._flush = function (done) { if (this.first) { this.push( this.header() ) this.first = false } if (this.length > 0) { var buf = Buffer.concat(this.buffer, this.length) this.buffer = [] this.length = 0 var cc = this.compress_DataBlock(buf) this.push( cc ) } if (this.options.streamChecksum) { this.state = STATES.CHECKSUM var eos = Buffer.alloc(SIZES.EOS + SIZES.CHECKSUM) eos.writeUInt32LE( utils.streamChecksum(null, this.checksum), SIZES.EOS ) } else { var eos = Buffer.alloc(SIZES.EOS) } this.state = STATES.EOS eos.writeInt32LE(lz4_static.EOS, 0) this.push(eos) done() } module.exports = Encoder