diff --git a/run_tests.sh b/run_tests.sh index 313010a..fd7554a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -49,7 +49,7 @@ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) lua_path="$script_dir/?.lua;$script_dir/?/init.lua;$script_dir/src/?.lua;$script_dir/src/?/init.lua;$script_dir/vendor/?.lua;$LUA_PATH" # Parse command line arguments to determine which modules to run -all_modules=("sha256" "sha512" "blake2" "chacha20" "chacha20_poly1305" "poly1305" "aes_gcm" "hkdf" "random" "bignum" "srp" "x25519" "x448" "ed25519" "openssl_wrapper") +all_modules=("sha256" "sha512" "blake2" "chacha20" "chacha20_poly1305" "poly1305" "aes_gcm" "hkdf" "random" "bignum" "srp" "x25519" "x448" "ed25519" "openssl_wrapper" "lpack") default_modules=("${all_modules[@]}") modules_to_run=("$@") @@ -147,6 +147,15 @@ run_selftest "X448" "x448" "crypto.x448" run_selftest "Ed25519" "ed25519" "crypto.ed25519" run_selftest "OpenSSL gating" "openssl_wrapper" "crypto.openssl_wrapper" +# Control4's LuaJIT has string.pack/unpack as lpack, a different dialect: every +# selftest again with that shape installed before bitn loads. +run_test "All selftests with lpack-shaped string.pack" "lpack" " + dofile('$script_dir/test/lpack_stub.lua') + if not require('crypto').selftest() then + os.exit(1) + end + " + passed_count=${#passed_modules[@]} failed_count=${#failed_modules[@]} total_count=$((passed_count + failed_count)) diff --git a/test/lpack_stub.lua b/test/lpack_stub.lua new file mode 100644 index 0000000..5adb9f8 --- /dev/null +++ b/test/lpack_stub.lua @@ -0,0 +1,83 @@ +--- @diagnostic disable: duplicate-set-field +-- Installs string.pack/string.unpack in the shape Control4's LuaJIT ships (lpack), +-- as measured on a controller: single-letter codes with no size suffix, '<' and +-- '=' little-endian, '>' big-endian, a NUL ends the format, a digit or unknown +-- code raises, and unpack(data, fmt, pos) returns the next position before the +-- values, with nil for a value the data cannot supply. Loaded ahead of bitn so a +-- byte helper that assumes the 5.3 dialect fails here rather than on a controller. +local SIZE = { b = 1, B = 1, c = 1, h = 2, H = 2, i = 4, I = 4, l = 4, L = 4 } + +local function pack_int(value, size, big_endian) + value = value % 2 ^ (8 * size) + local out = {} + for _ = 1, size do + out[#out + 1] = string.char(value % 256) + value = math.floor(value / 256) + end + if big_endian then + local reversed = {} + for i = size, 1, -1 do + reversed[#reversed + 1] = out[i] + end + out = reversed + end + return table.concat(out) +end + +local function unpack_int(data, pos, size, big_endian) + if #data < pos + size - 1 then + return pos, nil + end + local value = 0 + for i = 0, size - 1 do + local weight = big_endian and (size - 1 - i) or i + value = value + string.byte(data, pos + i) * 2 ^ (8 * weight) + end + return pos + size, value +end + +local function codes(fmt) + local out = {} + for c in fmt:gmatch(".") do + if c == "\0" then + break + end + out[#out + 1] = c + end + return out +end + +function string.pack(fmt, ...) + local args, i, out, big_endian = { ... }, 0, {}, false + for _, c in ipairs(codes(fmt)) do + if c == "<" or c == "=" then + big_endian = false + elseif c == ">" then + big_endian = true + elseif SIZE[c] then + i = i + 1 + out[#out + 1] = pack_int(args[i], SIZE[c], big_endian) + else + error("lpack stub: unsupported code '" .. c .. "'") + end + end + return table.concat(out) +end + +function string.unpack(data, fmt, pos) + pos = pos or 1 + local out, n, big_endian = {}, 0, false + for _, c in ipairs(codes(fmt)) do + if c == "<" or c == "=" then + big_endian = false + elseif c == ">" then + big_endian = true + elseif SIZE[c] then + n = n + 1 + pos, out[n] = unpack_int(data, pos, SIZE[c], big_endian) + else + error("lpack stub: unsupported code '" .. c .. "'") + end + end + return pos, (table.unpack or unpack)(out, 1, n) +end diff --git a/vendor/bitn.lua b/vendor/bitn.lua index de62301..d4e546b 100644 --- a/vendor/bitn.lua +++ b/vendor/bitn.lua @@ -32,6 +32,31 @@ _compat.to_unsigned = to_unsigned -- Constants local MASK32 = 0xFFFFFFFF +-- string.pack/unpack are bound only if they answer in the 5.3 dialect. Control4's +-- LuaJIT ships lpack under the same names: no size suffixes, and unpack takes +-- (data, fmt, pos) and returns the position first, so existence proves nothing. +local function probe_string_pack() + local pack, unpack = rawget(string, "pack"), rawget(string, "unpack") + if not (pack and unpack) then + return nil, nil + end + local packed_ok, packed = pcall(pack, "I4", 0x04030201) + if not packed_ok or packed ~= "\4\3\2\1" then + return nil, nil + end + local unpacked_ok, value, next_pos = pcall(unpack, "