quake3 server connector | , decode client messages | monitor q3 servers | Search

This Node.js code implements a Huffman coding compression and decompression library using a WebAssembly module, allowing for the compression and decompression of messages. The code includes functions for writing and reading bits to and from a memory array, as well as a function for decompressing messages using the Huffman coding algorithm.

Run example

npm run import -- "huffman decode"

huffman decode

var fs = require('fs')
var huffman = fs.readFileSync('/Users/briancullinan/planet_quake/build/debug-js-js/huffman_js.wasm')
//var Huffman = require('/Users/briancullinan/planet_quake/code/xquakejs/lib/huffman.js')
var Huff_Compress
var Huff_Decompress
var HuffmanGetBit
var HuffmanGetSymbol
var isInit = false

var MAX_MSGLEN = 16384
var buffer
var memory


// negative bit values include signs
function writeBits( msgBytes, offset, value, bits ) {
    var base = 8192 * 12
    var bitIndex = offset
    var nbits = bits&7

	if ( bits < 0 ) {
		bits = -bits
	}
    for(var j = 0; j < MAX_MSGLEN; j++) {
        if(j < msgBytes.length)
            memory[base + j] = msgBytes[j] & 0xFF
        else
            memory[base + j] = 0
    }

    value &= (0xffffffff>>(32-bits))
    if ( nbits ) {
        for ( var i = 0; i < nbits ; i++ ) {
            HuffmanPutBit( base, bitIndex, (value & 1) )
            bitIndex++
            value = (value>>1)
        }
        bits = bits - nbits
    }
    if ( bits ) {
        for( var i = 0 ; i < bits ; i += 8 ) {
            bitIndex += HuffmanPutSymbol( base, bitIndex, (value & 0xFF) )
            value = (value>>8)
        }
    }
    return [bitIndex, memory.slice(base, base + (bitIndex>>3)+1)]
}


function readBits(m, offset, bits = 8) {
    var base = 8192 * 12
    var value
    var nbits = bits & 7
    var sym = base - 4
    var bitIndex = offset
    for(var i = 0; i < m.length; i++)
        memory[base + i] = m[i]
    if ( nbits )
    {
        for ( i = 0; i < nbits; i++ ) {
            value |= HuffmanGetBit( base, bitIndex ) << i
            bitIndex++
        }
        bits -= nbits
    }
    if ( bits )
    {
        for ( i = 0; i < bits; i += 8 )
        {
            bitIndex += HuffmanGetSymbol( sym, base, bitIndex )
            value |= ( memory[sym] << (i+nbits) )
        }
    }
    return [bitIndex, value]
}

async function decompressMessage(message, offset) {
    if(!isInit)
        await init()
    if(typeof message == 'string')
        message = message.split('')
    for(var i = 0; i < message.length; i++)
        Huffman.HEAP8[msgData+i] = c
	Huffman.HEAP32[(msg>>2)+5] = message.length
	Huffman._Huff_Decompress( msg, 12 )
	return Huffman.HEAP8.slice(msgData + offset, msgData + Huffman.HEAP32[(msg>>2)+5])
}

async function compressMessage(message) {
    var msg = 8192 * 12
    var msgStart = (msg + 64)
    if(!isInit)
        await init()
    for(var i = msg; i < msgStart + message.length; i++)
    {
        memory[i] = 0
    }
    memory[msg + 12] = msgStart & 255
    memory[msg + 13] = (msgStart >> 8) & 255
    memory[msg + 14] = (msgStart >> 16) & 255
    memory[msg + 15] = (msgStart >> 24) & 255
    memory[msg + 20] = (message.length + 1) & 255
    memory[msg + 21] = ((message.length + 1) >> 8) & 255
    memory[msg + 22] = 0
    memory[msg + 23] = 0

    if(typeof message == 'string')
        message = message.split('')
    for(var i = 0; i < message.length; i++)
        memory[msgStart + i] = message[i].charCodeAt(0)
    memory[msgStart + message.length] = 0

    Huff_Compress(msg, 12)
    var msgLength = (memory[msg + 21] << 8) + memory[msg + 20]
    var compressed = memory.slice(msgStart, msgStart + msgLength)
    return compressed
}

async function init() {
    var binary = new Uint8Array(huffman)
    let imports = {};
    imports['memory'] = new WebAssembly['Memory']( {'initial': 16, 'maximum': 100} )
    memory = new Uint8Array( imports['memory']['buffer'] )
    let program = await WebAssembly.instantiate(binary, { env: imports })
    Huff_Compress = program.instance.exports.Huff_Compress
    Huff_Decompress = program.instance.exports.Huff_Decompress
    HuffmanGetBit = program.instance.exports.HuffmanGetBit
    HuffmanGetSymbol = program.instance.exports.HuffmanGetSymbol
    HuffmanPutBit = program.instance.exports.HuffmanPutBit
    HuffmanPutSymbol = program.instance.exports.HuffmanPutSymbol
}

init()

module.exports = init
Object.assign(init, {
    readBits,
    writeBits,
    decompressMessage,
    compressMessage
})

What the code could have been:

const fs = require('fs');
const WebAssembly = require('webassembly-js');

class HuffmanCompressor {
  constructor() {
    this.memory = new Uint8Array(16);
    this.msgData = 8192 * 12;
    this.MAX_MSGLEN = 16384;
    this.isInit = false;
    this.buffer = new Uint8Array(0);

    this.MAX_MSGLEN = 16384;
    this.buffer = new Uint8Array(16384);
    this.memory = new Uint8Array(16);
  }

  async init() {
    if (this.isInit) return;
    const binary = new Uint8Array(fs.readFileSync('/Users/briancullinan/planet_quake/build/debug-js-js/huffman_js.wasm'));
    const imports = {};
    imports['memory'] = new WebAssembly.Memory({ initial: 1, maximum: 1 });
    const memory = new Uint8Array(imports['memory'].buffer);
    this.buffer = memory;

    const program = await WebAssembly.instantiate(binary, { env: imports });
    this.Huff_Compress = program.instance.exports.Huff_Compress;
    this.Huff_Decompress = program.instance.exports.Huff_Decompress;
    this.HuffmanGetBit = program.instance.exports.HuffmanGetBit;
    this.HuffmanGetSymbol = program.instance.exports.HuffmanGetSymbol;
    this.HuffmanPutBit = program.instance.exports.HuffmanPutBit;
    this.HuffmanPutSymbol = program.instance.exports.HuffmanPutSymbol;

    this.isInit = true;
  }

  async compressMessage(message) {
    if (!this.isInit) await this.init();
    const msg = this.msgData;
    const msgStart = msg + 64;

    if (typeof message ==='string') message = message.split('');
    for (let i = msg; i < msgStart + message.length; i++) {
      this.buffer[i] = 0;
    }

    const msgLength = message.length + 1;
    this.buffer[msg + 20] = msgLength & 255;
    this.buffer[msg + 21] = (msgLength >> 8) & 255;
    this.buffer[msg + 22] = 0;
    this.buffer[msg + 23] = 0;

    for (let i = 0; i < message.length; i++) {
      this.buffer[msgStart + i] = message[i].charCodeAt(0);
    }
    this.buffer[msgStart + message.length] = 0;

    this.Huff_Compress(msg, 12);
    const compressedLength = (this.buffer[msg + 21] << 8) + this.buffer[msg + 20];
    const compressed = this.buffer.slice(msgStart, msgStart + compressedLength);
    return compressed;
  }

  async decompressMessage(message, offset) {
    if (!this.isInit) await this.init();
    const m = [];
    for (let i = 0; i < message.length; i++) {
      m.push(message[i]);
    }
    return this.readBits(m, offset);
  }

  readBits(m, offset, bits = 8) {
    const base = this.msgData;
    let value = 0;
    const nbits = bits & 7;
    for (let i = 0; i < m.length; i++) {
      this.buffer[base + i] = m[i];
    }
    let bitIndex = offset;
    if (nbits) {
      for (let i = 0; i < nbits; i++) {
        value |= this.HuffmanGetBit(base, bitIndex) << i;
        bitIndex++;
      }
      bits -= nbits;
    }
    if (bits) {
      for (let i = 0; i < bits; i += 8) {
        bitIndex += this.HuffmanGetSymbol(this.msgData - 4, base, bitIndex);
        value |= this.buffer[this.msgData - 4] << (i + nbits);
      }
    }
    return [bitIndex, value];
  }

  writeBits(msgBytes, offset, value, bits) {
    const base = this.msgData;
    const bitIndex = offset;
    const nbits = bits & 7;
    for (let j = 0; j < this.MAX_MSGLEN; j++) {
      if (j < msgBytes.length) {
        this.buffer[base + j] = msgBytes[j] & 0xFF;
      } else {
        this.buffer[base + j] = 0;
      }
    }
    value &= (0xffffffff >> (32 - bits));
    if (nbits) {
      for (let i = 0; i < nbits; i++) {
        this.HuffmanPutBit(base, bitIndex, value & 1);
        bitIndex++;
        value = value >> 1;
      }
      bits = bits - nbits;
    }
    if (bits) {
      for (let i = 0; i < bits; i += 8) {
        bitIndex += this.HuffmanPutSymbol(base, bitIndex, value & 0xFF);
        value = value >> 8;
      }
    }
    return [bitIndex, this.buffer.slice(base, base + (bitIndex >> 3) + 1)];
  }
}

module.exports = HuffmanCompressor;

Code Breakdown

This is a Node.js code snippet that appears to be implementing a Huffman coding compression and decompression library. Huffman coding is a lossless data compression algorithm that assigns variable-length codes to characters in a data set.

Variables and Functions

Function Explanations

Note: The code assumes that the huffman WASM module is present and correctly loaded. The init function is not implemented in this code snippet.