WARFRAME Wiki
WARFRAME Wiki
WARFRAME Wiki
Official wiki
9,067
pages


Codec is a library for (de)compression algorithms implemented in pure Lua. Originally created for World of Warcraft as LibCompress.lua, the library has been ported to be used on MediaWiki-based wikis.

Authors: jjsheets and Galmok of European Stormrage (Horde) Email : sheets.jeff@gmail.com and galmok@gmail.com Licence: GPL version 2 (General Public License) Revision: $Revision: 83 $ Date: $Date: 2018-07-03 14:33:48 +0000 (Tue, 03 Jul 2018) $

Source

For more documentation and original source see https://www.wowace.com/projects/libcompress

Documentation

Package items

LibCompress:CompressLZW(uncompressed) (function)
Compresses a string using LZW algorithm. Unless the uncompressed string starts with "\002", this is guaranteed to return a string equal to or smaller than the passed string. the returned string will only contain "\000" characters in rare circumstances, and will contain none if the source string has none.
Parameter: uncompressed Raw string to be compressed using LZW algorithm (string)
Returns: compressed string if the resultant string is smaller than the original input, otherwise returns the original input since compression does not result in smaller size (string)
LibCompress:DecompressLZW(compressed) (function)
Decompresses a string using LZW algorithm. If the passed string is a compressed string, this will decompress it and return the decompressed string. Otherwise it return an error message Compressed strings are marked by beginning with "\002"
Parameter: compressed LZW compressed string to be decompressed (string)
Returns: uncompressed result (string)
LibCompress:CompressHuffman(uncompressed) (function)
Compresses a string using Huffman coding algorithm. Word size for this huffman algorithm is 8 bits (1 byte). This means the best compression is representing 1 byte with 1 bit, i.e. compress to 0.125 of original size.
Parameter: uncompressed Raw string to be compressed using Huffman coding algorithm (string)
Returns: compressed string if the resultant string is smaller than the original input, otherwise returns the original input since compression does not result in smaller size (string)
LibCompress:DecompressHuffman(compressed) (function)
Decompresses a string using Huffman coding algorithm.
Parameter: compressed Huffman compressed string to be decompressed (string)
Returns: uncompressed result (string)
LibCompress:Store(uncompressed) (function)
Prepends "\001" to uncompressed string to mark as uncompressed.
Parameter: uncompressed Raw uncompressed string (string)
Returns: Uncompressed string prepended with "\001" (string)
LibCompress:DecompressUncompressed(data) (function)
Returns uncompressed string without prepended codec (e. g. "\001" or "\003").
Parameter: data Raw uncompressed string (string)
Returns: Uncompressed string without prepended codec (string)
LibCompress:Compress(data) (function)
Generic method that tries all compression codecs (LZW and Huffman) and return the best result.
Parameter: data Raw uncompressed string (string)
Returns: compressed string if the resultant string is smaller than the original input, otherwise returns the original input since compression does not result in smaller size (string)
LibCompress:Decompress(data) (function)
Generic method to decompress either a LZW or Huffman compressed string.
Parameter: data LZW or Huffman compressed string to be decompressed (string)
Returns: uncompressed result (string)
LibCompress:GetEncodeTable(reservedChar, escapeChars, mapChars) (function)
Builds an encoding table.
Parameters:
  • reservedChar String of reserved characters with no spaces between each unique character (string)
  • escapeChars String of escape characters with no spaces between each unique character (string)
  • mapChars Sting of characters with no spaces between each unique character; used for encoding reserved characters (string)
Returns: Encoding table (table)
LibCompress:GetAddonEncodeTable(reservedChar, escapeChars, mapChars) (function)
Addons: Call this only once and reuse the returned table for all encodings/decodings.
Parameters:
  • reservedChar String of reserved characters with no spaces between each unique character (string)
  • escapeChars String of escape characters with no spaces between each unique character (string)
  • mapChars Sting of characters with no spaces between each unique character; used for encoding reserved characters (string)
Returns: Encoding table (table)
LibCompress:GetChatEncodeTable(reservedChar, escapeChars, mapChars) (function)
Addons: Call this only once and reuse the returned table for all encodings/decodings.
Parameters:
  • reservedChar String of reserved characters with no spaces between each unique character (string)
  • escapeChars String of escape characters with no spaces between each unique character (string)
  • mapChars Sting of characters with no spaces between each unique character; used for encoding other characters (string)
Returns: Encoding table (table)
LibCompress:Encode7bit(str) (function)
Encodes data using values from 0 to 127 inclusive.
Parameter: str String to be encoded (string)
Returns: Encoded data using 7 bits (string)
LibCompress:Decode7bit(str) (function)
Decodes data that has only values from 0 to 127 inclusive.
Parameter: str String to be decoded (string)
Returns: Decoded data (string)
LibCompress:fcs16init() (function)
Initializes FCS-16 checksum/hash.
Returns: Value for an unsigned 16 bit integer that has all bits set (number)
LibCompress:fcs16update(uFcs16, pBuffer) (function)
Updates FCS-16 checksum/hash.
Parameters:
  • uFcs16 FCS-16 checksum/hash (number)
  • pBuffer Data input (string)
Returns: Resultant checksum/hash (number)
LibCompress:fcs16final(uFcs16) (function)
Performs the final bitwise XOR operation for FCS-16 checksum/hash.
Parameter: uFcs16 FCS-16 checksum/hash (number)
Returns: Resultant checksum/hash (number)
LibCompress:FCS16(...) (function)
Creates a FCS-16 checksum/hash based on input data.
Parameter: ... Input data (string)
Returns: Resultant checksum/hash (number)
LibCompress:fcs16init() (function)
Initializes FCS-32 checksum/hash.
Returns: Value for a signed 32 bit integer that has all bits set (number)
LibCompress:fcs32update(uFcs32, pBuffer) (function)
Updates FCS-32 checksum/hash.
Parameters:
  • uFcs32 FCS-32 checksum/hash (number)
  • pBuffer Data input (string)
Returns: Resultant checksum/hash (number)
LibCompress:fcs32final(uFcs32) (function)
Performs the final bitwise NOT operation for FCS-32 checksum/hash.
Parameter: uFcs32 FCS-32 checksum/hash (number)
Returns: Resultant checksum/hash (number)
LibCompress:FCS32(...) (function)
Creates a FCS-32 checksum/hash based on input data.
Parameter: ... Input data (string)
Returns: Resultant checksum/hash (number)

Created with Docbunto

See Also

Code


---	'''Codec''' is a library for (de)compression algorithms implemented in pure Lua.
--	
--	Originally created for World of Warcraft as LibCompress.lua, 
--	the library has been ported to be used on MediaWiki-based wikis.
--	
--	Authors: jjsheets and Galmok of European Stormrage (Horde)
--	Email : sheets.jeff@gmail.com and galmok@gmail.com
--	Licence: GPL version 2 (General Public License)
--	Revision: $Revision: 83 $
--	Date: $Date: 2018-07-03 14:33:48 +0000 (Tue, 03 Jul 2018) $
--	
--	@module		codec
--	@alias		LibCompress
--	@author		jjsheets (sheets.jeff@gmail.com)
--	@author		Galmok of European Stormrage (Horde) (galmok@gmail.com)
--	@license	GPL version 2 (General Public License)
--	@require	bit32
--	@release	beta
--	<nowiki>

local LibCompress = {}

local bit = require("bit32")

-- list of codecs in this file:
-- \000 - Never used
-- \001 - Uncompressed
-- \002 - LZW
-- \003 - Huffman


-- local is faster than global
local type = type
local tostring = tostring
local select = select
local next = next
local setmetatable = setmetatable
local rawset = rawset
local table_insert = table.insert
local table_remove = table.remove
local table_concat = table.concat
local string_char = string.char
local string_byte = string.byte
local string_len = string.len
local string_sub = string.sub
local unpack = unpack
local pairs = pairs
local sort = table.sort
local math_modf = math.modf
local bit_band = bit.band
local bit_bor = bit.bor
local bit_bxor = bit.bxor
local bit_bnot = bit.bnot
local bit_lshift = bit.lshift
local bit_rshift = bit.rshift

-- compression algorithms

-- LZW codec
-- implemented by sheets.jeff@gmail.com

-- encode is used to uniquely encode a number into a sequence of bytes that can be decoded using decode()
-- the bytes returned by this do not contain "\000"
local function encode(x)
	local bytes = {}

	bytes[#bytes + 1] = x % 255
	x = math.floor(x / 255)

	while x > 0 do
		bytes[#bytes + 1] = x % 255
		x = math.floor(x / 255)
	end
	if #bytes == 1 and bytes[1] > 0 and bytes[1] < 250 then
		return string_char(bytes[1])
	else
		for i = 1, #bytes do
			bytes[i] = bytes[i] + 1
		end
		return string_char(256 - #bytes, unpack(bytes))
	end
end

-- decode converts a unique character sequence into its equivalent number, from ss, beginning at the ith char.
-- returns the decoded number and the count of characters used in the decode process.
local function decode(ss, i)
	i = i or 1
	local a = string_byte(ss, i, i)
	if a > 249 then
		local r = 0
		a = 256 - a
		for n = i + a, i + 1, -1 do
			r = r * 255 + string_byte(ss, n, n) - 1
		end
		return r, a + 1
	else
		return a, 1
	end
end

---	Compresses a string using LZW algorithm.
--	Unless the uncompressed string starts with "\002", this is guaranteed to return a string equal to or smaller than
--	the passed string.
--	the returned string will only contain "\000" characters in rare circumstances, and will contain none if the
--	source string has none.
--	@function		LibCompress:CompressLZW
--	@param			{string} uncompressed Raw string to be compressed using LZW algorithm
--	@return			{string} compressed string if the resultant string is smaller than the original input,
--							 otherwise returns the original input since compression does not result in smaller size
function LibCompress:CompressLZW(uncompressed)
	if type(uncompressed) ~= "string" then
		error("LibCompress:CompressLZW(uncompressed): Can only compress strings")
	end
	local dict_size = 256
	local dict = {}

	local result = {"\002"}
	local w = ''
	local ressize = 1

	for i = 0, 255 do
		dict[string_char(i)] = i
	end

	for i = 1, #uncompressed do
		local c = uncompressed:sub(i, i)
		local wc = w..c
		if dict[wc] then
			w = wc
		else
			dict[wc] = dict_size
			dict_size = dict_size + 1
			local r = encode(dict[w])
			ressize = ressize + #r
			result[#result + 1] = r
			w = c
		end
	end

	if w then
		local r = encode(dict[w])
		ressize = ressize + #r
		result[#result + 1] = r
	end

	if (#uncompressed + 1) > ressize then
		return table_concat(result)
	else
		return string_char(1)..uncompressed
	end
end

---	Decompresses a string using LZW algorithm.
--	If the passed string is a compressed string, this will decompress it and return the decompressed string.
--	Otherwise it return an error message
--	Compressed strings are marked by beginning with "\002"
--	@function		LibCompress:DecompressLZW
--	@param			{string} compressed LZW compressed string to be decompressed
--	@return			{string} uncompressed result
function LibCompress:DecompressLZW(compressed)
	if type(compressed) ~= "string" then
		error("LibCompress:DecompressLZW(compressed): Can only uncompress strings")
	end
	if compressed:sub(1, 1) ~= "\002" then
		error("LibCompress:DecompressLZW(compressed): Can only decompress LZW compressed data ("..tostring(compressed:sub(1, 1))..")")
	end
	
	compressed = compressed:sub(2)
	local dict_size = 256
	local dict = {}

	for i = 0, 255 do
		dict[i] = string_char(i)
	end

	local result = {}
	local t = 1
	local delta, k
	k, delta = decode(compressed, t)
	t = t + delta
	result[#result + 1] = dict[k]

	local w = dict[k]
	local entry
	while t <= #compressed do
		k, delta = decode(compressed, t)
		t = t + delta
		entry = dict[k] or (w..w:sub(1, 1))
		result[#result + 1] = entry
		dict[dict_size] = w..entry:sub(1, 1)
		dict_size = dict_size + 1
		w = entry
	end
	return table_concat(result)
end


-- Huffman codec
-- implemented by Galmok of European Stormrage (Horde), galmok@gmail.com

local function addCode(tree, bcode, length)
	if tree then
		tree.bcode = bcode
		tree.blength = length
		if tree.c1 then
			addCode(tree.c1, bit_bor(bcode, bit_lshift(1, length)), length + 1)
		end
		if tree.c2 then
			addCode(tree.c2, bcode, length + 1)
		end
	end
end

local function escape_code(code, length)
	local escaped_code = 0
	local b
	local l = 0
	for i = length -1, 0, - 1 do
		b = bit_band(code, bit_lshift(1, i)) == 0 and 0 or 1
		escaped_code = bit_lshift(escaped_code, 1 + b) + b
		l = l + b
	end
	if length + l > 32 then
		error("escape_code(code, length): escape overflow ("..(length + l)..")")
	end
	return escaped_code, length + l
end

local compressed_size = 0
local remainder
local remainder_length
local function addBits(tbl, code, length)
	if remainder_length+length >= 32 then
		-- we have at least 4 bytes to store; bulk it
		remainder = remainder + bit_lshift(code, remainder_length) -- this overflows! Top part of code is lost (but we handle it below)
		-- remainder now holds 4 full bytes to store. So lets do it.
		compressed_size = compressed_size + 1
		tbl[compressed_size] = string_char(bit_band(remainder, 255)) ..
			string_char(bit_band(bit_rshift(remainder, 8), 255)) ..
			string_char(bit_band(bit_rshift(remainder, 16), 255)) ..
			string_char(bit_band(bit_rshift(remainder, 24), 255))
		remainder = 0
		code = bit_rshift(code, 32 - remainder_length)
		length =  remainder_length + length - 32
		remainder_length = 0
	end
	if remainder_length+length >= 16 then
		-- we have at least 2 bytes to store; bulk it
		remainder = remainder + bit_lshift(code, remainder_length)
		remainder_length = length + remainder_length
		-- remainder now holds at least 2 full bytes to store. So lets do it.
		compressed_size = compressed_size + 1
		tbl[compressed_size] = string_char(bit_band(remainder, 255)) .. string_char(bit_band(bit_rshift(remainder, 8), 255))
		remainder = bit_rshift(remainder, 16)
		code = remainder
		length = remainder_length - 16
		remainder = 0
		remainder_length = 0
	end
	remainder = remainder + bit_lshift(code, remainder_length)
	remainder_length = length + remainder_length
	if remainder_length >= 8 then
		compressed_size = compressed_size + 1
		tbl[compressed_size] = string_char(bit_band(remainder, 255))
		remainder = bit_rshift(remainder, 8)
		remainder_length = remainder_length -8
	end
end

---	Compresses a string using Huffman coding algorithm.
--	Word size for this huffman algorithm is 8 bits (1 byte).
--	This means the best compression is representing 1 byte with 1 bit, i.e. compress to 0.125 of original size.
--	@function		LibCompress:CompressHuffman
--	@param			{string} uncompressed Raw string to be compressed using Huffman coding algorithm
--	@return			{string} compressed string if the resultant string is smaller than the original input,
--							 otherwise returns the original input since compression does not result in smaller size
function LibCompress:CompressHuffman(uncompressed)
	if type(uncompressed) ~= "string" then
		error("LibCompress:CompressHuffman(uncompressed): Can only compress strings")
	end
	
	if #uncompressed == 0 then
		return "\001"
	end

	-- make histogram
	local hist = {}
	-- don't have to use all data to make the histogram
	local uncompressed_size = string_len(uncompressed)
	local c
	for i = 1, uncompressed_size do
		c = string_byte(uncompressed, i)
		hist[c] = (hist[c] or 0) + 1
	end

	--Start with as many leaves as there are symbols.
	local leafs = {}
	local leaf
	local symbols = {}
	for symbol, weight in pairs(hist) do
		leaf = { symbol=string_char(symbol), weight=weight }
		symbols[symbol] = leaf
		table_insert(leafs, leaf)
	end

	-- Enqueue all leaf nodes into the first queue (by probability in increasing order,
	-- so that the least likely item is in the head of the queue).
	sort(leafs, function(a, b)
		if a.weight < b.weight then
			return true
		elseif a.weight > b.weight then
			return false
		else
			return nil
		end
	end)

	local nLeafs = #leafs

	-- create tree
	local huff = {}
	--While there is more than one node in the queues:
	local length, height, li, hi, leaf1, leaf2
	local newNode
	while (#leafs + #huff > 1) do
		-- Dequeue the two nodes with the lowest weight.
		-- Dequeue first
		if not next(huff) then
			li, leaf1 = next(leafs)
			table_remove(leafs, li)
		elseif not next(leafs) then
			hi, leaf1 = next(huff)
			table_remove(huff, hi)
		else
			li, length = next(leafs)
			hi, height = next(huff)
			if length.weight <= height.weight then
				leaf1 = length
				table_remove(leafs, li)
			else
				leaf1 = height
				table_remove(huff, hi)
			end
		end

		-- Dequeue second
		if not next(huff) then
			li, leaf2 = next(leafs)
			table_remove(leafs, li)
		elseif not next(leafs) then
			hi, leaf2 = next(huff)
			table_remove(huff, hi)
		else
			li, length = next(leafs)
			hi, height = next(huff)
			if length.weight <= height.weight then
				leaf2 = length
				table_remove(leafs, li)
			else
				leaf2 = height
				table_remove(huff, hi)
			end
		end

		--Create a new internal node, with the two just-removed nodes as children (either node can be either child) and the sum of their weights as the new weight.
		newNode = {
			c1 = leaf1,
			c2 = leaf2,
			weight = leaf1.weight + leaf2.weight
		}
		table_insert(huff,newNode)
	end

	if #leafs > 0 then
		li, length = next(leafs)
		table_insert(huff, length)
		table_remove(leafs, li)
	end
	huff = huff[1]

	-- assign codes to each symbol
	-- c1 = "0", c2 = "1"
	-- As a common convention, bit '0' represents following the left child and bit '1' represents following the right child.
	-- c1 = left, c2 = right

	addCode(huff, 0, 0)
	if huff then
		huff.bcode = 0
		huff.blength = 1
	end

	-- READING
	-- bitfield = 0
	-- bitfield_len = 0
	-- read byte1
	-- bitfield = bitfield + bit_lshift(byte1, bitfield_len)
	-- bitfield_len = bitfield_len + 8
	-- read byte2
	-- bitfield = bitfield + bit_lshift(byte2, bitfield_len)
	-- bitfield_len = bitfield_len + 8
	-- (use 5 bits)
	--	word = bit_band( bitfield, bit_lshift(1,5)-1)
	--	bitfield = bit_rshift( bitfield, 5)
	--	bitfield_len = bitfield_len - 5
	-- read byte3
	-- bitfield = bitfield + bit_lshift(byte3, bitfield_len)
	-- bitfield_len = bitfield_len + 8

	-- WRITING
	remainder = 0
	remainder_length = 0

	local compressed = {}
	--compressed_size = 0

	-- first byte is version info. 0 = uncompressed, 1 = 8 - bit word huffman compressed
	compressed[1] = "\003"

	-- Header: byte 0 = #leafs, bytes 1-3 = size of uncompressed data
	-- max 2^24 bytes
	length = string_len(uncompressed)
	compressed[2] = string_char(bit_band(nLeafs -1, 255))	-- number of leafs
	compressed[3] = string_char(bit_band(length, 255))			-- bit 0-7
	compressed[4] = string_char(bit_band(bit_rshift(length, 8), 255))	-- bit 8-15
	compressed[5] = string_char(bit_band(bit_rshift(length, 16), 255))	-- bit 16-23
	compressed_size = 5

	-- create symbol/code map
	local escaped_code, escaped_code_len, success, msg
	for symbol, leaf in pairs(symbols) do
		addBits(compressed, symbol, 8)
		escaped_code, escaped_code_len = escape_code(leaf.bcode, leaf.blength)
		if not escaped_code then
			return nil, escaped_code_len
		end
		addBits(compressed, escaped_code, escaped_code_len)
		addBits(compressed, 3, 2)
	end

	-- create huffman code
	local large_compressed = {}
	local large_compressed_size = 0
	local ulimit
	for i = 1, length, 200 do
		ulimit = length < (i + 199) and length or (i + 199)

		for sub_i = i, ulimit do
			c = string_byte(uncompressed, sub_i)
			addBits(compressed, symbols[c].bcode, symbols[c].blength)
		end

		large_compressed_size = large_compressed_size + 1
		large_compressed[large_compressed_size] = table_concat(compressed, "", 1, compressed_size)
		compressed_size = 0
	end

	-- add remaining bits (if any)
	if remainder_length > 0 then
		large_compressed_size = large_compressed_size + 1
		large_compressed[large_compressed_size] = string_char(remainder)
	end
	local compressed_string = table_concat(large_compressed, "", 1, large_compressed_size)

	-- is compression worth it? If not, return uncompressed data.
	if (#uncompressed + 1) <= #compressed_string then
		return "\001"..uncompressed
	end

	return compressed_string
end

-- lookuptable (cached between calls)
local lshiftMask = {}
setmetatable(lshiftMask, {
	__index = function (t, k)
		local v = bit_lshift(1, k)
		rawset(t, k, v)
		return v
	end
})

-- lookuptable (cached between calls)
local lshiftMinusOneMask = {}
setmetatable(lshiftMinusOneMask, {
	__index = function (t, k)
		local v = bit_lshift(1, k) -  1
		rawset(t, k, v)
		return v
	end
})

local function bor64(valueA_high, valueA, valueB_high, valueB)
	return bit_bor(valueA_high, valueB_high),
		bit_bor(valueA, valueB)
end

local function band64(valueA_high, valueA, valueB_high, valueB)
	return bit_band(valueA_high, valueB_high),
		bit_band(valueA, valueB)
end

local function lshift64(value_high, value, lshift_amount)
	if lshift_amount == 0 then
		return value_high, value
	end
	if lshift_amount >= 64 then
		return 0, 0
	end
	if lshift_amount < 32 then
		return bit_bor(bit_lshift(value_high, lshift_amount), bit_rshift(value, 32-lshift_amount)),
			bit_lshift(value, lshift_amount)
	end
	-- 32-63 bit shift
	return bit_lshift(value, lshift_amount), -- builtin modulus 32 on shift amount
		0
end

local function rshift64(value_high, value, rshift_amount)
	if rshift_amount == 0 then
		return value_high, value
	end
	if rshift_amount >= 64 then
		return 0, 0
	end
	if rshift_amount < 32 then
		return bit_rshift(value_high, rshift_amount),
			bit_bor(bit_lshift(value_high, 32-rshift_amount), bit_rshift(value, rshift_amount))
	end
	-- 32-63 bit shift
	return 0,
		bit_rshift(value_high, rshift_amount)
end

local function getCode2(bitfield_high, bitfield, field_len)
	if field_len >= 2 then
		-- [bitfield_high..bitfield]: bit 0 is right most in bitfield. bit <field_len-1> is left most in bitfield_high
		local b1, b2, remainder_high, remainder
		for i = 0, field_len - 2 do
			b1 = i <= 31 and bit_band(bitfield, bit_lshift(1, i)) or bit_band(bitfield_high, bit_lshift(1, i)) -- for shifts, 32 = 0 (5 bit used)
			b2 = (i+1) <= 31 and bit_band(bitfield, bit_lshift(1, i+1)) or bit_band(bitfield_high, bit_lshift(1, i+1))
			if not (b1 == 0) and not (b2 == 0) then
				-- found 2 bits set right after each other (stop bits) with i pointing at the first stop bit
				-- return the two bitfields separated by the two stopbits (3 values for each: bitfield_high, bitfield, field_len)
				-- bits left: field_len - (i+2)
				remainder_high, remainder = rshift64(bitfield_high, bitfield, i+2)
				-- first bitfield is the lower part
				return (i-1) >= 32 and bit_band(bitfield_high, bit_lshift(1, i) - 1) or 0,
					i >= 32 and bitfield or bit_band(bitfield, bit_lshift(1, i) - 1),
					i,
					remainder_high,
					remainder,
					field_len-(i+2)
			end
		end
	end
	return nil
end

local function unescape_code(code, code_len)
	local unescaped_code = 0
	local b
	local l = 0
	local i = 0
	while i < code_len do
		b = bit_band( code, lshiftMask[i])
		if not (b == 0) then
			unescaped_code = bit_bor(unescaped_code, lshiftMask[l])
			i = i + 1
		end
		i = i + 1
		l = l + 1
	end
	return unescaped_code, l
end

---	Decompresses a string using Huffman coding algorithm.
--	@function		LibCompress:DecompressHuffman
--	@param			{string} compressed Huffman compressed string to be decompressed
--	@return			{string} uncompressed result
function LibCompress:DecompressHuffman(compressed)
	if type(compressed) ~= "string" then
		error("LibCompress:DecompressHuffman(compressed): Can only uncompress strings")
	end
	
	local compressed_size = #compressed
	--decode header
	local info_byte = string_byte(compressed)
	-- is data compressed
	if info_byte == 1 then
		return compressed:sub(2) --return uncompressed data
	end
	if info_byte ~= 3 then
		error("LibCompress:DecompressHuffman(compressed): Can only decompress Huffman compressed data ("..tostring(info_byte)..")")
	end
	
	local num_symbols = string_byte(string_sub(compressed, 2, 2)) + 1
	local c0 = string_byte(string_sub(compressed, 3, 3))
	local c1 = string_byte(string_sub(compressed, 4, 4))
	local c2 = string_byte(string_sub(compressed, 5, 5))
	local orig_size = c2 * 65536 + c1 * 256 + c0
	if orig_size == 0 then
		return ""
	end

	-- decode code -> symbol map
	local bitfield = 0
	local bitfield_high = 0
	local bitfield_len = 0
	local map = {} -- only table not reused in Huffman decode.
	setmetatable(map, {
		__index = function (t, k)
			local v = {}
			rawset(t, k, v)
			return v
		end
	})

	local i = 6 -- byte 1-5 are header bytes
	local c, cl
	local minCodeLen = 1000
	local maxCodeLen = 0
	local symbol, code_high, code, code_len, temp_high, temp, _bitfield_high, _bitfield, _bitfield_len
	local n = 0
	local state = 0 -- 0 = get symbol (8 bits),  1 = get code (varying bits, ends with 2 bits set)
	while n < num_symbols do
		if i > compressed_size then
			error("LibCompress:DecompressHuffman(compressed): Cannot decode map")
		end

		c = string_byte(compressed, i)
		temp_high, temp = lshift64(0, c, bitfield_len)
		bitfield_high, bitfield = bor64(bitfield_high, bitfield, temp_high, temp)
		bitfield_len = bitfield_len + 8

		if state == 0 then
			symbol = bit_band(bitfield, 255)
			bitfield_high, bitfield = rshift64(bitfield_high, bitfield, 8)
			bitfield_len = bitfield_len - 8
			state = 1 -- search for code now
		else
			code_high, code, code_len, _bitfield_high, _bitfield, _bitfield_len = getCode2(bitfield_high, bitfield, bitfield_len)
			if code_high then
				bitfield_high, bitfield, bitfield_len = _bitfield_high, _bitfield, _bitfield_len
				if code_len > 32 then
					error("LibCompress:DecompressHuffman(compressed): Unsupported symbol code length ("..code_len..")")
				end
				c, cl = unescape_code(code, code_len)
				map[cl][c] = string_char(symbol)
				minCodeLen = cl < minCodeLen and cl or minCodeLen
				maxCodeLen = cl > maxCodeLen and cl or maxCodeLen
				--print("symbol: "..string_char(symbol).."  code: "..tobinary(c, cl))
				n = n + 1
				state = 0 -- search for next symbol (if any)
			end
		end
		i = i + 1
	end

	-- don't create new subtables for entries not in the map. Waste of space.
	-- But do return an empty table to prevent runtime errors. (instead of returning nil)
	local mt = {}
	setmetatable(map, {
		__index = function (t, k)
			return mt
		end
	})

	local uncompressed = {}
	local large_uncompressed = {}
	local uncompressed_size = 0
	local large_uncompressed_size = 0
	local test_code
	local test_code_len = minCodeLen
	local dec_size = 0
	compressed_size = compressed_size + 1
	local temp_limit = 200 -- first limit of uncompressed data. large_uncompressed will hold strings of length 200
	temp_limit = temp_limit > orig_size and orig_size or temp_limit

	while true do
		if test_code_len <= bitfield_len then
			test_code = bit_band( bitfield, lshiftMinusOneMask[test_code_len])
			symbol = map[test_code_len][test_code]

			if symbol then
				uncompressed_size = uncompressed_size + 1
				uncompressed[uncompressed_size] = symbol
				dec_size = dec_size + 1
				if dec_size >= temp_limit then
					if dec_size >= orig_size then -- checked here for speed reasons
						break
					end
					-- process compressed bytes in smaller chunks
					large_uncompressed_size = large_uncompressed_size + 1
					large_uncompressed[large_uncompressed_size] = table_concat(uncompressed, "", 1, uncompressed_size)
					uncompressed_size = 0
					temp_limit = temp_limit + 200 -- repeated chunk size is 200 uncompressed bytes
					temp_limit = temp_limit > orig_size and orig_size or temp_limit
				end

				bitfield = bit_rshift(bitfield, test_code_len)
				bitfield_len = bitfield_len - test_code_len
				test_code_len = minCodeLen
			else
				test_code_len = test_code_len + 1
				if test_code_len > maxCodeLen then
					error("LibCompress:DecompressHuffman(compressed): Decompression error at "..tostring(i).."/"..tostring(#compressed))
				end
			end
		else
			c = string_byte(compressed, i)
			bitfield = bitfield + bit_lshift(c or 0, bitfield_len)
			bitfield_len = bitfield_len + 8
			if i > compressed_size then
				break
			end
			i = i + 1
		end
	end
	
	return table_concat(large_uncompressed, "", 1, large_uncompressed_size)..table_concat(uncompressed, "", 1, uncompressed_size)
end


-- Generic codec interface

---	Prepends "\001" to uncompressed string to mark as uncompressed.
--	@function		LibCompress:Store
--	@param			{string} uncompressed Raw uncompressed string
--	@return			{string} Uncompressed string prepended with "\001"
function LibCompress:Store(uncompressed)
	if type(uncompressed) ~= "string" then
		error("LibCompress:Store(uncompressed): Can only compress strings")
	end
	return "\001"..uncompressed
end

---	Returns uncompressed string without prepended codec (e.g. "\001" or "\003").
--	@function		LibCompress:DecompressUncompressed
--	@param			{string} data Raw uncompressed string
--	@return			{string} Uncompressed string without prepended codec
function LibCompress:DecompressUncompressed(data)
	if type(data) ~= "string" then
		error("LibCompress:DecompressUncompressed(data): Can only handle strings")
	end
	if string_byte(data) ~= 1 then
		error("LibCompress:DecompressUncompressed(data): Can only handle uncompressed data")
	end
	return data:sub(2)
end

local compression_methods = {
	[2] = LibCompress.CompressLZW,
	[3] = LibCompress.CompressHuffman
}

local decompression_methods = {
	[1] = LibCompress.DecompressUncompressed,
	[2] = LibCompress.DecompressLZW,
	[3] = LibCompress.DecompressHuffman
}

---	Generic method that tries all compression codecs (LZW and Huffman) and return the best result.
--	@function		LibCompress:Compress
--	@param			{string} data Raw uncompressed string
--	@return			{string} compressed string if the resultant string is smaller than the original input,
--							 otherwise returns the original input since compression does not result in smaller size
function LibCompress:Compress(data)
	local method = next(compression_methods)
	local result = compression_methods[method](self, data)
	local n
	method = next(compression_methods, method)
	while method do
		n = compression_methods[method](self, data)
		if #n < #result then
			result = n
		end
		method = next(compression_methods, method)
	end
	return result
end

---	Generic method to decompress either a LZW or Huffman compressed string.
--	@function		LibCompress:Decompress
--	@param			{string} data LZW or Huffman compressed string to be decompressed
--	@return			{string} uncompressed result
function LibCompress:Decompress(data)
	local header_info = string_byte(data)
	if decompression_methods[header_info] then
		return decompression_methods[header_info](self, data)
	end
	error("LibCompress:Decompress(data): Unknown compression method ("..tostring(header_info)..")")
end


-- Encoding algorithms

-- Prefix encoding algorithm
-- implemented by Galmok of European Stormrage (Horde), galmok@gmail.com

--[[
	Howto: Encode and Decode:

	3 functions are supplied, 2 of them are variants of the first.  They return a table with functions to encode and decode text.

	table, msg = LibCompress:GetEncodeTable(reservedChars, escapeChars,  mapChars)

		reservedChars: The characters in this string will not appear in the encoded data.
		escapeChars: A string of characters used as escape-characters (don't supply more than needed). #escapeChars >= 1
		mapChars: First characters in reservedChars maps to first characters in mapChars.  (#mapChars <= #reservedChars)

	return value:
		table
			if nil then msg holds an error message, otherwise use like this:

			encoded_message = table:Encode(message)
			message = table:Decode(encoded_message)

	GetAddonEncodeTable: Sets up encoding for the addon channel (\000 is encoded)
	GetChatEncodeTable: Sets up encoding for the chat channel (many bytes encoded, see the function for details)

	Except for the mapped characters, all encoding will be with 1 escape character followed by 1 suffix, i.e. 2 bytes.
]]
-- to be able to match any requested byte value, the search string must be preprocessed
-- characters to escape with %:
-- ( ) . % + - * ? [ ] ^ $
-- "illegal" byte values:
-- 0 is replaces %z
local gsub_escape_table = {
	['\000'] = "%z",
	[('(')] = "%(",
	[(')')] = "%)",
	[('.')] = "%.",
	[('%')] = "%%",
	[('+')] = "%+",
	[('-')] = "%-",
	[('*')] = "%*",
	[('?')] = "%?",
	[('[')] = "%[",
	[(']')] = "%]",
	[('^')] = "%^",
	[('$')] = "%$"
}

local function escape_for_gsub(str)
	return str:gsub("([%z%(%)%.%%%+%-%*%?%[%]%^%$])", gsub_escape_table)
end

---	Builds an encoding table.
--	@function		LibCompress:GetEncodeTable
--	@param			{string} reservedChar String of reserved characters with no spaces between each unique character
--	@param			{string} escapeChars String of escape characters with no spaces between each unique character
--	@param			{string} mapChars Sting of characters with no spaces between each unique character; used for encoding reserved characters
--	@return			{table} Encoding table
function LibCompress:GetEncodeTable(reservedChars, escapeChars, mapChars)
	reservedChars = reservedChars or ""
	escapeChars = escapeChars or ""
	mapChars = mapChars or ""

	-- select a default escape character
	if escapeChars == "" then
		error("LibCompress:GetEncodeTable(reservedChars, escapeChars, mapChars): No escape characters supplied")
	end

	if #reservedChars < #mapChars then
		error("LibCompress:GetEncodeTable(reservedChars, escapeChars, mapChars): Number of reserved characters must be at least as many as the number of mapped chars")
	end

	if reservedChars == "" then
		error("LibCompress:GetEncodeTable(reservedChars, escapeChars, mapChars): No characters to encode")
	end

	-- list of characters that must be encoded
	local encodeBytes = reservedChars..escapeChars..mapChars

	-- build list of bytes not available as a suffix to a prefix byte
	local taken = {}
	for i = 1, string_len(encodeBytes) do
		taken[string_sub(encodeBytes, i, i)] = true
	end

	-- allocate a table to hold encode/decode strings/functions
	local codecTable = {}

	-- the encoding can be a single gsub, but the decoding can require multiple gsubs
	local decode_func_table = {}

	local encode_search = {}
	local encode_translate = {}
	local encode_func
	local decode_search = {}
	local decode_translate = {}
	local decode_func
	local c, r, to, from
	local escapeCharIndex, escapeChar = 0

	-- map single byte to single byte
	if #mapChars > 0 then
		for i = 1, #mapChars do
			from = string_sub(reservedChars, i, i)
			to = string_sub(mapChars, i, i)
			encode_translate[from] = to
			table_insert(encode_search, from)
			decode_translate[to] = from
			table_insert(decode_search, to)
		end
		codecTable["decode_search"..tostring(escapeCharIndex)] = "([".. escape_for_gsub(table_concat(decode_search)).."])"
		codecTable["decode_translate"..tostring(escapeCharIndex)] = decode_translate
		table_insert(decode_func_table, function(self, str)
			return str:gsub(self.decode_search..tostring(escapeCharIndex), self.decode_translate..tostring(escapeCharIndex))
		end)
	end

	-- map single byte to double-byte
	escapeCharIndex = escapeCharIndex + 1
	escapeChar = string_sub(escapeChars, escapeCharIndex, escapeCharIndex)
	r = 0 -- suffix char value to the escapeChar
	decode_search = {}
	decode_translate = {}
	for i = 1, string_len(encodeBytes) do
		c = string_sub(encodeBytes, i, i)
		if not encode_translate[c] then
			-- this loop will update escapeChar and r
			while r >= 256 or taken[string_char(r)] do
				r = r + 1
				if r > 255 then -- switch to next escapeChar
					codecTable["decode_search"..tostring(escapeCharIndex)] = escape_for_gsub(escapeChar).."([".. escape_for_gsub(table_concat(decode_search)).."])"
					codecTable["decode_translate"..tostring(escapeCharIndex)] = decode_translate
					table_insert(decode_func_table, function(self, str)
						return str:gsub(self.decode_search..tostring(escapeCharIndex), self.decode_translate..tostring(escapeCharIndex))
					end)

					escapeCharIndex  = escapeCharIndex + 1
					escapeChar = string_sub(escapeChars, escapeCharIndex, escapeCharIndex)

					if escapeChar == "" then -- we are out of escape chars and we need more!
						error("LibCompress:GetEncodeTable(reservedChars, escapeChars, mapChars): Out of escape characters")
					end

					r = 0
					decode_search = {}
					decode_translate = {}
				end
			end
			encode_translate[c] = escapeChar..string_char(r)
			table_insert(encode_search, c)
			decode_translate[string_char(r)] = c
			table_insert(decode_search, string_char(r))
			r = r + 1
		end
	end

	if r > 0 then
		codecTable["decode_search"..tostring(escapeCharIndex)] = escape_for_gsub(escapeChar).."([".. escape_for_gsub(table_concat(decode_search)).."])"
		codecTable["decode_translate"..tostring(escapeCharIndex)] = decode_translate
		table_insert(decode_func_table, function(self, str)
			return str:gsub(self.decode_search..tostring(escapeCharIndex), self.decode_translate..tostring(escapeCharIndex))
		end)
	end

	encode_search = "([".. escape_for_gsub(table_concat(encode_search)).."])"
	decode_search = escape_for_gsub(escapeChars).."([".. escape_for_gsub(table_concat(decode_search)).."])"

	encode_func = function(self, str)
		return str:gsub(self.encode_search, self.encode_translate)
	end
	decode_func = function(self, str)
		-- Decodes each encoded character
		-- (inefficient, but this is a workaround if loadstring() is not available 
		-- in Scribunto environment due to security reasons)
		for _, decode_single_char in ipairs(decode_func_table) do
			str = decode_single_char(self, str)
		end
		return str
	end
	codecTable.encode_search = encode_search
	codecTable.encode_translate = encode_translate
	codecTable.Encode = encode_func
	codecTable.decode_search = decode_search
	codecTable.decode_translate = decode_translate
	codecTable.Decode = decode_func

	codecTable.decode_func_table = decode_func_table -- to be deleted
	return codecTable
end

---	Addons: Call this only once and reuse the returned table for all encodings/decodings.
--	@function		LibCompress:GetAddonEncodeTable
--	@param			{string} reservedChar String of reserved characters with no spaces between each unique character
--	@param			{string} escapeChars String of escape characters with no spaces between each unique character
--	@param			{string} mapChars Sting of characters with no spaces between each unique character; used for encoding reserved characters
--	@return			{table} Encoding table
function LibCompress:GetAddonEncodeTable(reservedChars, escapeChars, mapChars)
	reservedChars = reservedChars or ""
	escapeChars = escapeChars or ""
	mapChars = mapChars or ""
	-- Following byte values are not allowed:
	-- \000
	if escapeChars == "" then
		escapeChars = "\001"
	end
	return self:GetEncodeTable( (reservedChars or "").."\000", escapeChars, mapChars)
end

---	Addons: Call this only once and reuse the returned table for all encodings/decodings.
--	@function		LibCompress:GetChatEncodeTable
--	@param			{string} reservedChar String of reserved characters with no spaces between each unique character
--	@param			{string} escapeChars String of escape characters with no spaces between each unique character
--	@param			{string} mapChars Sting of characters with no spaces between each unique character; used for encoding other characters
--	@return			{table} Encoding table
function LibCompress:GetChatEncodeTable(reservedChars, escapeChars, mapChars)
	reservedChars = reservedChars or ""
	escapeChars = escapeChars or ""
	mapChars = mapChars or ""
	-- Following byte values are not allowed:
	-- \000, s, S, \010, \013, \124, %
	-- Because SendChatMessage will error if an UTF8 multibyte character is incomplete,
	-- all character values above 127 have to be encoded to avoid this. This costs quite a bit of bandwidth (about 13-14%)
	-- Also, because drunken status is unknown for the received, strings used with SendChatMessage should be terminated with
	-- an identifying byte value, after which the server MAY add "...hic!" or as much as it can fit(!).
	-- Pass the identifying byte as a reserved character to this function to ensure the encoding doesn't contain that value.
	--  or use this: local message, match = arg1:gsub("^(.*)\029.-$", "%1")
	--  arg1 is message from channel, \029 is the string terminator, but may be used in the encoded datastream as well. :-)
	-- This encoding will expand data anywhere from:
	-- 0% (average with pure ascii text)
	-- 53.5% (average with random data valued zero to 255)
	-- 100% (only encoding data that encodes to two bytes)
	local r = {}

	for i = 128, 255 do
		table_insert(r, string_char(i))
	end

	reservedChars = "sS\000\010\013\124%"..table_concat(r)..(reservedChars or "")
	if escapeChars == "" then
		escapeChars = "\029\031"
	end

	if mapChars == "" then
		mapChars = "\015\020";
	end
	return self:GetEncodeTable(reservedChars, escapeChars, mapChars)
end


-- 7 bit encoding algorithm
-- implemented by Galmok of European Stormrage (Horde), galmok@gmail.com

-- The encoded data holds values from 0 to 127 inclusive. Additional encoding may be necessary.
-- This algorithm isn't exactly fast and be used with care and consideration

---	Encodes data using values from 0 to 127 inclusive.
--	@function		LibCompress:Encode7bit
--	@param			{string} str String to be encoded
--	@return			{string} Encoded data using 7 bits
function LibCompress:Encode7bit(str)
	local remainder = 0
	local remainder_length = 0
	local tbl = {}
	local encoded_size = 0
	local length = #str
	for i = 1, length do
		local code = string_byte(str, i)
		remainder = remainder + bit_lshift(code, remainder_length)
		remainder_length = 8 + remainder_length
		while remainder_length >= 7 do
			encoded_size = encoded_size + 1
			tbl[encoded_size] = string_char(bit_band(remainder, 127))
			remainder = bit_rshift(remainder, 7)
			remainder_length = remainder_length -7
		end
	end

	if remainder_length > 0 then
		encoded_size = encoded_size + 1
		tbl[encoded_size] = string_char(remainder)
	end
	return table_concat(tbl, "", 1, encoded_size)
end

---	Decodes data that has only values from 0 to 127 inclusive.
--	@function		LibCompress:Decode7bit
--	@param			{string} str String to be decoded
--	@return			{string} Decoded data
function LibCompress:Decode7bit(str)
	local bit8 = {}
	local decoded_size = 0
	local ch
	local i = 1
	local bitfield_len = 0
	local bitfield = 0
	local length = #str
	while true do
		if bitfield_len >= 8 then
			decoded_size = decoded_size + 1
			bit8[decoded_size] = string_char(bit_band(bitfield, 255))
			bitfield = bit_rshift(bitfield, 8)
			bitfield_len = bitfield_len - 8
		end
		ch = string_byte(str, i)
		bitfield=bitfield + bit_lshift(ch or 0, bitfield_len)
		bitfield_len = bitfield_len + 7
		if i > length then
			break
		end
		i = i + 1
	end
	return table_concat(bit8, "", 1, decoded_size)
end


-- Checksum/hash algorithms

-- FCS16/32 checksum algorithms
-- converted from C by Galmok of European Stormrage (Horde), galmok@gmail.com
-- usage:
-- 	code = LibCompress:fcs16init()
--	code = LibCompress:fcs16update(code, data1)
--	code = LibCompress:fcs16update(code, data2)
--	code = LibCompress:fcs16update(code, data...)
--	code = LibCompress:fcs16final(code)
--
--	data = string
--	fcs16 provides a 16 bit checksum, fcs32 provides a 32 bit checksum.


--[[/* The following copyright notice concerns only the FCS hash algorithm
---------------------------------------------------------------------------
Copyright (c) 2003, Dominik Reichl <dominik.reichl@t-online.de>, Germany.
All rights reserved.

Distributed under the terms of the GNU General Public License v2.

This software is provided 'as is' with no explicit or implied warranties
in respect of its properties, including, but not limited to, correctness
and/or fitness for purpose.
---------------------------------------------------------------------------
*/]]
--// FCS-16 algorithm implemented as described in RFC 1331
local FCSINIT16 = 65535
--// Fast 16 bit FCS lookup table
local fcs16tab = { [0]=0, 4489, 8978, 12955, 17956, 22445, 25910, 29887,
	35912, 40385, 44890, 48851, 51820, 56293, 59774, 63735,
	4225, 264, 13203, 8730, 22181, 18220, 30135, 25662,
	40137, 36160, 49115, 44626, 56045, 52068, 63999, 59510,
	8450, 12427, 528, 5017, 26406, 30383, 17460, 21949,
	44362, 48323, 36440, 40913, 60270, 64231, 51324, 55797,
	12675, 8202, 4753, 792, 30631, 26158, 21685, 17724,
	48587, 44098, 40665, 36688, 64495, 60006, 55549, 51572,
	16900, 21389, 24854, 28831, 1056, 5545, 10034, 14011,
	52812, 57285, 60766, 64727, 34920, 39393, 43898, 47859,
	21125, 17164, 29079, 24606, 5281, 1320, 14259, 9786,
	57037, 53060, 64991, 60502, 39145, 35168, 48123, 43634,
	25350, 29327, 16404, 20893, 9506, 13483, 1584, 6073,
	61262, 65223, 52316, 56789, 43370, 47331, 35448, 39921,
	29575, 25102, 20629, 16668, 13731, 9258, 5809, 1848,
	65487, 60998, 56541, 52564, 47595, 43106, 39673, 35696,
	33800, 38273, 42778, 46739, 49708, 54181, 57662, 61623,
	2112, 6601, 11090, 15067, 20068, 24557, 28022, 31999,
	38025, 34048, 47003, 42514, 53933, 49956, 61887, 57398,
	6337, 2376, 15315, 10842, 24293, 20332, 32247, 27774,
	42250, 46211, 34328, 38801, 58158, 62119, 49212, 53685,
	10562, 14539, 2640, 7129, 28518, 32495, 19572, 24061,
	46475, 41986, 38553, 34576, 62383, 57894, 53437, 49460,
	14787, 10314, 6865, 2904, 32743, 28270, 23797, 19836,
	50700, 55173, 58654, 62615, 32808, 37281, 41786, 45747,
	19012, 23501, 26966, 30943, 3168, 7657, 12146, 16123,
	54925, 50948, 62879, 58390, 37033, 33056, 46011, 41522,
	23237, 19276, 31191, 26718, 7393, 3432, 16371, 11898,
	59150, 63111, 50204, 54677, 41258, 45219, 33336, 37809,
	27462, 31439, 18516, 23005, 11618, 15595, 3696, 8185,
	63375, 58886, 54429, 50452, 45483, 40994, 37561, 33584,
	31687, 27214, 22741, 18780, 15843, 11370, 7921, 3960 }

---	Initializes FCS-16 checksum/hash.
--	@function		LibCompress:fcs16init
--	@return			{number} Value for an unsigned 16 bit integer that has all bits set
function LibCompress:fcs16init()
	return FCSINIT16
end

---	Updates FCS-16 checksum/hash.
--	@function		LibCompress:fcs16update
--	@param			{number} uFcs16 FCS-16 checksum/hash
--	@param			{string} pBuffer Data input 
--	@return			{number} Resultant checksum/hash
function LibCompress:fcs16update(uFcs16, pBuffer)
	local length = string_len(pBuffer)
	for i = 1, length do
		uFcs16 = bit_bxor(bit_rshift(uFcs16,8), fcs16tab[bit_band(bit_bxor(uFcs16, string_byte(pBuffer, i)), 255)])
	end
	return uFcs16
end

---	Performs the final bitwise XOR operation for FCS-16 checksum/hash.
--	@function		LibCompress:fcs16final
--	@param			{number} uFcs16 FCS-16 checksum/hash
--	@return			{number} Resultant checksum/hash
function LibCompress:fcs16final(uFcs16)
	return bit_bxor(uFcs16,65535)
end

---	Creates a FCS-16 checksum/hash based on input data.
--	@function		LibCompress:FCS16
--	@param			{string} ... Input data
--	@return			{number} Resultant checksum/hash
function LibCompress:FCS16(...)
	local hash = LibCompress:fcs16init()
	for _, data in ipairs{...} do
		hash = LibCompress:fcs16update(hash, data)
	end
	return LibCompress:fcs16final(hash)
end
-- END OF FCS16

--[[/*
---------------------------------------------------------------------------
Copyright (c) 2003, Dominik Reichl <dominik.reichl@t-online.de>, Germany.
All rights reserved.

Distributed under the terms of the GNU General Public License v2.

This software is provided 'as is' with no explicit or implied warranties
in respect of its properties, including, but not limited to, correctness
and/or fitness for purpose.
---------------------------------------------------------------------------
*/]]

--// FCS-32 algorithm implemented as described in RFC 1331

local FCSINIT32 = -1

--// Fast 32 bit FCS lookup table
local fcs32tab = { [0] = 0, 1996959894, -301047508, -1727442502, 124634137, 1886057615, -379345611, -1637575261,
	249268274, 2044508324, -522852066, -1747789432, 162941995, 2125561021, -407360249, -1866523247,
	498536548, 1789927666, -205950648, -2067906082, 450548861, 1843258603, -187386543, -2083289657,
	325883990, 1684777152, -43845254, -1973040660, 335633487, 1661365465, -99664541, -1928851979,
	997073096, 1281953886, -715111964, -1570279054, 1006888145, 1258607687, -770865667, -1526024853,
	901097722, 1119000684, -608450090, -1396901568, 853044451, 1172266101, -589951537, -1412350631,
	651767980, 1373503546, -925412992, -1076862698, 565507253, 1454621731, -809855591, -1195530993,
	671266974, 1594198024, -972236366, -1324619484, 795835527, 1483230225, -1050600021, -1234817731,
	1994146192, 31158534, -1731059524, -271249366, 1907459465, 112637215, -1614814043, -390540237,
	2013776290, 251722036, -1777751922, -519137256, 2137656763, 141376813, -1855689577, -429695999,
	1802195444, 476864866, -2056965928, -228458418, 1812370925, 453092731, -2113342271, -183516073,
	1706088902, 314042704, -1950435094, -54949764, 1658658271, 366619977, -1932296973, -69972891,
	1303535960, 984961486, -1547960204, -725929758, 1256170817, 1037604311, -1529756563, -740887301,
	1131014506, 879679996, -1385723834, -631195440, 1141124467, 855842277, -1442165665, -586318647,
	1342533948, 654459306, -1106571248, -921952122, 1466479909, 544179635, -1184443383, -832445281,
	1591671054, 702138776, -1328506846, -942167884, 1504918807, 783551873, -1212326853, -1061524307,
	-306674912, -1698712650, 62317068, 1957810842, -355121351, -1647151185, 81470997, 1943803523,
	-480048366, -1805370492, 225274430, 2053790376, -468791541, -1828061283, 167816743, 2097651377,
	-267414716, -2029476910, 503444072, 1762050814, -144550051, -2140837941, 426522225, 1852507879,
	-19653770, -1982649376, 282753626, 1742555852, -105259153, -1900089351, 397917763, 1622183637,
	-690576408, -1580100738, 953729732, 1340076626, -776247311, -1497606297, 1068828381, 1219638859,
	-670225446, -1358292148, 906185462, 1090812512, -547295293, -1469587627, 829329135, 1181335161,
	-882789492, -1134132454, 628085408, 1382605366, -871598187, -1156888829, 570562233, 1426400815,
	-977650754, -1296233688, 733239954, 1555261956, -1026031705, -1244606671, 752459403, 1541320221,
	-1687895376, -328994266, 1969922972, 40735498, -1677130071, -351390145, 1913087877, 83908371,
	-1782625662, -491226604, 2075208622, 213261112, -1831694693, -438977011, 2094854071, 198958881,
	-2032938284, -237706686, 1759359992, 534414190, -2118248755, -155638181, 1873836001, 414664567,
	-2012718362, -15766928, 1711684554, 285281116, -1889165569, -127750551, 1634467795, 376229701,
	-1609899400, -686959890, 1308918612, 956543938, -1486412191, -799009033, 1231636301, 1047427035,
	-1362007478, -640263460, 1088359270, 936918000, -1447252397, -558129467, 1202900863, 817233897,
	-1111625188, -893730166, 1404277552, 615818150, -1160759803, -841546093, 1423857449, 601450431,
	-1285129682, -1000256840, 1567103746, 711928724, -1274298825, -1022587231, 1510334235, 755167117 }

---	Initializes FCS-32 checksum/hash.
--	@function		LibCompress:fcs16init
--	@return			{number} Value for a signed 32 bit integer that has all bits set
function LibCompress:fcs32init()
	return FCSINIT32
end

---	Updates FCS-32 checksum/hash.
--	@function		LibCompress:fcs32update
--	@param			{number} uFcs32 FCS-32 checksum/hash
--	@param			{string} pBuffer Data input
--	@return			{number} Resultant checksum/hash
function LibCompress:fcs32update(uFcs32, pBuffer)
	local length = string_len(pBuffer)
	for i = 1, length do
		uFcs32 = bit_bxor(bit_rshift(uFcs32, 8), fcs32tab[bit_band(bit_bxor(uFcs32, string_byte(pBuffer, i)), 255)])
	end
	return uFcs32
end

---	Performs the final bitwise NOT operation for FCS-32 checksum/hash.
--	@function		LibCompress:fcs32final
--	@param			{number} uFcs32 FCS-32 checksum/hash
--	@return			{number} Resultant checksum/hash
function LibCompress:fcs32final(uFcs32)
	return bit_bnot(uFcs32)
end

---	Creates a FCS-32 checksum/hash based on input data.
--	@function		LibCompress:FCS32
--	@param			{string} ... Input data
--	@return			{number} Resultant checksum/hash
function LibCompress:FCS32(...)
	local hash = LibCompress:fcs32init()
	for _, data in ipairs{...} do
		hash = LibCompress:fcs32update(hash, data)
	end
	return LibCompress:fcs32final(hash)
end

return LibCompress