-- Test for bd-awv / ra6 3l: 64-bit cdata fidelity.
--
-- int64/uint64/sint64/fixed64/sfixed64 must round-trip as LuaJIT
-- int64_t/uint64_t cdata through the C runtime — never narrowed to
-- a Lua double. Same convention as Tarantool's msgpackffi, net.box,
-- box.tuple, and the built-in protobuf. The C decode path pushes
-- cdata via luaL_pushint64 / luaL_pushuint64; the C encode path
-- accepts cdata via luaL_toint64 / luaL_touint64 alongside plain
-- Lua numbers.
--
-- Acceptance per bd-awv:
-- A value > 2^53 round-trips byte-equal to mode=full pure-Lua, and
-- the decoded value remains cdata. Covers all five 64-bit kinds in
-- both codegen modes. Only runs when PB_ENABLE_C=1 is set and the
-- C runtime module is loadable.
local t = require('luatest')
local ffi = require('ffi')
local pb = require('pb')
local c_runtime = pb.c_runtime
local function skip_if_no_c()
if c_runtime == nil then
t.skip('PB_ENABLE_C not set or pb.c_runtime not available')
end
end
-- Values past Lua's 2^53 double-precision boundary, picked so the
-- top bits would silently drop if any code path narrowed to double.
-- int64_max - 1, uint64_max - 1, and high bit patterns for fixed.
local U64_BIG = 0xDEADBEEFCAFEBABEULL
local U64_MAX_ISH = 0xFFFFFFFFFFFFFFFEULL -- one off all-ones
local I64_NEG_BIG = -0x0123456789ABCDEFLL -- past -2^53
local I64_POS_BIG = 0x0123456789ABCDEFLL -- past 2^53
local full_wide
for _, mode in ipairs({'full', 'runtime'}) do
local g = t.group('c_runtime_int64.' .. mode)
local wide
g.before_all(function()
skip_if_no_c()
wide = require(mode .. '.c_int64.c_int64_pb')
full_wide = require('full.c_int64.c_int64_pb')
end)
g.before_each(skip_if_no_c)
-- ---------- Acceptance per bd-awv ----------
function g.test_acceptance_all_kinds_round_trip_past_2pow53()
-- One message carries every 64-bit kind with a value that
-- would lose precision if narrowed through a Lua double.
-- Bytes must match mode=full, and every decoded field must
-- remain cdata.
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
local msg = {
a_int64 = ffi.new('int64_t', I64_POS_BIG),
a_uint64 = U64_BIG,
a_sint64 = ffi.new('int64_t', I64_NEG_BIG),
a_fixed64 = U64_MAX_ISH,
a_sfixed64 = ffi.new('int64_t', I64_NEG_BIG),
}
local c_bytes = c_runtime.encode(plan, msg)
local lua_bytes = full_wide.Wide_encode(msg)
t.assert_equals(c_bytes, lua_bytes, 'encode bytes match mode=full')
local c_decoded = c_runtime.decode(plan, c_bytes)
local lua_decoded = full_wide.Wide_decode(c_bytes)
t.assert_equals(c_decoded, lua_decoded, 'decoded table matches mode=full')
-- Every value must surface as cdata, not double.
for _, k in ipairs({'a_int64', 'a_uint64', 'a_sint64',
'a_fixed64', 'a_sfixed64'}) do
t.assert_equals(type(c_decoded[k]), 'cdata',
('field %s decoded as %s, expected cdata')
:format(k, type(c_decoded[k])))
end
-- Value comparison done via cdata equality so the top bits
-- are checked, not just the low 53.
t.assert_equals(c_decoded.a_int64, msg.a_int64)
t.assert_equals(c_decoded.a_uint64, msg.a_uint64)
t.assert_equals(c_decoded.a_sint64, msg.a_sint64)
t.assert_equals(c_decoded.a_fixed64, msg.a_fixed64)
t.assert_equals(c_decoded.a_sfixed64, msg.a_sfixed64)
end
-- ---------- Per-kind: encode from cdata + decode preserves cdata --
function g.test_int64_cdata_round_trip()
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
local v = ffi.new('int64_t', I64_POS_BIG)
local bytes = c_runtime.encode(plan, {a_int64 = v})
t.assert_equals(bytes, full_wide.Wide_encode({a_int64 = v}))
local d = c_runtime.decode(plan, bytes)
t.assert_equals(type(d.a_int64), 'cdata')
t.assert_equals(d.a_int64, v)
end
function g.test_int64_negative_cdata_round_trip()
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
-- Negative int64 is sign-extended to 10-byte varint on the wire.
local v = ffi.new('int64_t', I64_NEG_BIG)
local bytes = c_runtime.encode(plan, {a_int64 = v})
t.assert_equals(bytes, full_wide.Wide_encode({a_int64 = v}))
local d = c_runtime.decode(plan, bytes)
t.assert_equals(type(d.a_int64), 'cdata')
t.assert_equals(d.a_int64, v)
end
function g.test_uint64_cdata_round_trip()
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
local v = U64_BIG
local bytes = c_runtime.encode(plan, {a_uint64 = v})
t.assert_equals(bytes, full_wide.Wide_encode({a_uint64 = v}))
local d = c_runtime.decode(plan, bytes)
t.assert_equals(type(d.a_uint64), 'cdata')
t.assert_equals(d.a_uint64, v)
end
function g.test_uint64_near_max_cdata_round_trip()
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
local v = U64_MAX_ISH
local bytes = c_runtime.encode(plan, {a_uint64 = v})
t.assert_equals(bytes, full_wide.Wide_encode({a_uint64 = v}))
local d = c_runtime.decode(plan, bytes)
t.assert_equals(type(d.a_uint64), 'cdata')
t.assert_equals(d.a_uint64, v)
end
function g.test_sint64_cdata_round_trip()
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
local v = ffi.new('int64_t', I64_NEG_BIG)
local bytes = c_runtime.encode(plan, {a_sint64 = v})
t.assert_equals(bytes, full_wide.Wide_encode({a_sint64 = v}))
local d = c_runtime.decode(plan, bytes)
t.assert_equals(type(d.a_sint64), 'cdata')
t.assert_equals(d.a_sint64, v)
end
function g.test_fixed64_cdata_round_trip()
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
local v = U64_BIG
local bytes = c_runtime.encode(plan, {a_fixed64 = v})
t.assert_equals(bytes, full_wide.Wide_encode({a_fixed64 = v}))
-- fixed64 is always 8 bytes + 1 tag byte; spot-check the wire size.
t.assert_equals(#bytes, 9, 'fixed64 wire size = tag + 8B')
local d = c_runtime.decode(plan, bytes)
t.assert_equals(type(d.a_fixed64), 'cdata')
t.assert_equals(d.a_fixed64, v)
end
function g.test_sfixed64_cdata_round_trip()
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
local v = ffi.new('int64_t', I64_NEG_BIG)
local bytes = c_runtime.encode(plan, {a_sfixed64 = v})
t.assert_equals(bytes, full_wide.Wide_encode({a_sfixed64 = v}))
local d = c_runtime.decode(plan, bytes)
t.assert_equals(type(d.a_sfixed64), 'cdata')
t.assert_equals(d.a_sfixed64, v)
end
-- ---------- Encode also accepts plain Lua numbers (in-range) ------
function g.test_int64_from_lua_number_within_double_range()
-- Plain Lua numbers below 2^53 must encode identically to
-- the cdata path. This is the common case for IDs/counters
-- that happen to fit a double exactly. Tarantool's
-- luaL_pushint64 surfaces small values as Lua numbers (not
-- cdata) by convention — same as msgpackffi, net.box, the
-- built-in protobuf — so the decoded type tracks that, and
-- equality compares numerically across number/cdata.
local plan = c_runtime.compile_plan(wide.Wide_descriptor)
local msg_num = {a_int64 = 42}
local msg_cdata = {a_int64 = ffi.new('int64_t', 42)}
local b_num = c_runtime.encode(plan, msg_num)
local b_cd = c_runtime.encode(plan, msg_cdata)
t.assert_equals(b_num, b_cd, 'Lua number encodes same as cdata')
t.assert_equals(b_num, full_wide.Wide_encode(msg_num))
local d = c_runtime.decode(plan, b_num)
t.assert_equals(d.a_int64, 42)
t.assert_equals(d, full_wide.Wide_decode(b_num),
'C decode matches mode=full shape-for-shape')
end
end