Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I convert a cdata structure into a lua string?

Tags:

lua

ffi

luajit

I'm in the middle of writing a small application that needs to read some complex binary messages in LuaJit.

I've been using the bit module and string.rep a lot. However, it's all very cumbersome. I'm new to using LuaJit and think there might be a much easier way using FFI.

In C I can declare a structure like this:

struct mystruct
{
  uint32_t field1;
  char     field2[6];
  uin64_t  field3;
  short    field4;
} __attribute__(packed);

In reading LuaJit's FFI it seems you can declare

ffi.cdef[[
    #pragma pack(1)
    struct mystruct
    {
      uint32_t field1;
      char     field2[6];
      uin64_t  field3;
      short    field4;
    };
]]

I can then create a mystruct and access the fields like this:

local ms = ffi.new("mystruct")
ms.field1 = 32;
// ... etc

But, how do I convert this back into a lua string?

I tried this, but it didn't seem to do what I wanted.

local s = tostring(ms)

and this:

local s = ffi.string(ms)

produces the following error "bad argument #1 to 'string' (cannot convert 'struct mystruct' to 'const char *')"

So I tried a cast:

local s = ffi.string(ffi.cast("char*", ms))

No error, but it looks wrong on the wire.

like image 855
hookenz Avatar asked Dec 11 '22 22:12

hookenz


1 Answers

You have to explicitly specify the length when using ffi.string with a non-string-like parameter:

str = ffi.string(ptr [,len])

Creates an interned Lua string from the data pointed to by ptr.

If the optional argument len is missing, ptr is converted to a "char *" and the data is assumed to be zero-terminated. The length of the string is computed with strlen().

When running the following code, I get the expected (little endian) result:

ffi = require 'ffi'
ffi.cdef[[
    typedef unsigned long uint32_t;
    typedef unsigned long long uint64_t;
    #pragma pack(1)
    struct mystruct
    {
      uint32_t field1;
      char     field2[6];
      uint64_t  field3;
      short    field4;
    };
]]

function string.tohex(str)
    return (str:gsub('.', function (c)
        return string.format('%02X', string.byte(c))
    end))
end

ms = ffi.new('struct mystruct', 1, {2, 3, 4, 5, 6, 7}, 8, 9)
s = ffi.string(ms, ffi.sizeof(ms)) -- specify how long the byte sequence is
print(s:tohex()) --> 0100000002030405060708000000000000000900

Update: I know this is not a part of the original question, but I just learned this trick, and in order to be complete, here is a way to convert Lua string back to FFI cdata:

    data = ffi.new('struct mystruct')   -- create a new cdata
    ffi.copy(data, s, ffi.sizeof(data)) -- fill it with data from Lua string 's'
    print(data.field1, data.field4)     --> 1   9
like image 132
Michal Kottman Avatar answered Dec 28 '22 06:12

Michal Kottman