local ffi = assert( require 'ffi' ) local buffer = assert( require( "string.buffer" ) ) local ipString = assert( require 'shared.ipstring' ) local packet = {} local mt = { __index = { new = function( self ) return ffi.new( self.ct ) end } } local function newStruct( t ) assert( not( packet[ t.name ] or packet[ t.netname ] )) packet[ t.name ] = t packet[ t.netname ] = t ffi.cdef(("typedef struct {uint8_t netname;\n%s;\n} %s;"):format( table.concat( t, ";\n" ), t.name )) t.ct = ffi.typeof( ffi.new( t.name ) ) t.size = ffi.sizeof( t.ct ) assert( t.size < 500, t.name ) setmetatable( t, mt ) --print( "Packet:", t.name, "Members:", #t + 1, "Size:", t.size, "Alignment:", ffi.alignof( t.ct ) ) end newStruct{ name = "serverInfo", netname = 42, "uint8_t players", "uint8_t capacity", "ipAddress ip", "uint16_t version", "uint16_t port", "char svname[32]", "char map[32]", } newStruct{ name = "clientInfo", netname = 22, "char username[31]", } newStruct{ name = "metaServer", netname = 123, "char padding[300]" --Just a bunch of padding to mitigate amplification. } newStruct{ name = "heartbeat", netname = 69, "uint32_t tick", "uint32_t hash", } newStruct{ name = "insect", netname = 81, "uint8_t id", "bool dead", "int8_t hp", "int8_t vx", "int8_t vy", "uint8_t folio", "uint32_t x", "uint32_t y", } newStruct{ name = "soleil", netname = 27, "uint16_t azimuth", "uint16_t altitude", "int8_t vazi", "int8_t valt", } newStruct{ name = "playerChange", netname = 72, "uint8_t id", "uint8_t role", "char username[31]", } newStruct{ name = "chatMessage", netname = 54, "char cmsg[127]", } newStruct{ name = "command", netname = 32, "char command", } newStruct{ name = "advertised", netname = 144, "uint32_t time", } local readBuffer = buffer.new( 1024 ) function packet.deserialise( str ) readBuffer:set( str ) local data = {} local types = {} local n = 0 while #readBuffer ~= 0 do local netname = readBuffer:ref()[0] --Read a byte to determine the packet type. local t = packet[ netname ] if not t then error( "Malformed packet. Unknown header:\n"..readBuffer:get() ) end if #readBuffer < t.size then error( "Malformed packet. Packet too small:\n"..readBuffer:get() ) end --Malformed packets might cause an overread. --Allocate new struct and copy into it. n = n + 1 data[n] = ffi.new( t.ct ) types[n] = t.name ffi.copy( data[n], readBuffer:ref(), t.size ) readBuffer:skip( t.size ) end return data, types end function packet.getString( member ) return ffi.string( member, ffi.sizeof( member ) ) end local writeBuffer = buffer.new( 1024 ) function packet.add( struct, data ) local str = ffi.new( struct.ct, data or 0 ) str.netname = assert( struct.netname ) writeBuffer:putcdata( str, struct.size ) return str end function packet.get() return writeBuffer:get() end mt.__call = packet.add local testing = testing --TESTS-- if testing then packet.serverInfo{ players = 0, capacity = 255, map = "abcdefghijklmnopqrstuvwxyz1234567890", svname = "๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜kissyfaceserver", version = 25, port = 51312, ip = ipString.new{ 132, 213, 45, 21 } } packet.heartbeat{ tick = 49, hash = 33753745832876, protocol = 25 } packet.insect{ id = 5, dead = true, hp = -3, vx = -5, vy = 47, x = 59183, y = 21412 } local d, t = packet.deserialise( packet.get() ) assert( #writeBuffer == 0, "Test failed. Write buffer not empty!" ) for i = 1, #d do print( "", t[i], d[i] ) if t[i] == 'serverInfo' then print( "", "", packet.getString( d[i].map ), packet.getString( d[i].svname ) ) end if t[i] == 'insect' then print( d[i].vx ) end end packet[42]{} assert( not( pcall( packet.deserialise, "grgrsgs" ) ), "Test failed. Failed to reject malformed packet." ) end --END TESTS-- return packet