local table = require("table") local string = require("string") local luabit = require("bit") local tostr = string.char local double_decode_count = 0 local double_encode_count = 0 -- cache bitops local band, rshift = luabit.band, luabit.brshift if not rshift then -- luajit differ from luabit rshift = luabit.rshift end local function byte_mod(x, v) if x < 0 then x = x + 256 end return (x % v) end -- buffer local strbuf = "" -- for unpacking local strary = {} -- for packing local function strary_append_int16(n, h) if n < 0 then n = n + 65536 end table.insert(strary, tostr(h, math.floor(n / 256), n % 256)) end local function strary_append_int32(n, h) if n < 0 then n = n + 4294967296 end table.insert( strary, tostr(h, math.floor(n / 16777216), math.floor(n / 65536) % 256, math.floor(n / 256) % 256, n % 256) ) end local doubleto8bytes local strary_append_double = function(n) -- assume double double_encode_count = double_encode_count + 1 local b = doubleto8bytes(n) table.insert(strary, tostr(0xcb)) table.insert(strary, string.reverse(b)) -- reverse: make big endian double precision end --- IEEE 754 -- out little endian doubleto8bytes = function(x) local function grab_byte(v) return math.floor(v / 256), tostr(math.fmod(math.floor(v), 256)) end local sign = 0 if x < 0 then sign = 1 x = -x end local mantissa, exponent = math.frexp(x) if x == 0 then -- zero mantissa, exponent = 0, 0 elseif x == 1 / 0 then mantissa, exponent = 0, 2047 else mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53) exponent = exponent + 1022 end local v, byte = "" -- convert to bytes x = mantissa for _ = 1, 6 do _, byte = grab_byte(x) v = v .. byte -- 47:0 end x, byte = grab_byte(exponent * 16 + x) v = v .. byte -- 55:48 x, byte = grab_byte(sign * 128 + x) v = v .. byte -- 63:56 return v, x end local function bitstofrac(ary) local x = 0 local cur = 0.5 for _, v in ipairs(ary) do x = x + cur * v cur = cur / 2 end return x end local function bytestobits(ary) local out = {} for _, v in ipairs(ary) do for j = 0, 7, 1 do table.insert(out, band(rshift(v, 7 - j), 1)) end end return out end -- get little endian local function bytestodouble(v) -- sign:1bit -- exp: 11bit (2048, bias=1023) local sign = math.floor(v:byte(8) / 128) local exp = band(v:byte(8), 127) * 16 + rshift(v:byte(7), 4) - 1023 -- bias -- frac: 52 bit local fracbytes = { band(v:byte(7), 15), v:byte(6), v:byte(5), v:byte(4), v:byte(3), v:byte(2), v:byte(1), -- big endian } local bits = bytestobits(fracbytes) for _ = 1, 4 do table.remove(bits, 1) end if sign == 1 then sign = -1 else sign = 1 end local frac = bitstofrac(bits) if exp == -1023 and frac == 0 then return 0 end if exp == 1024 and frac == 0 then return 1 / 0 * sign end local real = math.ldexp(1 + frac, exp) return real * sign end --- packers local packers = {} packers.dynamic = function(data) local t = type(data) return packers[t](data) end packers["nil"] = function() table.insert(strary, tostr(0xc0)) end packers.boolean = function(data) if data then -- pack true table.insert(strary, tostr(0xc3)) else -- pack false table.insert(strary, tostr(0xc2)) end end packers.number = function(n) if math.floor(n) == n then -- integer if n >= 0 then -- positive integer if n < 128 then -- positive fixnum table.insert(strary, tostr(n)) elseif n < 256 then -- uint8 table.insert(strary, tostr(0xcc, n)) elseif n < 65536 then -- uint16 strary_append_int16(n, 0xcd) elseif n < 4294967296 then -- uint32 strary_append_int32(n, 0xce) else -- lua cannot handle uint64, so double strary_append_double(n) end else -- negative integer if n >= -32 then -- negative fixnum table.insert(strary, tostr(0xe0 + ((n + 256) % 32))) elseif n >= -128 then -- int8 table.insert(strary, tostr(0xd0, byte_mod(n, 0x100))) elseif n >= -32768 then -- int16 strary_append_int16(n, 0xd1) elseif n >= -2147483648 then -- int32 strary_append_int32(n, 0xd2) else -- lua cannot handle int64, so double strary_append_double(n) end end else -- floating point strary_append_double(n) end end packers.string = function(data) local n = #data if n < 32 then table.insert(strary, tostr(0xa0 + n)) elseif n < 65536 then strary_append_int16(n, 0xda) elseif n < 4294967296 then strary_append_int32(n, 0xdb) else error("overflow") end table.insert(strary, data) end packers["function"] = function() error("unimplemented:function") end packers.userdata = function() error("unimplemented:userdata") end packers.thread = function() error("unimplemented:thread") end packers.table = function(data) local is_map, ndata, nmax = false, 0, 0 for k, _ in pairs(data) do if type(k) == "number" then if k > nmax then nmax = k end else is_map = true end ndata = ndata + 1 end if is_map then -- pack as map if ndata < 16 then table.insert(strary, tostr(0x80 + ndata)) elseif ndata < 65536 then strary_append_int16(ndata, 0xde) elseif ndata < 4294967296 then strary_append_int32(ndata, 0xdf) else error("overflow") end for k, v in pairs(data) do packers[type(k)](k) packers[type(v)](v) end else -- pack as array if nmax < 16 then table.insert(strary, tostr(0x90 + nmax)) elseif nmax < 65536 then strary_append_int16(nmax, 0xdc) elseif nmax < 4294967296 then strary_append_int32(nmax, 0xdd) else error("overflow") end for i = 1, nmax do packers[type(data[i])](data[i]) end end end -- types decoding local types_map = { [0xc0] = "nil", [0xc2] = "false", [0xc3] = "true", [0xca] = "float", [0xcb] = "double", [0xcc] = "uint8", [0xcd] = "uint16", [0xce] = "uint32", [0xcf] = "uint64", [0xd0] = "int8", [0xd1] = "int16", [0xd2] = "int32", [0xd3] = "int64", [0xda] = "raw16", [0xdb] = "raw32", [0xdc] = "array16", [0xdd] = "array32", [0xde] = "map16", [0xdf] = "map32", } local type_for = function(n) if types_map[n] then return types_map[n] elseif n < 0xc0 then if n < 0x80 then return "fixnum_posi" elseif n < 0x90 then return "fixmap" elseif n < 0xa0 then return "fixarray" else return "fixraw" end elseif n > 0xdf then return "fixnum_neg" else return "undefined" end end local types_len_map = { uint16 = 2, uint32 = 4, uint64 = 8, int16 = 2, int32 = 4, int64 = 8, float = 4, double = 8, } --- unpackers local unpackers = {} local unpack_number = function(offset, ntype, nlen) local b1, b2, b3, b4, b5, b6, b7, b8 if nlen >= 2 then b1, b2 = string.byte(strbuf, offset + 1, offset + 2) end if nlen >= 4 then b3, b4 = string.byte(strbuf, offset + 3, offset + 4) end if nlen >= 8 then b5, b6, b7, b8 = string.byte(strbuf, offset + 5, offset + 8) end if ntype == "uint16_t" then return b1 * 256 + b2 elseif ntype == "uint32_t" then return b1 * 65536 * 256 + b2 * 65536 + b3 * 256 + b4 elseif ntype == "int16_t" then local n = b1 * 256 + b2 local nn = (65536 - n) * -1 if nn == -65536 then nn = 0 end return nn elseif ntype == "int32_t" then local n = b1 * 65536 * 256 + b2 * 65536 + b3 * 256 + b4 local nn = (4294967296 - n) * -1 if nn == -4294967296 then nn = 0 end return nn elseif ntype == "double_t" then local s = tostr(b8, b7, b6, b5, b4, b3, b2, b1) double_decode_count = double_decode_count + 1 local n = bytestodouble(s) return n else error("unpack_number: not impl:" .. ntype) end end local function unpacker_number(offset) local obj_type = type_for(string.byte(strbuf, offset + 1, offset + 1)) local nlen = types_len_map[obj_type] local ntype if obj_type == "float" then error("float is not implemented") else ntype = obj_type .. "_t" end return offset + nlen + 1, unpack_number(offset + 1, ntype, nlen) end local function unpack_map(offset, n) local r = {} local k, v for _ = 1, n do offset, k = unpackers.dynamic(offset) assert(offset) offset, v = unpackers.dynamic(offset) assert(offset) r[k] = v end return offset, r end local function unpack_array(offset, n) local r = {} for i = 1, n do offset, r[i] = unpackers.dynamic(offset) assert(offset) end return offset, r end function unpackers.dynamic(offset) if offset >= #strbuf then error("need more data") end local obj_type = type_for(string.byte(strbuf, offset + 1, offset + 1)) return unpackers[obj_type](offset) end function unpackers.undefined() error("unimplemented:undefined") end unpackers["nil"] = function(offset) return offset + 1, nil end unpackers["false"] = function(offset) return offset + 1, false end unpackers["true"] = function(offset) return offset + 1, true end unpackers.fixnum_posi = function(offset) return offset + 1, string.byte(strbuf, offset + 1, offset + 1) end unpackers.uint8 = function(offset) return offset + 2, string.byte(strbuf, offset + 2, offset + 2) end unpackers.uint16 = unpacker_number unpackers.uint32 = unpacker_number unpackers.uint64 = unpacker_number unpackers.fixnum_neg = function(offset) -- alternative to cast below: local n = string.byte(strbuf, offset + 1, offset + 1) local nn = (256 - n) * -1 return offset + 1, nn end unpackers.int8 = function(offset) local i = string.byte(strbuf, offset + 2, offset + 2) if i > 127 then i = (256 - i) * -1 end return offset + 2, i end unpackers.int16 = unpacker_number unpackers.int32 = unpacker_number unpackers.int64 = unpacker_number unpackers.float = unpacker_number unpackers.double = unpacker_number unpackers.fixraw = function(offset) local n = byte_mod(string.byte(strbuf, offset + 1, offset + 1), 0x1f + 1) -- print("unpackers.fixraw: offset:", offset, "#buf:", #buf, "n:",n ) local b if (#strbuf - 1 - offset) < n then error("require more data") end if n > 0 then b = string.sub(strbuf, offset + 1 + 1, offset + 1 + 1 + n - 1) else b = "" end return offset + n + 1, b end unpackers.raw16 = function(offset) local n = unpack_number(offset + 1, "uint16_t", 2) if (#strbuf - 1 - 2 - offset) < n then error("require more data") end local b = string.sub(strbuf, offset + 1 + 1 + 2, offset + 1 + 1 + 2 + n - 1) return offset + n + 3, b end unpackers.raw32 = function(offset) local n = unpack_number(offset + 1, "uint32_t", 4) if (#strbuf - 1 - 4 - offset) < n then error("require more data (possibly bug)") end local b = string.sub(strbuf, offset + 1 + 1 + 4, offset + 1 + 1 + 4 + n - 1) return offset + n + 5, b end unpackers.fixarray = function(offset) return unpack_array(offset + 1, byte_mod(string.byte(strbuf, offset + 1, offset + 1), 0x0f + 1)) end unpackers.array16 = function(offset) return unpack_array(offset + 3, unpack_number(offset + 1, "uint16_t", 2)) end unpackers.array32 = function(offset) return unpack_array(offset + 5, unpack_number(offset + 1, "uint32_t", 4)) end unpackers.fixmap = function(offset) return unpack_map(offset + 1, byte_mod(string.byte(strbuf, offset + 1, offset + 1), 0x0f + 1)) end unpackers.map16 = function(offset) return unpack_map(offset + 3, unpack_number(offset + 1, "uint16_t", 2)) end unpackers.map32 = function(offset) return unpack_map(offset + 5, unpack_number(offset + 1, "uint32_t", 4)) end -- Main functions local ljp_pack = function(data) strary = {} packers.dynamic(data) local s = table.concat(strary, "") return s end local ljp_unpack = function(s, offset) if offset == nil then offset = 0 end if type(s) ~= "string" then return false, "invalid argument" end local data strbuf = s offset, data = unpackers.dynamic(offset) return offset, data end local function ljp_stat() return { double_decode_count = double_decode_count, double_encode_count = double_encode_count, } end local msgpack = { pack = ljp_pack, unpack = ljp_unpack, stat = ljp_stat, } return msgpack