---
-- Pack and unpack binary data.
--
-- THIS LIBRARY IS DEPRECATED! Please use builtin Lua 5.3 string.pack facilities.
--
-- A problem script authors often face is the necessity of encoding values
-- into binary data. For example after analyzing a protocol the starting
-- point to write a script could be a hex dump, which serves as a preamble
-- to every sent packet. Although it is possible to work with the
-- functionality Lua provides, it's not very convenient. Therefore NSE includes
-- Binlib, based on lpack (http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/)
-- by Luiz Henrique de Figueiredo.
--
-- The Binlib functions take a format string to encode and decode binary
-- data. Packing and unpacking are controlled by the following operator
-- characters:
-- * H hex string
-- * B bit string
-- * x null byte
-- * z zero-terminated string
-- * p string preceded by 1-byte integer length
-- * P string preceded by 2-byte integer length
-- * a string preceded by 4-byte integer length
-- * A string
-- * f float
-- * d double
-- * n Lua number
-- * c char (1-byte integer)
-- * C byte = unsigned char (1-byte unsigned integer)
-- * s short (2-byte integer)
-- * S unsigned short (2-byte unsigned integer)
-- * i int (4-byte integer)
-- * I unsigned int (4-byte unsigned integer)
-- * l long (8-byte integer)
-- * L unsigned long (8-byte unsigned integer)
-- * < little endian modifier
-- * > big endian modifier
-- * = native endian modifier
--
-- Note that the endian operators work as modifiers to all the
-- characters following them in the format string.
local assert = assert
local ipairs = ipairs
local tonumber = tonumber
local char = require "string".char
local insert = require "table".insert
local move = require "table".move
local pack = require "table".pack
local unpack = require "table".unpack
local _ENV = {}
--- Returns a binary packed string.
--
-- The format string describes how the parameters (p1,
-- ...) will be interpreted. Numerical values following operators
-- stand for operator repetitions and need an according amount of parameters.
-- Operators expect appropriate parameter types.
--
-- Note: on Windows packing of 64-bit values > 2^63 currently
-- results in packing exactly 2^63.
-- @param format Format string, used to pack following arguments.
-- @param ... The values to pack.
-- @return String containing packed data.
function _ENV.pack (format, ...)
format = "!1="..format -- 1 byte alignment
local endianness = "="
local i, args = 0, pack(...)
local function translate (o, n)
if o == "=" or o == "<" or o == ">" then
endianness = o
return o
end
i = i + 1
n = #n == 0 and 1 or tonumber(n)
if o == "H" then
-- hex string
-- N.B. n is the reptition
-- FIXME native not big?
assert(n > 0, "n cannot be 0") -- original bin library allowed this, it doesn't make sense
local new = ">" -- !! in original bin library, hex strings are always big endian...
for j = i, i+n-1 do
args[j] = args[j]:gsub("(%x%x?)", function (s) return char(tonumber(s, 16)) end)
new = new .. ("c%d"):format(#args[j])
end
new = new .. endianness -- restore old endianness
return new
elseif o == "B" then
-- bit string
-- N.B. n is the reptition
-- This needs to be removed, it doesn't even work:
-- print(bin.unpack(">B", bin.pack(">B", "0101"))) --> "01010000"
error "FIXME"
elseif o == "p" then
return ("s1"):rep(n)
elseif o == "P" then
return ("s2"):rep(n)
elseif o == "a" then
return ("s4"):rep(n)
elseif o == "A" then
-- an unterminated string
-- N.B. n is the reptition
assert(n > 0, "n cannot be 0") -- original bin library allowed this, it doesn't make sense
local new = ""
for j = i, i+n-1 do
new = new .. ("c%d"):format(#args[j])
end
return new
elseif o == "c" then
return ("b"):rep(n)
elseif o == "C" then
return ("B"):rep(n)
elseif o == "s" then
return ("i2"):rep(n)
elseif o == "S" then
return ("I2"):rep(n)
elseif o == "i" then
return ("i4"):rep(n)
elseif o == "I" then
return ("I4"):rep(n)
elseif o == "l" then
return ("i8"):rep(n)
elseif o == "L" then
return ("I8"):rep(n)
else
return o:rep(n)
end
end
format = format:gsub("([%a=<>])(%d*)", translate)
return format:pack(unpack(args))
end
do
-- !! endianness is always big endian for H !!
assert(_ENV.pack(">H", "415D615A") == "\x41\x5D\x61\x5A")
assert(_ENV.pack("init value for subsequent
-- calls. The following return values are the values according to the format
-- string. Numerical values in the format string are interpreted as repetitions
-- like in pack, except if used with A,
-- B, or H, in which cases the number tells
-- unpack how many bytes to read. unpack stops if
-- either the format string or the binary data string are exhausted.
-- @param format Format string, used to unpack values out of data string.
-- @param data String containing packed data.
-- @param init Optional starting position within the string.
-- @return Position in the data string where unpacking stopped.
-- @return All unpacked values.
function _ENV.unpack (format, data, init)
format = "!1="..format -- 1 byte alignment
local endianness = "="
local hex_fix = {}
local i = 0
local function translate (o, n)
i = i + 1
n = #n == 0 and 1 or tonumber(n)
if o == "=" then
endianness = "="
elseif o == "<" then
endianness = "<"
elseif o == ">" then
endianness = ">"
elseif o == "H" then
-- hex string
-- N.B. n is the number of bytes to read
insert(hex_fix, i)
return (">c%d%s"):format(n, endianness) -- !! in original bin library, hex strings are always big endian...
elseif o == "B" then
-- bit string
-- N.B. n is the number of bytes to read
-- This needs to be removed, it doesn't even work:
-- print(bin.unpack(">B", bin.pack(">B", "0101"))) --> "01010000"
error "FIXME"
elseif o == "p" then
return ("s1"):rep(n)
elseif o == "P" then
return ("s2"):rep(n)
elseif o == "a" then
return ("s4"):rep(n)
elseif o == "A" then
-- an unterminated string
-- N.B. n is the number of bytes to read
return ("c%d"):format(n)
elseif o == "c" then
return ("b"):rep(n)
elseif o == "C" then
return ("B"):rep(n)
elseif o == "s" then
return ("i2"):rep(n)
elseif o == "S" then
return ("I2"):rep(n)
elseif o == "i" then
return ("i4"):rep(n)
elseif o == "I" then
return ("I4"):rep(n)
elseif o == "l" then
return ("i8"):rep(n)
elseif o == "L" then
return ("I8"):rep(n)
else
return o:rep(n)
end
end
format = format:gsub("([%a=<>])(%d*)", translate)
return unpacker(hex_fix, format:unpack(data, init))
end
do
local i, v
-- !! endianness is always big endian for H !!
i, v = _ENV.unpack("H", "\x00\xff\x0f\xf0")
assert(i == 2 and v == "00")
i, v = _ENV.unpack("H0", "\x00\xff\x0f\xf0")
assert(i == 1 and v == "")
i, v = _ENV.unpack("H1", "\x00\xff\x0f\xf0")
assert(i == 2 and v == "00")
i, v = _ENV.unpack("H2", "\x00\xff\x0f\xf0")
assert(i == 3 and v == "00FF")
i, v = _ENV.unpack("H4", "\x00\xff\x0f\xf0")
assert(i == 5 and v == "00FF0FF0")
i, v = _ENV.unpack("A", "foo");
assert(i == 2 and v == "f")
i, v = _ENV.unpack("A0", "foo");
assert(i == 1 and v == "")
i, v = _ENV.unpack("A1", "foo");
assert(i == 2 and v == "f")
i, v = _ENV.unpack("A2", "foo");
assert(i == 3 and v == "fo")
i, v = _ENV.unpack("A3", "foo");
assert(i == 4 and v == "foo")
i, v = _ENV.unpack("A4", "foo");
assert(i == 1 and v == nil)
end
return _ENV