--[[ adc.lua by blastbeat - ADC stuff ]]-- ----------------------------------// DECLARATION //-- local clean = use "cleantable" --// lua functions //-- local type = use "type" local ipairs = use "ipairs" local assert = use "assert" local tostring = use "tostring" --// lua libs //-- local os = use "os" local math = use "math" local table = use "table" local debug = use "debug" local string = use "string" --// lua lib methods //-- local os_date = os.date local os_time = os.time local os_clock = os.clock local string_sub = string.sub local math_random = math.random local string_gsub = string.gsub local string_find = string.find local string_match = string.match local table_insert = table.insert local table_concat = table.concat local table_remove = table.remove local debug_traceback = debug.traceback --// extern libs //-- local adclib = use "adclib" local unicode = use "unicode" --// extern lib methods //-- local utf_find = unicode.utf8.find local adclib_hash = adclib.hash local adclib_isutf8 = adclib.isutf8 local adclib_hashpas = adclib.hashpas --// core scripts //-- local out = use "out" local mem = use "mem" local types = use "types" --// core methods //-- local types_utf8 = types.utf8 local out_put = out.put local types_check = types.check local mem_gettable = mem.gettable local mem_addtable = mem.addtable --// functions //-- local parse local createid local tokenize local checkadccmd local checkadcstr local checkadcstring --// exported adc object methods //-- local adccmd_pos local adccmd_mysid local adccmd_getnp local adccmd_addnp local adccmd_setnp local adccmd_fourcc local adccmd_deletenp local adccmd_getallnp local adccmd_hasparam local adccmd_targetsid local adccmd_adcstring --// tables //-- local _base32 local _clone local _regex -- some regex patterns local _buffer -- array with message params local _protocol -- adc specs local _protocol_types -- caching.. local _protocol_commands local _adccmds -- collection of all created adc commands --// simple data types //-- local _eol local _th -- pattern strings.. local _su local _sid local _sup local _sta local _bool local _onetwo local _integer local _feature local _contextsend local _contextdirect local _ ----------------------------------// DEFINITION //-- _adccmds = { } _clone = { } _buffer = { } _th = "^" .. string.rep( "[A-Z2-7]", 39 ) .. "$" _su = "[A-Z]" .. string.rep( "[A-Z0-9]", 3 ) .. "," _sta = "^[012]%d%d$" _sid = "^" .. string.rep( "[A-Z2-7]", 4 ) .. "$" _sup = "^" .. string.rep( "[A-Z]", 3 ) .. "[A-Z0-9]$" _bool = "^[1]?$" _onetwo = "^[12]?$" _integer = "^%d*$" _feature = "[%+%-][A-Z]" .. string.rep( "[A-Z0-9]", 3 ) _regex = { th = function( str ) return string_match( str, _th ) end, sid = function( str ) return string_match( str, _sid ) end, bool = function( str ) return string_match( str, _sid ) end, bool = function( str ) return string_match( str, _bool ) end, integer = function( str ) return string_match( str, _integer ) end, sta = function( str ) return string_match( str, _sta ) end, onetwo = function( str ) return string_match( str, _onetwo ) end, su = function( str ) str = str .. "," for i = 1, #str, 5 do if not string_match( string_sub( str, i, i + 5 ), _su ) then return false end end return true end, sup = function( str ) return string_match( str, _sup ) end, feature = function( str ) for i = 1, #str, 5 do if not string_match( string_sub( str, i, i + 5 ), _feature ) then return false end end return true end, default = function( ) return true end, nowhitespace = function( str ) return not ( string_find( str, "\\n" ) or string_find( str, "\\s" ) ) end, context = { hub = "[H]", send = "[BFDE]", bcast = "[BF]", direct = "[DE]", hubdirect = "[HDE]", }, } _protocol = { types = { I = { len = 0, }, H = { len = 0, }, B = { _regex.sid, len = 1, }, F = { _regex.sid, _regex.feature, len = 2, }, D = { _regex.sid, _regex.sid, len = 2, }, E = { _regex.sid, _regex.sid, len = 2, }, }, commands = { SUP = { pp = { len = 0, }, np = { AD = _regex.sup, RM = _regex.sup, }, nonpclones = false, -- doesnt remove named parameters when parameter with same name already was found (for example ADBAS0, ADBASE) }, MSG = { pp = { _regex.default, len = 1, }, np = { PM = _regex.sid, ME = _regex.bool, }, nonpclones = true, -- removes named parameters when parameter with same name already was found (for example ME1, ME) }, STA = { pp = { _regex.sta, _regex.default, len = 2, }, np = { PR = _regex.default, FC = _regex.default, TL = _regex.default, TO = _regex.default, I4 = _regex.default, I6 = _regex.default, FM = _regex.default, FB = _regex.default, }, nonpclones = true, -- removes named parameters when parameter with same name already was found (for example ME1, ME) }, INF = { pp = { len = 0, }, np = { ID = _regex.th, PD = _regex.th, I4 = _regex.default, -- ip string will be compared with real ip later, so no need for checking here.. I6 = _regex.default, U4 = _regex.integer, U6 = _regex.integer, SS = _regex.integer, SF = _regex.integer, US = _regex.integer, DS = _regex.integer, SL = _regex.integer, AS = _regex.integer, AM = _regex.integer, NI = _regex.nowhitespace, HN = _regex.integer, HR = _regex.integer, HO = _regex.integer, OP = _regex.bool, AW = _regex.onetwo, BO = _regex.bool, HI = _regex.bool, HU = _regex.bool, SU = _regex.su, CT = _regex.integer, DE = _regex.default, EM = _regex.default, VE = _regex.default, }, nonpclones = true, -- removes named parameters when parameter with same name already was found (for example HN1, HN4) }, CTM = { pp = { _regex.default, _regex.integer, _regex.default, len = 3, }, np = { }, nonpclones = false, }, RCM = { pp = { _regex.default, _regex.default, len = 2, }, np = { }, nonpclones = false, }, SCH = { pp = { len = 0, }, np = { AN = _regex.default, NO = _regex.default, EX = _regex.default, LE = _regex.integer, GE = _regex.integer, EQ = _regex.integer, TO = _regex.default, TY = _regex.onetwo, TR = _regex.th, TD = _regex.integer, }, nonpclones = true, }, RES = { pp = { len = 0, }, np = { FN = _regex.default, SI = _regex.integer, SL = _regex.integer, TO = _regex.default, TR = _regex.th, TD = _regex.integer, }, nonpclones = true, }, PAS = { pp = { _regex.th, len = 1, }, nonpclones = true, }, }, contexts = { STA = _regex.context.hubdirect, SUP = _regex.context.hub, SID = _regex.context.hub, INF = _regex.context.bcast, MSG = _regex.context.send, SCH = _regex.context.send, RES = _regex.context.direct, CTM = _regex.context.direct, RCM = _regex.context.direct, GPA = _regex.context.hub, PAS = _regex.context.hub, QUI = _regex.context.hub, GET = _regex.context.hub, GFI = _regex.context.hub, SND = _regex.context.hub, } } _base32 = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "2", "3", "4", "5", "6", "7", } _protocol_types = _protocol.types _protocol_commands = _protocol.commands _contextsend = "[BFDE]" _contextdirect = "[DE]" adclib.createsid = function( ) return "".. _base32[ math_random( 32 ) ] .. _base32[ math_random( 32 ) ] .. _base32[ math_random( 32 ) ] .. _base32[ math_random( 32 ) ] end adclib.createsalt = function( num ) num = num or 10 local eol = 0 local data = mem_gettable( ) for i = 1, num do eol = eol + 1 data[ eol ] = _base32[ math_random( 32 ) ] end return table_concat( data ) end checkadccmd = function( data, traceback, noerror ) local what = type( data ) if not _adccmds[ data ] then _ = noerror or error( "wrong type: adccmd expected, got " .. what, traceback or 3 ) return false end return true end checkadcstring = function( data, traceback, noerror ) local what = type( data ) if what ~= "string" or not adclib_isutf8( data ) or not parse( data ) then _ = noerror or error( "wrong type: adcstring expected, got " .. what, traceback or 3 ) return false end return true end checkadcstr = function( data, traceback, noerror ) local what = type( data ) if what ~= "string" or not adclib_isutf8( data ) or utf_find( data, " " ) or utf_find( data, "\n" ) then _ = noerror or error( "wrong type: adcstr expected, got " .. what, traceback or 3 ) return false end return true end createid = function( ) local str = os_date( ) .. os_clock( ) .. os_time( ) local pass = adclib.createsalt( ) local pid = adclib_hashpas( pass .. str, str .. pass ) return pid, adclib_hash( pid ) end tokenize = function( str ) _eol = _eol + 1 _buffer[ _eol ] = str end adccmd_pos = function( self, pos ) types_check( pos, "number" ) if pos == 1 then return self[ 1 ] .. self[ 2 ] else return self[ 2 * pos ] end end adccmd_getallnp = function( self ) local length = self.length local namedstart = self.namedstart if namedstart then local i = namedstart - 3 return function( ) i = i + 3 if i < length then return self[ i ], self[ i + 1 ] end end else return function( ) end end end adccmd_getnp = function( self, target ) types_utf8( target ) local namedstart = self.namedstart if namedstart then for i = namedstart, self.length, 3 do if target == self[ i ] then return self[ i + 1 ] end end end return nil end adccmd_addnp = function( self, target, value ) types_utf8( target ) types_utf8( value ) local length = self.length self[ length ] = " " self[ length + 1 ] = target self[ length + 2 ] = value self[ length + 3 ] = "\n" self.namedstart = self.namedstart or length + 1 local namedend = self.namedend if namedend then self.namedend = namedend + 3 else self.namedend = length + 2 end self.length = length + 3 --types_check( self:adcstring( ), "adcstring" ) return true end adccmd_setnp = function( self, target, value ) types_utf8( target ) types_utf8( value ) local namedstart = self.namedstart local len = self.length if namedstart then for i = namedstart, len, 3 do if target == self[ i ] then self[ i + 1 ] = value return true end end end --types_check( self:adcstring( ), "adcstring" ) return adccmd_addnp( self, target, value ) -- add new np end adccmd_deletenp = function( self, target ) types_utf8( target ) local length = self.length local namedstart = self.namedstart if namedstart then for i = namedstart, length, 3 do if target == self[ i ] then table_remove( self, i - 1 ) table_remove( self, i - 1 ) table_remove( self, i - 1 ) local namedend = self.namedend - 3 self.namedend = namedend self.length = length - 3 if namedend <= namedstart then self.namedstart, self.namedend = nil, nil end return true end end end return false end adccmd_hasparam = function( self, target ) types_utf8( target ) for i = 1, self.length - 1 do local param = self[ i ] if target == param .. self[ i + 1 ] or target == param then return true end end return false end adccmd_adcstring = function( self ) return table_concat( self, "", 1, self.length ) end adccmd_mysid = function( self ) return string_match( self[ 1 ], _contextsend ) and self[ 4 ] end adccmd_targetsid = function( self ) return string_match( self[ 1 ], _contextdirect ) and self[ 6 ] end adccmd_fourcc = function( self ) return self[ 1 ] .. self[ 2 ] end parse = function( data ) --types_utf8( data ) out_put( "adc.lua: try to parse '", data, "'" ) local command = mem_gettable( ) -- array with parsed and checked message params (includes seperators and "\n"); is used also as adc command object with methods _eol = 0 -- end of buffer string_gsub( data, "([^ ]+)", tokenize ) -- extract message data into buffer; seperators wont be saved if _eol < 2 then out_put( "adc.lua: adc message to short" ) return nil end --// extract type, command from message header; check context //-- local fourcc = _buffer[ 1 ] local msgtype = string_sub( fourcc, 1, 1 ) local header = _protocol_types[ msgtype ] if not header then out_put( "adc.lua: type '", msgtype, "' is invalid, unknown or unsupported" ) return nil end local msgcmd = string_sub( fourcc, 2, -1 ) local context = _protocol.contexts[ msgcmd ] if not context or not string_match( msgtype, context ) then out_put( "adc.lua: invalid message header: type/cmd mismatch, unknown or unsupported ('", fourcc, "')" ) return nil end --// parse message header, body and parameters //-- command[ 1 ] = msgtype command[ 2 ] = msgcmd local length = 2 --// header //-- local len = header.len if _eol < len then out_put( "adc.lua: adc message to short" ) return nil end for i, regex in ipairs( header ) do local param = _buffer[ i + 1 ] if not regex( param ) then out_put( "adc.lua: invalid value in header '", fourcc, "': ", param ) return nil end length = length + 2 command[ length - 1 ] = " " command[ length ] = param end --// body //-- local cmd = _protocol_commands[ msgcmd ] if not cmd then out_put( "adc.lua: command '", msgcmd, "' is unknown or unsupported" ) return nil end --// positional parameters //-- local paramstart = 2 + len -- start of message params in buffer local positionalstart -- start of positional parameters in array "command" local positionalend -- end of positional parameters in array "command" local namedstart -- start of named parameters in array "command" local namedend -- end of named parameters in array "command" local ppregex = cmd.pp len = paramstart + ppregex.len - 1 for i = paramstart, len do local param = _buffer[ i ] if ppregex[ i - paramstart + 1 ]( param ) then length = length + 2 command[ length - 1 ] = " " command[ length ] = param positionalstart = positionalstart or length else out_put( "adc.lua: invalid positional parameter in '", fourcc, "' on position ", i, ": ", param ) return nil end end positionalend = positionalstart and length --// named paramters //-- local noclones = cmd.nonpclones local np = cmd.np for i = len + 1, _eol do local param = _buffer[ i ] local name = string_sub( param, 1, 2 ) or "" local npregex = np[ name ] if npregex then local body = string_sub( param, 3, -1 ) or "" if _clone[ name ] ~= true and _clone[ name ] ~= body then if npregex( body ) then length = length + 3 command[ length - 2 ] = " " command[ length - 1 ] = name command[ length ] = body if noclones then _clone[ name ] = true else _clone[ name ] = body end namedstart = namedstart or length - 1 else out_put( "adc.lua: invalid named parameter in '", fourcc, "': ", body ) return nil end else out_put( "adc.lua: removed clone named parameter in '", fourcc, "': ", body ) end else out_put( "adc.lua: ignored unknown named parameter in '", fourcc, "': ", name ) end end clean( _clone ) namedend = namedstart and length length = length + 1 command[ length ] = "\n" --// create adc command object //-- local contextsend = "[BFDE]" local contextdirect = "[DE]" --// public methods of the object //-- command.length = length command.namedend = namedend command.namedstart = namedstart --// this saves creating closures, but you have to use "self" //-- command.pos = adccmd_pos command.mysid = adccmd_mysid command.getnp = adccmd_getnp command.addnp = adccmd_addnp command.setnp = adccmd_setnp command.fourcc = adccmd_fourcc command.getallnp = adccmd_getallnp command.deletenp = adccmd_deletenp command.hasparam = adccmd_hasparam command.adcstring = adccmd_adcstring command.targetsid = adccmd_targetsid out_put( "adc.lua: parsed '", command:adcstring( ), "'" ) _adccmds[ command ] = fourcc return command, fourcc end ----------------------------------// BEGIN //-- use "setmetatable" ( _adccmds, { __mode = "k" } ) types.add( "adcstr", checkadcstr ) types.add( "adccmd", checkadccmd ) types.add( "adcstring", checkadcstring ) ----------------------------------// PUBLIC INTERFACE //-- return { parse = parse, createid = createid, }