-- -- Copyright 2016 diacritic -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . -- -------------------------------------- package.preload['wssdl.bit'] = function (...) local oldbit = bit bit = {} setmetatable(bit, { __index = function(bit, key) if oldbit[key] == nil then return nil end return function(x, ...) local mm = nil if key.sub(1, 1) == 'b' then for i, v in ipairs({x, ...}) do local mt = getmetatable(v) if mt and mt['__' .. key] then mm = mt['__' .. key] break end end else local mt = getmetatable(x) if mt and mt['__' .. key] then mm = mt['__' .. key] end end return (mm or oldbit[key])(x, ...) end end; __metatable = false; }) end -------------------------------------- package.preload['wssdl.core'] = function (...) local wssdl = {} require('wssdl.bit') local placeholder = require 'wssdl.placeholder' :init(wssdl) local utils = require 'wssdl.utils' local ws = require 'wssdl.wireshark' local debug = require 'wssdl.debug' local initenv = function () wssdl.env, wssdl.fenv = debug.getfenv(4) end local make_fields = nil wssdl._packet = { _properties = { padding = 0, size = 0, desegment = false, }; _create = function(pkt, def) local newpacket = {} newpacket = { _definition = def, _values = {}, _properties = pkt._properties, _imbue = function (field, ...) local pkt = newpacket:eval({...}) field._type = "packet" field._packet = pkt return field end; eval = function (pkt, params) if next(params) == nil then return pkt end if not pkt._properties.noclone then pkt = utils.deepcopy(pkt) end for i, v in ipairs(pkt._definition) do local def = v:_eval(params) if def == nil then table.remove(pkt._definition, i) else pkt._definition[i] = def end end pkt._lookup = {} for i, v in ipairs(pkt._definition) do pkt._lookup[v._name] = i end return pkt end; proto = ws.proto } newpacket._lookup = {} for i, v in ipairs(def) do newpacket._lookup[v._name] = i end newpacket.fields = {} setmetatable(newpacket.fields, { __index = function(_, k) local idx = newpacket._lookup[k] if idx ~= nil then return newpacket._definition[idx] end end }) setmetatable(newpacket, { __len = pkt._calcsize; }) return newpacket end; padding = function(pkt, pad) pkt._properties.padding = pad return pkt end; size = function(pkt, sz) pkt._properties.size = sz return pkt end; desegment = function(pkt, dseg) pkt._properties.desegment = dseg or true return pkt end; _calcsize = function(pkt) local sz = 0 if pkt._properties.size > 0 then sz = tonumber(pkt.size) else for _, v in ipairs(pkt._definition) do sz = sz + #v end end if pkt._properties.padding > 0 then local mask = pkt._properties.padding - 1 sz = bit.band(sz + mask, bit.bnot(mask)) end return sz end; } wssdl._current_def = nil setmetatable(wssdl._packet, { __call = function(pkt, ...) debug.setfenv(wssdl.fenv, wssdl.env) debug.set_locals(3, wssdl._locals) local out = pkt._create(pkt, ...) for k, v in pairs(wssdl._current_def) do if k:sub(1,1) ~= '_' then v._pktdef = nil end end wssdl._current_def = nil return out end; }) local packetdef_metatable = nil local make_packetdef_placeholder make_packetdef_placeholder = function(t, k) local o = { _name = k; _pktdef = wssdl._current_def; _eval = function(field, params) for k, v in pairs(field) do field[k] = placeholder.do_eval(v, params) end return field end } setmetatable(o, placeholder.metatable(_G, packetdef_metatable, make_packetdef_placeholder)) return o end; packetdef_metatable = { __index = function(t, k) local o = make_packetdef_placeholder(t, k) debug.setfenv(wssdl.fenv, wssdl.env) debug.set_locals(3, wssdl._locals) return o end; } local dissectdef_metatable = nil local make_dissectdef_placeholder make_dissectdef_placeholder = function(ctx, k) local o = { _path = { k }; } setmetatable(o, { __index = function(t, k) debug.setfenv(wssdl.fenv, wssdl.env) debug.set_locals(3, wssdl._locals) t._path[#t._path + 1] = k return t end; __call = function(t, _, params) local method = t._path[#t._path] t._path[#t._path] = nil local tname = table.concat(t._path, '.') local dt = DissectorTable.get(tname) for k, v in pairs(params) do dt[method](dt, k, v) end local env = setmetatable({}, dissectdef_metatable()) debug.setfenv(wssdl.fenv, env) wssdl._locals = debug.get_locals(3) debug.reset_locals(3, nil, make_dissectdef_placeholder) end; }) return o end dissectdef_metatable = function(newdissect) return { __index = function(t, k) local o = make_dissectdef_placeholder(t, k) debug.setfenv(wssdl.fenv, wssdl.env) debug.set_locals(3, wssdl._locals) return o end; } end setmetatable(wssdl, { __index = function(t, k) initenv() if k == 'packet' then local newpacket = {} for k, v in pairs(wssdl._packet) do newpacket[k] = v end local newprops = {} for k, v in pairs(wssdl._packet._properties) do newprops[k] = v end newpacket._properties = newprops setmetatable(newpacket, getmetatable(wssdl._packet)) wssdl._current_def = { _pktdef = newpacket } local env = setmetatable({}, packetdef_metatable) debug.setfenv(wssdl.fenv, env) wssdl._locals = debug.get_locals(3) debug.reset_locals(3, nil, make_packetdef_placeholder) return newpacket elseif k == 'dissect' then local newdissect = {} setmetatable(newdissect, { __call = function(dissect, ...) debug.setfenv(wssdl.fenv, wssdl.env) debug.set_locals(3, wssdl._locals) return nil end; }) local env = setmetatable({}, dissectdef_metatable(newdissect)) debug.setfenv(wssdl.fenv, env) wssdl._locals = debug.get_locals(3) debug.reset_locals(3, nil, make_dissectdef_placeholder) return newdissect end end; }) wssdl.dissector = ws.dissector return wssdl end -------------------------------------- package.preload['wssdl.wireshark'] = function (...) local ws = {} local utils = require 'wssdl.utils' local known_dissectors = {} do local dissectors = DissectorTable.list() for i, v in ipairs(dissectors) do known_dissectors[v] = true end end ws.make_fields = function (fields, pkt, prefix) local prefix = prefix or '' for i, field in ipairs(pkt._definition) do local ftype = nil if field._type == 'packet' then local pkt = field._packet pkt._properties.noclone = true ws.make_fields(fields, pkt, prefix .. field._name .. '.') ftype = ftypes.STRING elseif field._type == 'payload' then ftype = ftypes.PROTOCOL elseif field._type == 'string' then local tname = 'STRING' if type(field._size) == 'number' and field._size == 0 then tname = tname .. 'Z' end ftype = ftypes[tname] elseif field._type == 'address' then if field._size == 32 then ftype = ftypes.IPv4 else if utils.semver(get_version()) >= utils.semver('2.3.0') then ftype = ftypes.IPv6 else ftype = ftypes.STRING end end elseif field._type == 'bits' then local len = #field if type(len) == 'number' then local tname = 'UINT' .. tostring(math.ceil(len / 8) * 8) ftype = ftypes[tname] else ftype = ftypes.UINT64 end elseif field._type == 'float' then local len = #field if type(len) ~= 'number' then error('wssdl: Cannot compute size of primitive ' .. utils.quote(field._name) .. ' field.') end if len == 4 then ftype = ftypes.FLOAT else ftype = ftypes.DOUBLE end else local corr = { signed = 'INT', unsigned = 'UINT', bytes = 'BYTES', bool = 'BOOLEAN', } local tname = corr[field._type] if field._type == 'signed' or field._type == 'unsigned' then local len = #field if type(len) ~= 'number' then error('wssdl: Cannot compute size of primitive ' .. utils.quote(field._name) .. ' field.') end tname = tname .. tostring(len > 32 and 64 or math.ceil(len / 8) * 8) end ftype = ftypes[tname] end local format = nil if field._format ~= nil then local corr = { decimal = base.DEC, hexadecimal = base.HEX, octal = base.OCT, } format = corr[field._format] end fields[prefix .. field._name] = ProtoField.new( field._displayname or field._name, prefix .. field._name, ftype, nil, format, nil, field._description) field._ftype = ftype end end local dissect_int_base = function(char, c64, mname) return function (field, buf, raw, idx, sz) if idx % 8 > 0 then if sz > 64 then error('wssdl: Unaligned ' .. mname .. ' field ' .. utils.quote(field._name) .. ' is larger than 64 bits, which is not supported by wireshark.') end local fmt, fmtp if math.ceil(sz / 8) > 4 then fmt = (field._le and '<' or '>') .. c64 fmtp = '>E' else fmt = (field._le and '<' or '>') .. char .. tostring(math.ceil(sz / 8)) fmtp = '>I' .. tostring(math.ceil(sz / 8)) end local packed = Struct.pack(fmtp, raw:bitfield(idx % 8, sz)) return raw, Struct.unpack(fmt, packed), sz else local val if mname == 'int' and sz == 24 and utils.semver(get_version()) < utils.semver('2.3.0') then local lo, hi = raw(1,2):uint(), raw(0,1):uint() val = Struct.unpack((field._le and '<' or '>') .. 'I3', Struct.pack('>I1I2', hi, lo)) else val = raw[(field._le and 'le_' or '') .. mname .. (sz > 32 and '64' or '')](raw) end return raw, val, sz end end end local dissect_type = { bits = function (field, buf, raw, idx, sz, reverse) if sz > 64 then error('wssdl: "' .. field._type .. '" field ' .. field._name .. ' is larger than 64 bits, which is not supported by wireshark.') end local start = reverse and idx - sz or idx return raw, raw:bitfield(start % 8, sz), sz end; string = function (field, buf, raw, idx, sz, reverse) local mname = 'string' if not field._size or field._size == 0 then if reverse then error('wssdl: Null-terminated strings cannot logically be used as footer fields.') end raw = buf(math.floor(idx / 8)) mname = mname .. 'z' end if field._basesz == 2 then mname = 'u' .. mname end local val = raw[mname](raw) sz = #val * 8 if not field._size or field._size == 0 then sz = sz + field._basesz * 8 end return raw, val, sz end; address = function (field, buf, raw, idx, sz) local mname = sz == 32 and 'ipv4' or 'ipv6' local val if utils.semver(get_version()) < utils.semver('2.3.0') and mname == 'ipv6' then val = utils.tvb_ipv6(raw) label = {(field._displayname or field._name) .. ': ', val} else val = raw[mname](raw) end return raw, val, sz, label end; bytes = function (field, buf, raw, idx, sz) if idx % 8 > 0 then error('wssdl: field ' .. utils.quote(field._name) .. ' is an unaligned "bytes" field, which is not supported.') end return raw, Struct.fromhex(tostring(raw:bytes())), sz end; signed = dissect_int_base('i', 'e', 'int'); unsigned = dissect_int_base('I', 'E', 'uint'); float = function (field, buf, raw, idx, sz) if idx % 8 > 0 then if sz > 64 then error('wssdl: Unaligned float field ' .. field._name .. ' is larger than 64 bits, which is not supported by wireshark.') end local fmt = (field._le and '<' or '>') .. (sz == 64 and 'd' or 'f') local packed = Struct.pack(sz == 64 and '>E' or '>I4', raw:bitfield(idx % 8, sz)) return raw, Struct.unpack(fmt, packed), sz else local val = field._le and raw:le_float() or raw:float() return raw, val, sz end end; default = function (field, buf, raw, idx, sz) error('wssdl: Unknown "' .. field._type .. '" field ' .. field._name .. '.') end; } dissect_type.bool = dissect_type.bits; ws.dissector = function (pkt, proto) local function tree_add_fields(pkt, prefix, tree, pktval) for i, field in ipairs(pkt._definition) do local protofield = proto.fields[prefix .. field._name] local labels = pktval.label[field._name] or {} local val = pktval.val[field._name] local raw = pktval.buf[field._name] if field._type == 'packet' then local pktval = { buf = raw, label = labels, val = val } node = tree:add(protofield, raw._self, '', unpack(labels)) tree_add_fields(field._packet, prefix .. field._name .. '.', node, pktval) else node = tree:add(protofield, raw, val, unpack(labels)) end end end local function dissect_pkt(pkt, start, buf, pinfo, istart, iend, reverse) local idx = start local pktval = { sz = {}, buf = {}, val = {}, label = {} } local subdissect = {} local function size_of(field) local sz = #field if sz and type(sz) ~= 'number' then pkt:eval(pktval.val) sz = #field end if sz and type(sz) ~= 'number' then error('wssdl: Cannot evaluate size of ' .. utils.quote(field._name) .. ' field.') end return sz end local function dissect_field(ifield, field, idx) local sz = nil local raw = nil local val = nil local label = nil local sdiss = nil if field._type == 'packet' then raw = reverse and buf(0, math.ceil(idx / 8)) or buf(math.floor(idx / 8)) local istart = reverse and #field._packet._definition or 1 local iend = reverse and 1 or #field._packet._definition local start = reverse and idx or idx % 8 local res, err = dissect_pkt(field._packet, start, raw:tvb(), pinfo, istart, iend, reverse) if err then return nil, err end sz, val, sdiss = unpack(res, 1, 3) if reverse then sz = -sz end local procsz = math.ceil((sz + idx % 8) / 8) val.buf._self = reverse and buf(math.floor(idx / 8) - procsz, procsz) or buf(math.floor(idx / 8), procsz) raw = val.buf label = val.label val = val.val for k, v in pairs(sdiss) do subdissect[#subdissect + 1] = v end else sz = size_of(field) if sz == nil then if reverse then error('wssdl: Cannot evaluate the size of the footer field ' .. utils.quote(field._name)) elseif #pkt._definition ~= ifield then local res, err = dissect_pkt(pkt, buf:len() * 8, buf, pinfo, #pkt._definition, ifield + 1, true) if err then return nil, err end local len, val, sdiss = unpack(res, 1, 3) len = -len for k, v in pairs(sdiss) do subdissect[#subdissect + 1] = v end for k, v in pairs(val.sz) do pktval.sz[k] = v end for k, v in pairs(val.val) do pktval.val[k] = v end for k, v in pairs(val.buf) do pktval.buf[k] = v end for k, v in pairs(val.label) do pktval.label[k] = v end sz = (buf:len() * 8 - len) - idx else sz = buf:len() * 8 - idx end end local offlen = math.ceil((sz + idx % 8) / 8) local needed = reverse and math.ceil(idx / 8) or math.floor(idx / 8) + offlen raw = buf(0,0) if sz > 0 and needed > buf:len() or needed < offlen then if needed < 0 then needed = buf:len() - needed end return nil, {needed = needed} end if needed <= buf:len() and sz > 0 then raw = buf(reverse and math.ceil(idx / 8) - offlen or math.floor(idx / 8), offlen) end if field._type == 'payload' then local dtname = field._dt_name or table.concat({string.lower(proto.name), unpack(field._dissection_criterion)}, '.') local dt = DissectorTable.get(dtname) local val = pktval.val for i, v in pairs(field._dissection_criterion) do val = val[v] if not val then error('wssdl: Dissection criterion for ' .. utils.quote(field._name) .. ' does not match a real field.') end end subdissect[#subdissect + 1] = {dt = dt, tvb = raw:tvb(), val = val} else local df = dissect_type[field._type] or dissect_type.default raw, val, sz, label = df(field, buf, raw, idx, sz, reverse) end end return {raw, val, sz, label} end local istep = reverse and -1 or 1 for i = istart, iend, istep do local field = pkt._definition[i] if pktval.val[field._name] == nil then local res, err = dissect_field(i, field, idx) if err then if err.needed > 0 then if pkt._properties.desegment then local desegment = (err.needed - buf:len()) * 8 for j = i + 1, #pkt._definition do local len = #pkt._definition[j] if type(len) ~= 'number' then err.desegment = DESEGMENT_ONE_MORE_SEGMENT return nil, err end desegment = desegment + len end err.desegment = math.ceil(desegment / 8) else err.expert = proto.experts.too_short end end return nil, err end local raw, val, len, label = unpack(res, 1, 4) pktval.sz[field._name] = len pktval.buf[field._name] = raw pktval.val[field._name] = val pktval.label[field._name] = label end idx = reverse and idx - pktval.sz[field._name] or idx + pktval.sz[field._name] end return {idx - start, pktval, subdissect} end local function dissect_proto(pkt, buf, pinfo, root) local pkt = utils.deepcopy(pkt) pkt._properties.noclone = true local res, err = dissect_pkt(pkt, 0, buf, pinfo, 1, #pkt._definition, false) if err and err.desegment then return len, desegment end pinfo.cols.protocol = proto.name local tree = root:add(proto, buf(), proto.description) if err then if err.expert then tree:add_proto_expert_info(err.expert) end return -1, 0 end local len, val, subdissect = unpack(res, 1, 3) tree_add_fields(pkt, string.lower(proto.name) .. '.', tree, val) for k, v in pairs(subdissect) do if v.tvb:len() > 0 then v.dt:try(v.val, v.tvb, pinfo, root) end end return math.ceil(len / 8), desegment end return function(buf, pinfo, root) local pktlen = buf:len() local consumed = 0 if pkt._properties.desegment then while consumed < pktlen do local result, desegment = dissect_proto(pkt, buf(consumed):tvb(), pinfo, root) if result > 0 then consumed = consumed + result elseif result == 0 then return 0 elseif desegment ~= 0 then pinfo.desegment_offset = consumed pinfo.desegment_len = desegment return pktlen else return end end else local result = dissect_proto(pkt, buf, pinfo, root) if result < 0 then return end return result end return consumed end end ws.proto = function (pkt, name, description) local ok, res = pcall(Proto.new, name, description) if not ok then error(res, 2) end local proto = res ws.make_fields(proto.fields, pkt, string.lower(name) .. '.') proto.experts.too_short = ProtoExpert.new( string.lower(name) .. '.too_short.expert', name .. ' message too short', expert.group.MALFORMED, expert.severity.ERROR) for i, field in ipairs(pkt._definition) do if field._type == 'payload' then local dtname = field._dt_name or table.concat({string.lower(proto.name), unpack(field._dissection_criterion)}, '.') local criterion = field._dissection_criterion local target = pkt local j = 1 for k, v in pairs(criterion) do local tfield = target._definition[target._lookup[v]] if j < #criterion then if tfield._type ~= 'packet' then error('wssdl: DissectorTable key ' .. utils.quote(dtname) .. ' does not match a field', 2) end target = tfield._packet else target = tfield end j = j + 1 end if not known_dissectors[dtname] then DissectorTable.new(dtname, nil, target._ftype) known_dissectors[dtname] = true end end end proto.dissector = ws.dissector(pkt, proto) return proto end return ws end -------------------------------------- package.preload['wssdl.utils'] = function (...) local utils = {} utils.copy = function (o) if type(o) == 'table' then local copy = {} for k, v in pairs(o) do copy[k] = v end setmetatable(copy, getmetatable(o)) return copy else return o end end utils.deepcopy = function (o) if type(o) == 'table' then local copy = {} for k, v in pairs(o) do copy[k] = utils.deepcopy(v) end setmetatable(copy, getmetatable(o)) return copy else return o end end utils.quote = function (s) return '‘' .. s .. '’' end utils.semver = function(ver) ver = ver or "" local t, count = {}, 0 ver:gsub("([^%.]+)", function(c) count = count + 1 t[count] = tonumber(c) or 0 end) t.major = t[1] or 0 t.minor = t[2] or 0 t.patch = t[3] or 0 setmetatable(t, { __newindex = false; __lt = function(lhs, rhs) for i, v in ipairs(lhs) do local comp = rhs[i] or 0 if v ~= comp then return v < comp end end return false end; __le = function(lhs, rhs) return lhs < rhs or lhs == rhs end; __eq = function(lhs, rhs) for i, v in ipairs(lhs) do local comp = rhs[i] or 0 if v ~= comp then return false end end return true end; __metatable = false }) return t end utils.tvb_ipv6 = function (tvb) local ip = '' for i=0,7 do local n = tvb(i*2,2):uint() if n ~= 0 then if i > 0 and ip == '' then ip = ':' end ip = ip .. string.format('%x', n) if i < 7 then ip = ip .. ':' end elseif i == 7 then ip = ip .. ':' end end return ip end return utils end -------------------------------------- package.preload['wssdl.specifiers'] = function (...) local specifiers = {} local utils = require 'wssdl.utils' local type_specifier = function (type, basesz) local o = { _imbue = function (field, s) if s then field._size = s * basesz else field._size = nil end field._type = type return field end } return o end local type_specifier_sized = function (type, size) local o = { _imbue = function (field) field._size = size field._type = type return field end } return o end local string_type = function(basesz, nullterm) return { _imbue = function(field, size) if nullterm then field._size = 0 else field._size = size * basesz * 8 end field._type = "string" field._basesz = basesz return field end } end local format_specifier = function(fmt) local o = { _imbue = function(field) field._format = fmt return field end } return o end specifiers.field_types = { bits = type_specifier("bits", 1); bytes = type_specifier("bytes", 8); int = type_specifier("signed", 1); uint = type_specifier("unsigned", 1); bit = type_specifier_sized("bits", 1); i8 = type_specifier_sized("signed", 8); i16 = type_specifier_sized("signed", 16); i24 = type_specifier_sized("signed", 24); i32 = type_specifier_sized("signed", 32); i64 = type_specifier_sized("signed", 64); u8 = type_specifier_sized("unsigned", 8); u16 = type_specifier_sized("unsigned", 16); u24 = type_specifier_sized("unsigned", 24); u32 = type_specifier_sized("unsigned", 32); u64 = type_specifier_sized("unsigned", 64); f32 = type_specifier_sized("float", 32); f64 = type_specifier_sized("float", 64); ipv4 = type_specifier_sized("address", 32); ipv6 = type_specifier_sized("address", 128); utf8 = string_type(1, false); utf8z = string_type(1, true); utf16 = string_type(2, false); utf16z = string_type(2, true); le = { _imbue = function (field) local t = field._type if t == 'address' and field._size == 128 then error('wssdl: Field type can\'t be parsed as Little-Endian', 2) end if t == 'signed' or t == 'unsigned' or t == 'float' or t == 'address' or (t == 'string' and field._basesz == 2) then if type(field._size) ~= 'number' then field._le = true else if field._size > 64 then error('wssdl: Little-Endian fields larger than 64-bits aren\'t supported by Wireshark.', 2) end if field._size % 8 > 0 then error('wssdl: Endianness only makes sense for sizes divisible by 8.', 2) end if field._size <= 8 then error('wssdl: Endianness only makes sense for multiple octets.', 2) end field._le = true end else error('wssdl: Field type can\'t be parsed as Little-Endian', 2) end return field end }; bool = { _imbue = function (field, s) field._size = (s or 1) field._type = "bool" return field end }; payload = { _imbue = function(field, cr_expr, size) local criterion = {} if type(cr_expr) == 'string' then local sep, fields = '\\.', {} local pattern = string.format("([^%s]+)", sep) cr_expr:gsub(pattern, function(c) fields[#fields+1] = c end) criterion = fields else local dt_name = nil if cr_expr._id == nil then dt_name = cr_expr[2] or cr_expr.name cr_expr = cr_expr[1] or cr_expr.criterion end while cr_expr do table.insert(criterion, 1, cr_expr._id) cr_expr = cr_expr._parent end field._dt_name = dt_name end if #criterion == 0 then error('wssdl: Field ' .. utils.quote(field._name) .. ' needs a dissection criterion.', 2) end local path = criterion[1] local f = field._pktdef[criterion[1]] if f and #criterion > 1 then f = f._packet end for i = 2, #criterion do local v = criterion[i] if f then local idx = f._lookup[v] if idx then if i < #criterion then f = rawget(f._definition[idx], '_packet') end else f = nil end end path = path .. '.' .. v end if not f then error('wssdl: Dissection criterion ' .. utils.quote(path) .. ' does not match a real field.', 2) end local validt = { unsigned = true, bits = true, string = true } if not validt[f._type] then error('wssdl: Dissection criterion ' .. utils.quote(path) .. ' must be an unsigned integer or a string.', 2) end field._dissection_criterion = criterion field._size = size field._type = "payload" return field end }; oct = format_specifier('octal'); dec = format_specifier('decimal'); hex = format_specifier('hexadecimal'); description = { _imbue = function(field, desc) field._description = desc return field end }; name = { _imbue = function(field, str) field._displayname = str return field end }; } return specifiers end -------------------------------------- package.preload['wssdl.placeholder'] = function (...) local specifiers = require 'wssdl.specifiers' local utils = require 'wssdl.utils' local debug = require 'wssdl.debug' local placeholder = {} local wssdl = nil placeholder.init = function (self, mod) wssdl = mod return self end local placeholder_metatable = {} local do_eval = function (v, values) if type(v) == 'table' and v._eval ~= nil then return v:_eval(values) else return v end end placeholder.do_eval = do_eval local new_placeholder = function (eval) local obj = { _eval = eval } setmetatable(obj, placeholder_metatable) return obj end local new_binop_placeholder = function(eval) return function(lhs, rhs) local ph = new_placeholder(eval) ph._rhs = rhs ph._lhs = lhs return ph end end local new_valued_placeholder = function(eval) return function(value) local ph = new_placeholder(eval) ph._value = value return ph end end local new_funcall_placeholder = function(func, ...) local ph = new_placeholder (function(self, values) return self._func(unpack(self._params)) end) ph._func = func ph._params = {...} return ph end local new_field_placeholder = function(id, field) local ph = new_placeholder (function(self, values) local val = values[self._id] if val ~= nil then return val else return new_field_placeholder(self._id, self._field) end end) ph._id = id ph._field = field return ph end local new_subscript_placeholder = function(parent, subscript, field) local ph = new_placeholder (function(self, values) return do_eval(self._parent, values)[self._id] end) ph._parent = parent ph._id = subscript ph._field = field return ph end local new_unm_placeholder = new_valued_placeholder (function(self, values) return -do_eval(self._value, values) end) local new_add_placeholder = new_binop_placeholder (function(self, values) return do_eval(self._lhs, values) + do_eval(self._rhs, values) end) local new_sub_placeholder = new_binop_placeholder (function(self, values) return do_eval(self._lhs, values) - do_eval(self._rhs, values) end) local new_mul_placeholder = new_binop_placeholder (function(self, values) return do_eval(self._lhs, values) * do_eval(self._rhs, values) end) local new_div_placeholder = new_binop_placeholder (function(self, values) return do_eval(self._lhs, values) / do_eval(self._rhs, values) end) local new_pow_placeholder = new_binop_placeholder (function(self, values) return do_eval(self._lhs, values) ^ do_eval(self._rhs, values) end) local new_mod_placeholder = new_binop_placeholder (function(self, values) return do_eval(self._lhs, values) % do_eval(self._rhs, values) end) local new_band_placeholder = new_binop_placeholder (function(self, values) return bit.band(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) local new_bor_placeholder = new_binop_placeholder (function(self, values) return bit.bor(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) local new_bxor_placeholder = new_binop_placeholder (function(self, values) return bit.bxor(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) local new_bnot_placeholder = new_valued_placeholder (function(self, values) return bit.bnot(do_eval(self._value, values)) end) local new_lshift_placeholder = new_binop_placeholder (function(self, values) return bit.lshift(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) local new_rshift_placeholder = new_binop_placeholder (function(self, values) return bit.rshift(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) local new_arshift_placeholder = new_binop_placeholder (function(self, values) return bit.arshift(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) local new_rol_placeholder = new_binop_placeholder (function(self, values) return bit.rol(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) local new_ror_placeholder = new_binop_placeholder (function(self, values) return bit.ror(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) local new_bswap_placeholder = new_valued_placeholder (function(self, values) return bit.bswap(do_eval(self._value, values)) end) local new_tobit_placeholder = new_valued_placeholder (function(self, values) return bit.tobit(do_eval(self._value, values)) end) local new_tohex_placeholder = new_binop_placeholder (function(self, values) return bit.tohex(do_eval(self._lhs, values), do_eval(self._rhs, values)) end) placeholder_metatable = { __index = function(t, k) if string.sub(k, 1, 1) == '_' then return nil end if t._field._type ~= 'packet' then error('wssdl: Symbol ' .. utils.quote(t._id) .. ' is not subscriptable.', 2) end local fidx = t._field._packet._lookup[k] if not fidx then local path, e = '', t while t do path = t._id .. '.' .. path t = t._parent end error('wssdl: Symbol ' .. utils.quote(path:sub(1, #path - 1)) .. ' has no member named ' .. utils.quote(k) .. '.', 2) end return new_subscript_placeholder(t, k, t._field._packet._definition[fidx]) end; __unm = function(val) return new_unm_placeholder(val) end; __add = function(lhs, rhs) return new_add_placeholder(lhs, rhs) end; __sub = function(lhs, rhs) return new_sub_placeholder(lhs, rhs) end; __mul = function(lhs, rhs) return new_mul_placeholder(lhs, rhs) end; __div = function(lhs, rhs) return new_div_placeholder(lhs, rhs) end; __pow = function(lhs, rhs) return new_pow_placeholder(lhs, rhs) end; __mod = function(lhs, rhs) return new_mod_placeholder(lhs, rhs) end; __band = function(lhs, rhs) return new_band_placeholder(lhs, rhs) end; __bor = function(lhs, rhs) return new_bor_placeholder(lhs, rhs) end; __bxor = function(lhs, rhs) return new_bxor_placeholder(lhs, rhs) end; __bnot = function(val) return new_bnot_placeholder(val) end; __lshift = function(lhs, rhs) return new_lshift_placeholder(lhs, rhs) end; __rshift = function(lhs, rhs) return new_rshift_placeholder(lhs, rhs) end; __arshift = function(lhs, rhs) return new_arshift_placeholder(lhs, rhs) end; __rol = function(lhs, rhs) return new_rol_placeholder(lhs, rhs) end; __ror = function(lhs, rhs) return new_ror_placeholder(lhs, rhs) end; __bswap = function(val) return new_bswap_placeholder(val) end; __tobit = function(val) return new_tobit_placeholder(val) end; __tohex = function(val, n) return new_tohex_placeholder(val, n) end; } placeholder.metatable = function(defenv, packetdef_metatable, make_pktfield) return { __index = function(field, k) if string.sub(k, 1, 1) == '_' then return nil end if field._name:sub(1,1) == '_' then error('wssdl: Invalid identifier for field ' .. utils.quote(field._name) .. ': Fields must not start with an underscore', 3) end if wssdl._current_def[field._name] and wssdl._current_def[field._name] ~= field then error('wssdl: Duplicate field ' .. utils.quote(field._name) .. ' in packet definition.', 3) end wssdl._current_def[field._name] = field local type = rawget(specifiers.field_types, k) if type == nil then type = rawget(wssdl.env, k) end if type == nil then type = debug.find_local(3, k) end if type == nil then return nil end local locals = utils.copy(wssdl._locals) for k, v in pairs(wssdl._current_def) do if k:sub(1,1) ~= '_' then for i = 1, #locals do local l = wssdl._locals[i] if l and l[1] == k then wssdl._locals[i] = nil end end end end local fieldtype = {} setmetatable(fieldtype, { __call = function(ft, f, ...) local env = setmetatable({}, packetdef_metatable) debug.setfenv(wssdl.fenv, env) wssdl._locals = locals debug.reset_locals(3, nil, make_pktfield) return type._imbue(field, ...) end }) local pktdef = field._pktdef local env = setmetatable({}, { __index = function(t, k) if pktdef[k] == nil then error('wssdl: Unknown symbol ' .. utils.quote(k) .. '.', 2) end return new_field_placeholder(k, pktdef[k]) end; }) debug.setfenv(wssdl.fenv, env) debug.reset_locals(3, nil, function(ctx, n) return new_field_placeholder(n, pktdef[n]) end) debug.set_locals(3, wssdl._locals) return fieldtype end; __len = function (field) local packet = rawget(field, '_packet') if packet ~= nil then return #packet else return field._size end end; } end return placeholder end -------------------------------------- package.preload['wssdl.debug'] = function (...) local debug = {} local luadebug = require 'debug' debug.print = function(tbl, indent, depth) local depth = depth or 3 if depth == 0 then print(string.rep(" ", indent) .. '') return end if not indent then indent = 0 end if type(tbl) == 'table' then for k, v in pairs(tbl) do local formatting = string.rep(" ", indent) .. "[" .. tostring(k) .. "]: " if type(v) == "table" then print(formatting) debug.print(v, indent+1, depth - 1) elseif type(v) == 'string' then print(formatting .. v) else print(formatting .. tostring(v)) end end else print(string.rep(" ", indent) .. tostring(tbl)) end end debug.setfenv = setfenv or function (fn, env) local i = 1 while true do if type(fn) == 'number' then fn = luadebug.getinfo(fn).func end local name = luadebug.getupvalue(fn, i) if name == '_ENV' then luadebug.upvaluejoin(fn, i, (function() return env end), 1) break elseif not name then break end i = i + 1 end return fn end if getfenv then debug.getfenv = function (fn, env) local env = getfenv(fn, env) return env, type(fn) == 'number' and luadebug.getinfo(fn).func or fn end else debug.getfenv = function (fn, env) if type(fn) == 'number' then fn = luadebug.getinfo(fn).func end local i = 1 while true do local name, val = luadebug.getupvalue(fn, i) if name == '_ENV' then return val, fn elseif not name then break end i = i + 1 end end end debug.traceback = function () local level = 1 while true do local info = luadebug.getinfo(level, "Sl") if not info then break end if info.what == "C" then print(level, "") else print(level, string.format("[%s]:%d", info.short_src, info.currentline)) end level = level + 1 end end debug.find_local = function(lvl, n) local i = 1 while true do local name, val = luadebug.getlocal(lvl, i) if not name then break end if name == n then return val end i = i + 1 end i = 1 while true do local name, val = luadebug.getupvalue(lvl, i) if not name then break end if name == n then return val end i = i + 1 end return nil end debug.get_locals = function(lvl) local locals = {} local i = 1 while true do local name, val = luadebug.getlocal(lvl, i) if not name then break end if name ~= '(*temporary)' then locals[i] = { name, val } end i = i + 1 end return locals end debug.set_locals = function(lvl, locals) local i = 1 while true do if locals[i] == nil then break end local name = luadebug.setlocal(lvl, i, locals[i][2]) if not name then break end i = i + 1 end end debug.reset_locals = function(lvl, ctx, fn) local locals = debug.get_locals(lvl + 1) for i = 1, #locals do local o = fn(ctx, locals[i][1]) local name = luadebug.setlocal(lvl, i, o) if not name then break end end end return debug end ----------------------------------------------- do if not package.__loadfile then local original_loadfile = loadfile local function lf(file) local hndl = file:gsub('%.lua$', '') :gsub('/', '.') :gsub('\\', '.') :gsub('%.init$', '') return package.preload[hndl] or original_loadfile(name) end function dofile(name) return lf(name)() end loadfile, package.__loadfile = lf, loadfile end end return require 'wssdl.core'