--[[ UPCOMMING ADDITIONS AUDP - advance udp. Ensures packets arrive and handles late packets. P2P - peer to peer (Server to set up initial connection) Relay - offput server load (locally) Threading - Simple threading ~~(UDP/AUDP Only)~~ Thanks to an updated multi library we can thread with ease Priority handling ]] --[[ TODO: Finish stuff for Priority handling ]] function table.merge(t1, t2) for k,v in pairs(t2) do if type(v) == 'table' then if type(t1[k] or false) == 'table' then table.merge(t1[k] or {}, t2[k] or {}) else t1[k] = v end else t1[k] = v end end return t1 end function string.trim(s) local from = s:match"^%s*()" return from > #s and "" or s:match(".*%S", from) end local guid = {} local char = {} for i = 48,57 do char[#char+1]=string.char(i) end for i = 65,90 do char[#char+1]=string.char(i) end for i = 97,122 do char[#char+1]=string.char(i) end local isHyphen = {[9]=1,[14]=1,[19]=1,[24]=1} math.randomseed(os.time()) local multi = require("multi") local socket=require("socket") local http=require("socket.http") local mime=require("mime") --ssl=require("ssl") --https=require("ssl.https") local net={} net.Version={3,0,0} -- This will probably stay this version for quite a while... The modules on the otherhand will be more inconsistant net._VERSION="3.0.0" net.ClientCache = {} net.OnServerCreated=multi:newConnection() net.OnClientCreated=multi:newConnection() net.loadedModules={} net.OnCastedClientInfo=multi:newConnection() net.autoInit=true net.ConnectionDriver = {} net.BroadcastDriver = {} net.generateGUID = function(t) local pass = {} local a=0 local x="" for z = 1,36 do if isHyphen[z] then x='-' else a = math.random(1,#char) x = char[a] end table.insert(pass, x) if t == z then break end end z = nil return tostring(table.concat(pass)) end function net.normalize(input) local enc=mime.b64(input) return enc end function net.denormalize(input) local unenc=mime.unb64(input) return unenc end function net.getLocalIP() local someRandomIP = "192.168.1.122" local someRandomPort = "3102" local mySocket = socket.udp() mySocket:setpeername(someRandomIP,someRandomPort) local dat = (mySocket:getsockname()) mySocket:close() return dat end function net.getExternalIP() local data=http.request("http://whatismyip.host") return data:match("(%d+.%d+.%d+.%d+)") end function net:registerModule(mod,version) if net[mod] then error("Module by the name: "..mod.." has already been registered! Remember some modules are internal and use certain names!") end table.insert(self.loadedModules,mod) net[mod]={} if version then net[mod].Version=version net[mod]._VERSION=version[1].."."..version[2].."."..version[3] else net[mod].Version={1,0,0} net[mod]._VERSION={1,0,0} end return {Version=version,_VERSION=version[1].."."..version[2].."."..version[3]} end function net.getModuleVersion(ext) if not ext then return string.format("%d.%d.%d",net.Version[1],net.Version[2],net.Version[3]) end return string.format("%d.%d.%d",net[ext].Version[1],net[ext].Version[2],net[ext].Version[3]) end function net.resolveID(obj) local num=math.random(10000000,99999999) if obj[tostring(num)] then return net.resolveID(obj) end obj.ids[tostring(num)]=true return tostring(num) end function net.inList(list,dat) for i,v in pairs(list) do if v==dat then return true end end return false end function net.setTrigger(funcW,funcE) multi:newTrigger(func) end net:registerModule("net",net.Version) -- Client broadcast function net:newCastedClient(name) -- connects to the broadcasted server local listen = socket.udp() -- make a new socket listen:setsockname(net.getLocalIP(), 11111) listen:settimeout(0) local timer=multi:newTimer() while true do local data, ip, port = listen:receivefrom() if timer:Get()>3 then error("Timeout! Server by the name: "..name.." has not been found!") end if data then print("found!",data) local n,tp,ip,port=data:match("(%S-)|(%S-)|(%S-):(%d+)") if n:match(name) then print("Found Server!",n,tp,ip,port) if tp=="tcp" then return net:newTCPClient(ip,tonumber(port)) else return net:newClient(ip,tonumber(port)) end end end end end function net:newCastedClients(name) -- connects to the broadcasted server local listen = socket.udp() -- make a new socket listen:setsockname(net.getLocalIP(), 11111) listen:settimeout(0) multi:newTLoop(function(self) local data, ip, port = listen:receivefrom() if data then local n,tp,ip,port=data:match("(%S-)|(%S-)|(%S-):(%d+)") if n:match(name) and not net.ClientCache[n] then local capture = n:match(name) local client = {} if tp=="tcp" then client=net:newTCPClient(ip,tonumber(port)) else client=net:newUDPClient(ip,tonumber(port)) end net.ClientCache[n]=client net.OnCastedClientInfo:Fire(client,n,ip,port) end end end,.1):setName("net.castedTask") end -- UDP Stuff function net:newUDPServer(port,servercode,nonluaServer) local c={} c.udp=assert(socket.udp()) c.udp:settimeout(0) c.udp:setsockname("*", port) c.ips={} c.Type="udp" if port == 0 then _, c.port = c.udp:getsockname() end c.ids={} c.servercode=servercode c.bannedIPs={} c.bannedCIDs={} c.autoNormalization=false function c:setUpdateRate(n) print("Not needed in a udp server!") end function c:banCID(cid) table.insert(self.bannedCIDs,cid) end function c:banIP(ip) table.insert(self.bannedIPs,cid) end c.broad=socket.udp() c.hostip=net.getLocalIP() function c:broadcast(name) table.insert(net.BroadcastDriver,function(loop,dt) self.broad:setoption('broadcast',true) self.broad:sendto(name.."|"..self.Type.."|"..self.hostip..":"..self.port, "255.255.255.255", 11111) self.broad:setoption('broadcast',false) end) end function c:send(ip,data,port,cid) if self.autoNormalization then data=net.normalize(data) end if self.servercode then cid=cid or self:CIDFrom(ip,port) if not self.ips[cid] then print("Can't determine cid from client... sending the client a new one!") local cid=net.resolveID(self) print("Sending unique cid to client: "..cid) self.ips[cid]={ip,port,0,self.servercode==nil} print(ip) self.udp:sendto("I!"..cid,ip,port) if self.servercode then self.udp:sendto("S!",ip,port) end return end if net.inList(self.bannedIPs,ip) or net.inList(self.bannedCIDs,cid) then self.udp:sendto("BANNED CLIENT", ip, port or self.port) elseif self.ips[cid][4] then self.udp:sendto(data, ip, port or self.port) elseif self.ips[cid][4]==false then self.udp:sendto("Make sure your server code is correct!", ip, port) end else self.udp:sendto(data, ip, port or self.port) end end function c:pollClientModules(ip,port) self:send(ip,"L!",port) end function c:CIDFrom(ip,port) for i,v in pairs(self.ips) do if(ip==v[1] and v[2]==port) then return i end end end function c:sendAll(data) for i,v in pairs(self.ips) do self:send(v[1],data,v[2],i) end end function c:sendAllBut(data,cid) for i,v in pairs(self.ips) do if i~=cid then self:send(v[1],data,v[2],i) end end end function c:clientRegistered(cid) return self.ips[cid] end function c:clientLoggedIn(cid) if not self.clientRegistered(cid) then return nil end return self.ips[cid][4] end function c:update() local data,ip,port=self.udp:receivefrom() if net.inList(self.bannedIPs,ip) or net.inList(self.bannedCIDs,cid) then print("We will ignore data from a banned client!") return end if data then if self.autoNormalization then data=net.denormalize(data) end if data:sub(1,4)=="pong" then --print("Recieved pong from: "..data:sub(5,-1)) self.ips[data:sub(5,-1)][3]=os.clock() elseif data:sub(1,2)=="S!" then local cid=self:CIDFrom(ip,port) if data:sub(3,-1)==self.servercode then print("Servercode Accepted: "..self.servercode) if self.ips[cid] then self.ips[cid][4]=true else print("Server can't keep up! CID: "..cid.." has been skipped! Sending new CID to the client!") local cid=net.resolveID(self) print("Sending unique cid to client: "..cid) self.ips[cid]={ip,port,0,self.servercode==nil} print(ip) self.udp:sendto("I!"..cid,ip,port) if self.servercode then self.udp:sendto("S!",ip,port) end end else self.udp:sendto("Make sure your server code is correct!", ip, port) end elseif data:sub(1,2)=="C!" then local hook=(data:sub(11,-1)):match("!(.-)!") self.OnDataRecieved:getConnection(hook):Fire(self,data:sub(11,-1),data:sub(3,10),ip,port) elseif data:sub(1,2)=="E!" then self.ips[data:sub(3,10)]=nil obj.ids[data:sub(3,10)]=false self.OnClientClosed:Fire(self,"Client Closed Connection!",data:sub(3,10),ip,port) elseif data=="I!" then local cid=net.resolveID(self) print("Sending unique cid to client: "..cid) self.ips[cid]={ip,port,os.clock(),self.servercode==nil} print(ip) self.udp:sendto("I!"..cid,ip,port) if self.servercode then self.udp:sendto("S!",ip,port) end self.OnClientConnected:Fire(self,cid,ip,port) elseif data:sub(1,2)=="L!" then cid,cList=data:sub(3,10),data:sub(11,-1) local list={} for m,v in cList:gmatch("(%S-):(%S-)|") do list[m]=v end self.OnClientsModulesList:Fire(list,cid,ip,port) end end for cid,dat in pairs(self.ips) do if not((os.clock()-dat[3])<65) then self.ips[cid]=nil self.OnClientClosed:Fire(self,"Client lost Connection: ping timeout",cid,ip,port) end end end c.OnClientsModulesList=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnClientClosed=multi:newConnection() c.OnClientConnected=multi:newConnection() c.connectiontest=multi:newAlarm(30):setName("net.pingOutTask") c.connectiontest.link=c c.connectiontest:OnRing(function(alarm) alarm.link:sendAll("ping") alarm:Reset() end) table.insert(net.ConnectionDriver,c) net.OnServerCreated:Fire(c) return c end local pingManager = {} function net:newUDPClient(host,port,servercode,nonluaServer) local c={} c.ip=assert(socket.dns.toip(host)) c.udp=assert(socket.udp()) c.udp:settimeout(0) c.udp:setpeername(c.ip, port) c.cid="NIL" c.lastPing=0 c.Type="udp" c.servercode=servercode c.autoReconnect=true c.autoNormalization=false function c:pollPing(n) return not((os.clock()-self.lastPing)<(n or 60)) end function c:send(data) if self.autoNormalization then data=net.normalize(data) end self.udp:send("C!"..self.cid..data) end function c:sendRaw(data) if self.autoNormalization then data=net.normalize(data) end self.udp:send(data) end function c:getCID() if self:IDAssigned() then return self.cid end end function c:close() self:send("E!") end function c:IDAssigned() return self.cid~="NIL" end function c:update() local data=self.udp:receive() if data then if self.autoNormalization then data=net.denormalize(data) end if data:sub(1,2)=="I!" then self.cid=data:sub(3,-1) self.OnClientReady:Fire(self) elseif data=="S!" then self.udp:send("S!"..(self.servercode or "")) elseif data=="L!" then local mods="" local m="" for i=1,#net.loadedModules do m=net.loadedModules[i] mods=mods..m..":"..net.getModuleVersion(m).."|" end self.udp:send("L!"..self.cid..mods) elseif data=="ping" then self.lastPing=os.clock() self.OnPingRecieved:Fire(self) self.udp:send("pong"..self.cid) else local hook=data:match("!(.-)!") self.OnDataRecieved:getConnection(hook):Fire(self,data) end end end function c:reconnect() if not nonluaServer then self.cid="NIL" c.udp:send("I!") end self.pingEvent:Resume() self.OnConnectionRegained:Fire(self) end c.pingEvent=multi:newEvent(function(self) return self.link:pollPing() end) c.pingEvent:OnEvent(function(self) if self.link.autoReconnect then self.link.OnServerNotAvailable:Fire("Connection to server lost: ping timeout! Attempting to reconnect...") self.link.OnClientDisconnected:Fire(self,"closed") self.link:reconnect() else self.link.OnServerNotAvailable:Fire("Connection to server lost: ping timeout!") self.link.OnClientDisconnected:Fire(self,"closed") end self:Pause() end):setName("net.pingInTask") c.pingEvent.link=c c.OnPingRecieved=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnServerNotAvailable=multi:newConnection() c.OnClientReady=multi:newConnection() c.OnClientDisconnected=multi:newConnection() c.OnConnectionRegained=multi:newConnection() c.notConnected=multi:newFunction(function(self) multi:newAlarm(3):OnRing(function(alarm) if self.link:IDAssigned()==false then self.link.OnServerNotAvailable:Fire("Can't connect to the server: no response from server") end alarm:Destroy() end):setName("net.clientTimeout") end) c.notConnected.link=c if not nonluaServer then c.udp:send("I!") end table.insert(net.ConnectionDriver,c) multi.nextStep(function() c.notConnected() end) net.OnClientCreated:Fire(c) return c end --TCP Stuff function net:newTCPClientObject(fd) local c = {} local client c.Type="tcp-ClientObj" c.rMode="*l" c.sMode="*l" function c:packMsg(data) local temp = bin.new() temp:addBlock(#data,self.numspace,"n") temp:addBlock(data) return temp:getData() end function c:enableBinaryMode() self.rMode = "b" self.sMode = "b" end if fd then client=socket.tcp() client:setfd(fd) _,port = client:getsockname() c.handle = client else error("You need to enter a fd in order to be able to create a tcp client object like this!") end function c:setUpdateRate(n) self.updaterRate=n end function c:setReceiveMode(mode) self.rMode=mode end function c:setSendMode(mode) self.rMode=mode end function c:send(data) if self.autoNormalization then data=net.normalize(data) end if self.sMode=="*l" then self.handle:send(data.."\n") elseif self.sMode=="b" then self.handle:send(self:packMsg(data)) else self.handle:send(data) end end multi:newThread("ServerClientHandler",function() while true do thread.skip(1) local data, err, dat, len if self.rMode == "b" then thread.hold(function() dat = client:receive(self.numspace) return dat end) len = bin.new(dat):getBlock("n",self.numspace) data, err = client:receive(len) else data, err = client:receive(self.rMode) end if err=="closed" then for i=1,#self.ips do if self.ips[i]==client then table.remove(self.ips,i) end end self.OnClientClosed:Fire(self,"Client Closed Connection!",client,client,ip) self.links[client]=nil -- lets clean up self:Destroy() end if data then if self.autoNormalization then data=net.denormalize(data) end if net.inList(self.bannedIPs,ip) then print("We will ingore data from a banned client!") return end local hook=data:match("!(.-)!") self.OnDataRecieved:getConnection(hook):Fire(self,data,client,client,ip,self) if data:sub(1,2)=="L!" then cList=data local list={} for m,v in cList:gmatch("(%S-):(%S-)|") do list[m]=v end self.OnClientsModulesList:Fire(list,client,client,ip) end end end end) c.OnClientsModulesList=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnClientClosed=multi:newConnection() c.OnClientConnected=multi:newConnection() return c end function net:newTCPServer(port) local c={} local port = port or 0 c.tcp=assert(socket.bind("*", port)) c.tcp:settimeout(0) c.ip,c.port=c.tcp:getsockname() c.ips={} if port == 0 then _, c.port = c.tcp:getsockname() end c.ids={} c.bannedIPs={} c.Type="tcp" c.rMode="*l" c.sMode="*l" c.updaterRate=1 c.autoNormalization=false c.updates={} c.links={} c.numspace = 4 c.broad=socket.udp() c.hostip=net.getLocalIP() function c:packMsg(data) local temp = bin.new() temp:addBlock(#data,self.numspace,"n") temp:addBlock(data) return temp:getData() end function c:enableBinaryMode() self.rMode = "b" self.sMode = "b" end function c:broadcast(name) table.insert(net.BroadcastDriver,function(loop,dt) self.broad:setoption('broadcast',true) self.broad:sendto(name.."|"..self.Type.."|"..self.hostip..":"..self.port, "255.255.255.255", 11111) self.broad:setoption('broadcast',false) end) end function c:setUpdateRate(n) self.updaterRate=n end function c:setReceiveMode(mode) self.rMode=mode end function c:setSendMode(mode) self.rMode=mode end function c:banCID(cid) print("Function not supported on a tcp server!") end function c:banIP(ip) table.insert(self.bannedIPs,cid) end function c:send(handle,data) if self.autoNormalization then data=net.normalize(data) end if self.sMode=="*l" then handle:send(data.."\n") elseif self.sMode=="b" then handle:send(self:packMsg(data)) else handle:send(data) end end function c:sendAllData(handle,data) if self.autoNormalization then data=net.normalize(data) end handle:send(data) end function c:pollClientModules(ip,port) self:send(ip,"L!",port) end function c:CIDFrom(ip,port) print("Method not supported when using a TCP Server!") return "CIDs in TCP work differently!" end function c:sendAll(data) for i,v in pairs(self.ips) do self:send(v,data) end end function c:sendAllBut(data,cid) for i,v in pairs(self.ips) do if not(cid==i) then self:send(v,data) end end end function c:clientRegistered(cid) return self.ips[cid] end function c:clientLoggedIn(cid) return self.ips[cid] end function c:getUpdater(cid) return self.updates[cid] end function c:update() local client = self.tcp:accept(self.rMode) if not client then return end table.insert(self.ips,client) client:settimeout(0) --client:setoption('tcp-nodelay', true) client:setoption('keepalive', true) ip,port=client:getpeername() if ip and port then print("Got connection from: ",ip,port) -- local updater=multi:newUpdater(skip):setName("net.tcpClientObj") -- self.updates[client]=updater self.OnClientConnected:Fire(self,client,client,ip) --updater:OnUpdate(function(self) multi:newThread("ServerClientHandler",function() while true do thread.skip(1) local data, err, dat, len if self.rMode == "b" then thread.hold(function() dat = client:receive(self.numspace) return dat end) len = bin.new(dat):getBlock("n",self.numspace) data, err = client:receive(len) else data, err = client:receive(self.rMode) end if err=="closed" then for i=1,#self.ips do if self.ips[i]==client then table.remove(self.ips,i) end end self.OnClientClosed:Fire(self,"Client Closed Connection!",client,client,ip) self.links[client]=nil -- lets clean up self:Destroy() thread.kill() end if data then if self.autoNormalization then data=net.denormalize(data) end if net.inList(self.bannedIPs,ip) then print("We will ingore data from a banned client!") return end local hook=data:match("!(.-)!") self.OnDataRecieved:getConnection(hook):Fire(self,data,client,client,ip,self) if data:sub(1,2)=="L!" then cList=data local list={} for m,v in cList:gmatch("(%S-):(%S-)|") do list[m]=v end self.OnClientsModulesList:Fire(list,client,client,ip) end end end end) -- updater:SetSkip(self.updaterRate) -- updater.client=client -- updater.Link=self -- function updater:setReceiveMode(mode) -- self.rMode=mode -- end -- self.links[client]=updater end end c.OnClientsModulesList=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnClientClosed=multi:newConnection() c.OnClientConnected=multi:newConnection() table.insert(net.ConnectionDriver,c) net.OnServerCreated:Fire(c) return c end function net:newTCPClient(host,port) local c={} c.ip=assert(socket.dns.toip(host)) c.tcp=socket.connect(c.ip,port) if not c.tcp then print("Can't connect to the server: no response from server") return false end c.tcp:settimeout(0) --c.tcp:setoption('tcp-nodelay', true) c.tcp:setoption('keepalive', true) c.Type="tcp" c.autoReconnect=true c.rMode="*l" c.sMode="*l" c.autoNormalization=false c.numspace = 4 function c:enableBinaryMode() self.rMode = "b" self.sMode = "b" end function c:setReceiveMode(mode) self.rMode=mode end function c:setSendMode(mode) self.sMode=mode end function c:packMsg(data) local temp = bin.new() temp:addBlock(#data,self.numspace) temp:addBlock(data) return temp:getData() end function c:send(data) if self.autoNormalization then data=net.normalize(data) end if self.sMode=="*l" then ind,err=self.tcp:send(data.."\n") elseif self.sMode=="b" then ind,err=self.tcp:send(self:packMsg(data)) else ind,err=self.tcp:send(data) end if err=="closed" then self.OnClientDisconnected:Fire(self,err) elseif err=="timeout" then self.OnClientDisconnected:Fire(self,err) elseif err then print(err) end end function c:sendRaw(data) if self.autoNormalization then data=net.normalize(data) end self.tcp:send(data) end function c:getCID() return "No Cid on a tcp client!" end function c:close() self.tcp:close() end function c:IDAssigned() return true end function c:update() if not self.tcp then return end local data,err,dat if self.rMode == "b" then thread.hold(function() dat = self.tcp:receive(self.numspace) return dat end) len = bin.new(dat):getBlock("n",self.numspace) data, err = self.tcp:receive(len) else data, err = self.tcp:receive() end if err=="closed" then self.OnClientDisconnected:Fire(self,err) elseif err=="timeout" then self.OnClientDisconnected:Fire(self,err) elseif err then print(err) end if data then if self.autoNormalization then data=net.denormalize(data) end local hook=data:match("!(.-)!") self.OnDataRecieved:getConnection(hook):Fire(self,data) end end function c:reconnect() multi:newFunction(function(func) self.tcp=socket.connect(self.ip,self.port) if self.tcp==nil then print("Can't connect to the server: No response from server!") multi:newAlarm(3):OnRing(function(alarm) self:reconnect() alarm:Destroy() return end):setName("net.timeoutTask") end self.OnConnectionRegained:Fire(self) self.tcp:settimeout(0) --self.tcp:setoption('tcp-nodelay', true) self.tcp:setoption('keepalive', true) end) end c.event=multi:newEvent(function(event) return event.link:IDAssigned() end):OnEvent(function(event) event.link.OnClientReady:Fire(event.link) event:Destroy() end) c.event:setName("net.handshakeTask") c.event.link=c c.OnClientReady=multi:newConnection() c.OnClientDisconnected=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnConnectionRegained=multi:newConnection() table.insert(net.ConnectionDriver,c) net.OnClientCreated:Fire(c) return c end net.timer = multi:newTimer():Start() multi:newThread("ClientServerHandler",function() while true do thread.skip() for i=1,#net.ConnectionDriver do thread.skip() net.ConnectionDriver[i]:update() end if net.timer:Get()>=1 then for i=1,#net.BroadcastDriver do net.BroadcastDriver[i]() end net.timer:Reset() end end end) return net