Files
slimpack/src/main/formats/gvariant.lua
2025-03-16 01:50:21 +02:00

403 lines
8.8 KiB
Lua

local fmt = require "fmt";
local uint8_le_fmt = fmt.new "<! I1";
local uint16_le_fmt = fmt.new "<! I2";
local uint32_le_fmt = fmt.new "<! I4";
local parsers = {};
local cache = setmetatable({}, { __mode = "v" });
-- local indent = "";
local function alignof(i, n)
return math.ceil((i - 1) / n) * n + 1;
end
local function offset_reader(s, e)
if e - s + 1 < 256 then
return function (buf, i)
local res = uint8_le_fmt:unpack(buf, e - i + 1);
return res;
end, 1;
elseif e - s + 1 < 65536 then
return function (buf, i)
local res = uint16_le_fmt:unpack(buf, e - i * 2 + 1);
return res;
end, 2;
else
return function (buf, i)
local res = uint32_le_fmt:unpack(buf, e - i * 4 + 1);
return res;
end, 4;
end
end
local function construct_reader_no_cache(str, i)
local c = str:sub(i, i);
if c == "b" then
return parsers.bool(), i + 1;
elseif c == "y" then
return parsers.uint8(), i + 1;
elseif c == "n" then
return parsers.int16(), i + 1;
elseif c == "q" then
return parsers.uint16(), i + 1;
elseif c == "i" then
return parsers.int32(), i + 1;
elseif c == "u" then
return parsers.uint32(), i + 1;
elseif c == "x" then
return parsers.int64(), i + 1;
elseif c == "t" then
return parsers.uint64(), i + 1;
elseif c == "d" then
return parsers.float64(), i + 1;
elseif c == "s" or c == "o" or c == "g" then
return parsers.string(), i + 1;
elseif c == "r" then
return parsers.ref(), i + 1;
elseif c == "v" then
return parsers.variant(), i + 1;
elseif c == "m" then
local reader;
reader, i = construct_reader_no_cache(str, i + 1);
return parsers.maybe(reader), i;
elseif c == "a" or c == "l" then
i = i + 1;
local is_gen = c == "l";
if str:sub(i, i) == "{" then
i = i + 1;
local key, val;
key, i = construct_reader_no_cache(str, i);
val, i = construct_reader_no_cache(str, i);
if str:sub(i, i) ~= "}" then
error "malformed gvarian format";
end
i = i + 1;
return parsers.map(key, val), i;
elseif str:sub(i, i) == "y" then
i = i + 1;
return parsers.buffer(), i;
else
local reader;
reader, i = construct_reader_no_cache(str, i);
return parsers.array(reader, is_gen), i;
end
elseif c == "(" then
i = i + 1;
local tuple_parsers = {};
while str:sub(i, i) ~= ")" do
tuple_parsers[#tuple_parsers + 1], i = construct_reader_no_cache(str, i);
end
i = i + 1;
return parsers.tuple(table.unpack(tuple_parsers)), i;
elseif c == "{" then
i = i + 1;
local key, val;
key, i = construct_reader_no_cache(str, i);
val, i = construct_reader_no_cache(str, i);
if str:sub(i, i) ~= "}" then
error "malformed gvarian format";
end
i = i + 1;
return parsers.tuple(key, val), i;
else
error "malformed gvariant format";
end
end
local function construct_reader(str)
if not cache[str] then
cache[str] = { construct_reader_no_cache(str, 1) };
end
return cache[str][1], cache[str][2];
end
local function encoded_helper(c)
return ("%02x"):format(string.byte(c));
end
local function to_encoded(str)
return str:gsub(".", encoded_helper);
end
local function fmt_to_parser(format, fixed, align)
return function ()
return {
fixed = fixed, align = align,
read = function(buf, s)
return (format:unpack(buf, s));
end,
};
end
end
parsers.uint8 = fmt_to_parser(fmt.new ">! I1", 1, 1);
parsers.uint16 = fmt_to_parser(fmt.new ">! I2", 2, 2);
parsers.uint32 = fmt_to_parser(fmt.new ">! I4", 4, 4);
parsers.uint64 = fmt_to_parser(fmt.new ">! I8", 8, 8);
parsers.int8 = fmt_to_parser(fmt.new ">! i1", 1, 1);
parsers.int16 = fmt_to_parser(fmt.new ">! i2", 2, 2);
parsers.int32 = fmt_to_parser(fmt.new ">! i4", 4, 4);
parsers.int64 = fmt_to_parser(fmt.new ">! i8", 8, 8);
parsers.float32 = fmt_to_parser(fmt.new ">! f", 4, 4);
parsers.float64 = fmt_to_parser(fmt.new ">! d", 8, 8);
function parsers.string()
return {
fixed = nil, align = 1,
read = function(buf, s, e)
return buf:sub(s, e - 1);
end,
};
end
function parsers.ref()
return {
fixed = nil, align = 1,
read = function(buf, s, e)
return to_encoded(buf:sub(s, e));
end,
};
end
function parsers.buffer()
return {
fixed = nil, align = 1,
read = function(buf, s, e)
return buf:sub(s, e);
end,
};
end
function parsers.bool()
return {
fixed = 1, align = 1,
read = function(buf, s, e)
return buf:sub(s, e) == "\1";
end,
};
end
function parsers.maybe(val_parser)
if val_parser.fixed then
return {
fixed = nil, align = val_parser.align,
read = function(buf, s, e)
if s > e then
return nil;
else
return val_parser.read(buf, alignof(s, val_parser.align), e);
end
end
};
else
return {
fixed = nil, align = val_parser.align,
read = function(buf, s, e)
if s > e then
return nil;
else
return val_parser.read(buf, alignof(s, val_parser.align), e - 1);
end
end
};
end
end
function parsers.array(el_parser, as_iterable)
return {
fixed = nil, align = el_parser.align,
read = function(buf, s, e)
if s > e then
if as_iterable then
return function () return nil end
else
return {};
end
end
s = alignof(s, el_parser.align);
local read_offset, offset_size = offset_reader(s, e);
local n = ((e - s + 1) - read_offset(buf, 1)) / offset_size;
local curr_s = s;
-- print(indent, "ARR\t", s, e, n);
-- indent = indent .. "\t";
assert(not (n % 1 > 0), "offset calculation error");
assert(n > 0, "negative array length");
n = math.floor(n);
if as_iterable then
local i = 0;
return function ()
if i >= n then
-- indent = indent:sub(1, -2);
return nil;
end
i = i + 1;
local curr_e = s + read_offset(buf, n - i + 1) - 1;
curr_s = alignof(curr_s, el_parser.align);
-- print(indent, "ARR ELEMENT", i, curr_s, curr_e);
local res = el_parser.read(buf, curr_s, curr_e);
curr_s = curr_e + 1;
return res;
end
else
local res = {};
for i = 1, n do
local curr_e = s + read_offset(buf, n - i + 1) - 1;
curr_s = alignof(curr_s, el_parser.align);
-- print(indent, "ARR ELEMENT", i, curr_s, curr_e);
res[i] = el_parser.read(buf, curr_s, curr_e);
curr_s = curr_e + 1;
end
-- indent = indent:sub(1, -2);
return res;
end
end
};
end
function parsers.map(key, val)
local arr_parser = parsers.array(parsers.tuple(key, val), true);
return {
fixed = nil, align = arr_parser.align,
read = function (buf, s, e)
local res = {};
for el in arr_parser.read(buf, s, e) do
res[el[1]] = el[2];
end
return res;
end
}
end
function parsers.tuple(...)
local descriptors = { ... };
local align = 1;
--- @type integer | nil
local fixed = 0;
for i = 1, select("#", ...) do
assert(select(i, ...) ~= nil, "don't pass nil arguments!");
local parser = descriptors[i];
if align < parser.align then
align = parser.align;
end
if parser.fixed == nil then
fixed = nil;
elseif fixed ~= nil then
fixed = alignof(fixed, parser.align) + parser.fixed;
end
end
return {
fixed = fixed, align = align,
read = function (buff, s, e)
if s > e then return {} end
s = alignof(s, align);
-- print(indent, "TUPLE\t", s, e);
-- indent = indent .. "\t";
local read_offset, offset_size = offset_reader(s, e);
local res = {};
local curr_s = s;
local offset_i = 0;
for i = 1, #descriptors do
curr_s = alignof(curr_s, descriptors[i].align);
local curr_e;
if descriptors[i].fixed then
curr_e = curr_s + descriptors[i].fixed - 1;
-- print(indent, "TUPLE FIXEL", i, curr_s, curr_e);
elseif i == #descriptors then
curr_e = e - offset_size * offset_i;
-- print(indent, "TUPLE LASTEL", i, curr_s, curr_e);
else
offset_i = offset_i + 1;
curr_e = s + read_offset(buff, offset_i) - 1;
-- print(indent, "TUPLE DYNEL", i, curr_s, curr_e);
end
assert(curr_e - curr_s + 1 >= 0, "negative length segment");
res[i] = descriptors[i].read(buff, curr_s, curr_e);
curr_s = curr_e + 1;
end
-- indent = indent:sub(1, -2);
return res;
end
};
end
function parsers.variant()
return {
fixed = nil, align = 8,
read = function (buff, s, e)
local format = buff:sub(s, e):match "\0([^\0]+)$";
local parser = construct_reader(format);
-- print(indent, "VARIANT", format);
return parser.read(buff, alignof(s, parser.align), e - #format - 1);
end
};
end
return function (format)
local readers = {};
while #format > 0 do
local i;
readers[#readers + 1], i = construct_reader(format);
format = format:sub(i);
end
if #readers == 0 then
return function () end
elseif #readers == 1 then
local reader = readers[1];
return function (buff, s, e)
return reader.read(buff, s or 1, e or #buff);
end
else
local tuple_parser = parsers.tuple(table.unpack(readers));
return function (buff, s, e)
return table.unpack(tuple_parser.read(buff, s or 1, e or #buff));
end
end
end