Skip to content

Instantly share code, notes, and snippets.

@danielga
Created December 14, 2015 03:31
Show Gist options
  • Select an option

  • Save danielga/736b8e1d5a3389c725d0 to your computer and use it in GitHub Desktop.

Select an option

Save danielga/736b8e1d5a3389c725d0 to your computer and use it in GitHub Desktop.
A pure Lua module that provides the same functionality as the lpack C module.
--[[
luapack
A pure Lua module that provides the same functionality as the lpack
C module.
-----------------------------------------------------------------------
Copyright (c) 2015, Daniel Almeida
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
]]
local char, byte, format, find, sub = string.char, string.byte, string.format, string.find, string.sub
local type, error, assert, select, unpack = type, error, assert, select, unpack or table.unpack
local frexp, ldexp, floor = math.frexp, math.ldexp, math.floor
local big_endian = false -- just a guess, find a way to do it properly
local format_number = 0
local function swap2(a, b)
if not big_endian then
return b, a
end
return a, b
end
local function swap4(a, b, c, d)
if not big_endian then
return d, c, b, a
end
return a, b, c, d
end
local function swap8(a, b, c, d, e, f, g, h)
if not big_endian then
return h, g, f, e, d, c, b, a
end
return a, b, c, d, e, f, g, h
end
local unpackers = {}
local function unpack_uchar(data, offset)
assert(offset <= #data, "not enough data to unpack uint8")
return byte(data, offset), offset + 1
end
local function unpack_ushort(data, offset)
assert(offset + 1 <= #data, "not enough data to unpack uint16")
local b1, b2 = swap2(byte(data, offset, offset + 1))
return b1 * 0x100 + b2, offset + 2
end
local function unpack_uinteger(data, offset)
assert(offset + 3 <= #data, "not enough data to unpack uint32")
local b1, b2, b3, b4 = swap4(byte(data, offset, offset + 3))
return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4, offset + 4
end
local function unpack_ulong(data, offset)
assert(offset + 7 <= #data, "not enough data to unpack uint64")
local b1, b2, b3, b4, b5, b6, b7, b8 = swap8(byte(data, offset, offset + 7))
return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8, offset + 8
end
-- zero-terminated string
unpackers[byte("z")] = function(data, offset)
local pos = find(data, "\0", offset)
if pos == nil then
pos = #data
end
return sub(data, offset, pos - 1), pos + 1
end
-- string preceded by length byte
unpackers[byte("p")] = function(data, offset)
assert(offset <= #data, "not enough data to unpack uint8 length preceded string")
local len = 0
len, offset = unpack_uchar(data, offset)
if len == 0 then
return "", offset
end
local pos = offset + len
assert(pos <= #data, "not enough data to unpack uint8 length preceded string")
return sub(data, offset, pos), pos + 1
end
-- string preceded by length word
unpackers[byte("P")] = function(data, offset)
assert(offset + 1 <= #data, "not enough data to unpack uint16 length preceded string")
local len = 0
len, offset = unpack_ushort(data, offset)
if len == 0 then
return "", offset
end
local pos = offset + len
assert(pos <= #data, "not enough data to unpack uint16 length preceded string")
return sub(data, offset, pos), pos + 1
end
-- string preceded by length size_t
unpackers[byte("a")] = function(data, offset)
assert(offset + 3 <= #data, "not enough data to unpack size_t length preceded string")
local len = 0
len, offset = unpack_uinteger(data, offset)
if len == 0 then
return "", offset
end
local pos = offset + len
assert(pos <= #data, "not enough data to unpack size_t length preceded string")
return sub(data, offset, pos), pos + 1
end
-- string
unpackers[byte("A")] = function(data, offset)
if format_number == 0 then
return "", offset
end
local pos = offset + format_number - 1
format_number = 0
return sub(data, offset, pos), pos + 1
end
-- float
unpackers[byte("f")] = function(data, offset)
assert(offset + 3 <= #data, "not enough data to unpack float")
local b1, b2, b3, b4 = swap4(byte(data, offset, offset + 3))
local sign = b1 > 0x7F
local expo = (b1 % 0x80) * 0x2 + floor(b2 / 0x80)
local mant = ((b2 % 0x80) * 0x100 + b3) * 0x100 + b4
if sign then
sign = -1
else
sign = 1
end
local n
if mant == 0 and expo == 0 then
n = sign * 0
elseif expo == 0xFF then
if mant == 0 then
n = sign * huge
else
n = 0 / 0
end
else
n = sign * ldexp(1 + mant / 0x800000, expo - 0x7F)
end
return n, offset + 4
end
-- double
unpackers[byte("d")] = function(data, offset)
assert(offset + 7 <= #data, "not enough data to unpack double")
local b1, b2, b3, b4, b5, b6, b7, b8 = swap8(byte(data, offset, offset + 7))
local sign = b1 > 0x7F
local expo = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
local mant = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
if sign then
sign = -1
else
sign = 1
end
local n
if mant == 0 and expo == 0 then
n = sign * 0
elseif expo == 0x7FF then
if mant == 0 then
n = sign * huge
else
n = 0 / 0
end
else
n = sign * ldexp(1 + mant / 4503599627370496, expo - 0x3FF)
end
return n, offset + 8
end
-- lua_Number = double
unpackers[byte("n")] = unpackers[byte("d")]
-- char
unpackers[byte("c")] = function(data, offset)
assert(offset <= #data, "not enough data to unpack int8")
local b1 = byte(data, offset)
if b1 < 0x80 then
return b1, offset + 1
end
return b1 - 0x100, offset + 1
end
-- unsigned char
unpackers[byte("b")] = unpack_uchar
-- short
unpackers[byte("h")] = function(data, offset)
assert(offset + 1 <= #data, "not enough data to unpack int16")
local b1, b2 = swap2(byte(data, offset, offset + 1))
if b1 < 0x80 then
return b1 * 0x100 + b2, offset + 2
end
return ((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) - 1, offset + 2
end
-- unsigned short
unpackers[byte("H")] = unpack_ushort
--int
unpackers[byte("i")] = function(data, offset)
assert(offset + 3 <= #data, "not enough data to unpack int32")
local b1, b2, b3, b4 = swap4(byte(data, offset, offset + 3))
if b1 < 0x80 then
return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4, offset + 4
end
return ((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) - 1, offset + 4
end
-- unsigned int
unpackers[byte("I")] = unpack_uinteger
-- long
unpackers[byte("l")] = function(data, offset)
assert(offset + 7 <= #data, "not enough data to unpack int64")
local b1, b2, b3, b4, b5, b6, b7, b8 = swap8(byte(data, offset, offset + 7))
if b1 < 0x80 then
return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8, offset + 8
end
return ((((((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) * 0x100 + (b5 - 0xFF)) * 0x100 + (b6 - 0xFF)) * 0x100 + (b7 - 0xFF)) * 0x100 + (b8 - 0xFF)) - 1, offset + 8
end
-- unsigned long
unpackers[byte("L")] = unpack_ulong
-- little endian
unpackers[byte("<")] = function(data, offset)
big_endian = false
return true, offset
end
-- big endian
unpackers[byte(">")] = function(data, offset)
big_endian = true
return true, offset
end
-- native endian
unpackers[byte("=")] = function(data, offset)
big_endian = false -- just a guess, find a way to do it properly
return true, offset
end
local packers = {}
local function pack_uchar(data)
assert(type(data) == "number", "bad argument type, expected number")
return char(data % 0x100)
end
local function pack_ushort(data)
assert(type(data) == "number", "bad argument type, expected number")
return char(swap2(floor(data / 0x100) % 0x100, data % 0x100))
end
local function pack_uinteger(data)
assert(type(data) == "number", "bad argument type, expected number")
return char(swap4(
floor(data / 0x1000000) % 0x100,
floor(data / 0x10000) % 0x100,
floor(data / 0x100) % 0x100,
data % 0x100
))
end
-- zero-terminated string
packers[byte("z")] = function(data)
assert(type(data) == "string", "bad argument type, expected string")
return data .. "\0"
end
-- string preceded by length byte
packers[byte("p")] = function(data)
assert(type(data) == "string", "bad argument type, expected string")
local datalen = #data
assert(datalen <= 255, "string length is bigger than 255 (doesn't fit in 1 byte)")
return pack_uchar(datalen) .. data
end
-- string preceded by length word
packers[byte("P")] = function(data)
assert(type(data) == "string", "bad argument type, expected string")
local datalen = #data
assert(datalen <= 65535, "string length is bigger than 65535 (doesn't fit in 2 bytes)")
return pack_ushort(datalen) .. data
end
-- string preceded by length size_t
packers[byte("a")] = function(data)
assert(type(data) == "string", "bad argument type, expected string")
local datalen = #data
assert(datalen <= 4294967295, "string length is bigger than 4294967295 (doesn't fit in 4 bytes)")
return pack_uinteger(datalen) .. data
end
-- string
packers[byte("A")] = function(data)
assert(type(data) == "string", "bad argument type, expected string")
return data
end
-- float
packers[byte("f")] = function(data)
assert(type(data) == "number", "bad argument type, expected number")
local sign = 0
if data < 0 then
sign = 0x80
data = -data
end
local mant, expo = frexp(data)
local b1, b2, b3, b4
if mant ~= mant then
-- nan
b1, b2, b3, b4 = swap4(0xFF, 0x88, 0x00, 0x00)
elseif mant == huge or expo > 0x80 then
if sign == 0 then
-- inf
b1, b2, b3, b4 = swap4(0x7F, 0x80, 0x00, 0x00)
else
-- -inf
b1, b2, b3, b4 = swap4(0xFF, 0x80, 0x00, 0x00)
end
elseif (mant == 0 and expo == 0) or expo < -0x7E then
-- zero
b1, b2, b3, b4 = swap4(sign, 0x00, 0x00, 0x000)
else
expo = expo + 0x7E
mant = (mant * 2 - 1) * ldexp(0.5, 24)
b1, b2, b3, b4 = swap4(
sign + floor(expo / 0x2),
(expo % 0x2) * 0x80 + floor(mant / 0x10000),
floor(mant / 0x100) % 0x100,
mant % 0x100
)
end
return char(b1, b2, b3, b4)
end
-- double
packers[byte("d")] = function(data)
assert(type(data) == "number", "bad argument type, expected number")
local sign = 0
if data < 0 then
sign = 0x80
data = -data
end
local mant, expo = frexp(data)
local b1, b2, b3, b4, b5, b6, b7, b8
if mant ~= mant then
-- nan
b1, b2, b3, b4, b5, b6, b7, b8 = swap8(0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
elseif mant == huge then
if sign == 0 then
-- inf
b1, b2, b3, b4, b5, b6, b7, b8 = swap8(0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
else
-- -inf
b1, b2, b3, b4, b5, b6, b7, b8 = swap8(0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
end
elseif mant == 0 and expo == 0 then
-- zero
b1, b2, b3, b4, b5, b6, b7, b8 = swap8(sign, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
else
expo = expo + 0x3FE
mant = (mant * 2 - 1) * ldexp(0.5, 53)
b1, b2, b3, b4, b5, b6, b7, b8 = swap8(
sign + floor(expo / 0x10),
(expo % 0x10) * 0x10 + floor(mant / 0x1000000000000),
floor(mant / 0x10000000000) % 0x100,
floor(mant / 0x100000000) % 0x100,
floor(mant / 0x1000000) % 0x100,
floor(mant / 0x10000) % 0x100,
floor(mant / 0x100) % 0x100,
mant % 0x100
)
end
return char(b1, b2, b3, b4, b5, b6, b7, b8)
end
-- lua_Number = double
packers[byte("n")] = packers[byte("d")]
-- char
packers[byte("c")] = function(data)
assert(type(data) == "number", "bad argument type, expected number")
return char((0x100 + data) % 0x100)
end
-- unsigned char
packers[byte("b")] = pack_uchar
-- short
packers[byte("h")] = function(data)
assert(type(data) == "number", "bad argument type, expected number")
data = 0x10000 + data
return char(swap2(floor(data / 0x100) % 0x100, data % 0x100))
end
-- unsigned short
packers[byte("H")] = pack_ushort
-- int
packers[byte("i")] = function(data)
assert(type(data) == "number", "bad argument type, expected number")
data = 0x100000000 + data
return char(swap4(
floor(data / 0x1000000) % 0x100,
floor(data / 0x10000) % 0x100,
floor(data / 0x100) % 0x100,
data % 0x100
))
end
-- unsigned int
packers[byte("I")] = pack_uinteger
-- long
packers[byte("l")] = function(data)
assert(type(data) == "number", "bad argument type, expected number")
data = 0x10000000000000000 + data
return char(swap8(
floor(data / 0x100000000000000) % 0x100,
floor(data / 0x1000000000000) % 0x100,
floor(data / 0x10000000000) % 0x100,
floor(data / 0x100000000) % 0x100,
floor(data / 0x1000000) % 0x100,
floor(data / 0x10000) % 0x100,
floor(data / 0x100) % 0x100,
data % 0x100
))
end
-- unsigned long
packers[byte("L")] = function(data)
assert(type(data) == "number", "bad argument type, expected number")
return char(swap8(
floor(data / 0x100000000000000) % 0x100,
floor(data / 0x1000000000000) % 0x100,
floor(data / 0x10000000000) % 0x100,
floor(data / 0x100000000) % 0x100,
floor(data / 0x1000000) % 0x100,
floor(data / 0x10000) % 0x100,
floor(data / 0x100) % 0x100,
data % 0x100
))
end
-- little endian
packers[byte("<")] = function(data)
big_endian = false
return true
end
-- big endian
packers[byte(">")] = function(data)
big_endian = true
return true
end
-- native endian
packers[byte("=")] = function(data)
big_endian = false -- just a guess, find a way to do it properly
return true
end
function string.unpack(data, fmt, offset)
big_endian = false -- just a guess, find a way to do it properly
format_number = 0
offset = offset or 1
local tunpacked = {}
local nunpacked = 1
local c
for i = 1, #fmt + 1 do
local nc = byte(fmt, i)
if nc == nil or unpackers[nc] ~= nil then
if c ~= nil then
format_number = format_number <= 0 and 1 or format_number
while format_number > 0 do
local unpacked, extra = unpackers[c](data, offset)
if unpacked ~= true then
tunpacked[nunpacked] = unpacked
nunpacked = nunpacked + 1
offset = extra
end
format_number = format_number - 1
end
end
format_number = 0
c = nc
elseif nc >= 48 and nc <= 57 then -- 0 to 9
format_number = format_number * 10 + nc - 48
else
error(format("bad code '%c'", nc))
end
end
return offset, unpack(tunpacked)
end
function string.pack(fmt, ...)
big_endian = false -- just a guess, find a way to do it properly
format_number = 0
local pack = ""
local index = 1
local c
for i = 1, #fmt + 1 do
local nc = byte(fmt, i)
if nc == nil or packers[nc] ~= nil then
if c ~= nil then
format_number = format_number <= 0 and 1 or format_number
while format_number > 0 do
local data = select(index, ...)
local packed = packers[c](data)
if packed ~= true then
pack = pack .. packed
index = index + 1
end
format_number = format_number - 1
end
end
format_number = 0
c = nc
elseif nc >= 48 and nc <= 57 then -- 0 to 9
format_number = format_number * 10 + nc - 48
else
error(format("bad code '%c'", nc))
end
end
return pack
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment