A lua interface for csockets in a spirit of luasocket
							parent
							
								
									0b4d32d0e6
								
							
						
					
					
						commit
						d9c50d677f
					
				| @ -0,0 +1,67 @@ | |||||||
|  | local _ENV = mkmodule('plugins.luasocket') | ||||||
|  | local _funcs={} | ||||||
|  | for k,v in pairs(_ENV) do | ||||||
|  |     if type(v)=="function" then | ||||||
|  |         _funcs[k]=v | ||||||
|  |         _ENV[k]=nil | ||||||
|  |     end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | local socket=defclass(socket) | ||||||
|  | socket.ATTRS={ | ||||||
|  |     server_id=-1, | ||||||
|  |     client_id=-1, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function socket:close(  ) | ||||||
|  |     if self.client_id==-1 then | ||||||
|  |         _funcs.lua_server_close(self.server_id) | ||||||
|  |     else | ||||||
|  |         _funcs.lua_client_close(self.server_id,self.client_id) | ||||||
|  |     end | ||||||
|  | end | ||||||
|  | function socket:setTimeout( sec,msec ) | ||||||
|  |     msec=msec or 0 | ||||||
|  |     _funcs.lua_socket_set_timeout(self.server_id,self.client_id,sec,msec) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | local client=defclass(client,socket) | ||||||
|  | function client:receive( pattern ) | ||||||
|  |     local pattern=pattern or "*l" | ||||||
|  |     local bytes=-1 | ||||||
|  |     if type(pattern)== number then | ||||||
|  |         bytes=pattern | ||||||
|  |     end | ||||||
|  |     local ret=_funcs.lua_client_receive(self.server_id,self.client_id,bytes,pattern,false) | ||||||
|  |     if ret=="" then | ||||||
|  |         return | ||||||
|  |     else | ||||||
|  |         return ret | ||||||
|  |     end | ||||||
|  | end | ||||||
|  | function client:send( data ) | ||||||
|  |     _funcs.lua_client_send(self.server_id,self.client_id,data) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | local server=defclass(server,socket) | ||||||
|  | function server:accept() | ||||||
|  |     local id=_funcs.lua_server_accept(self.server_id,false) | ||||||
|  |     if id~=nil then | ||||||
|  |         return client{server_id=self.server_id,client_id=id} | ||||||
|  |     else | ||||||
|  |         return  | ||||||
|  |     end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | tcp={} | ||||||
|  | function tcp:bind( address,port ) | ||||||
|  |     local id=_funcs.lua_socket_bind(address,port) | ||||||
|  |     return server{server_id=id} | ||||||
|  | end | ||||||
|  | function tcp:connect( address,port ) | ||||||
|  |     local id=_funcs.lua_socket_connect(address,port) | ||||||
|  |     return client{client_id=id} | ||||||
|  | end | ||||||
|  | --TODO garbage collect stuff | ||||||
|  | return _ENV | ||||||
| @ -0,0 +1,352 @@ | |||||||
|  | #include "Core.h" | ||||||
|  | #include "Console.h" | ||||||
|  | #include "Export.h" | ||||||
|  | #include "PluginManager.h" | ||||||
|  | #include "DataDefs.h" | ||||||
|  | 
 | ||||||
|  | #include <vector> | ||||||
|  | #include <string> | ||||||
|  | #include <map> | ||||||
|  | #include <memory> | ||||||
|  | #include <PassiveSocket.h> | ||||||
|  | #include <ActiveSocket.h> | ||||||
|  | #include "MiscUtils.h" | ||||||
|  | #include "LuaTools.h" | ||||||
|  | #include "DataFuncs.h" | ||||||
|  | #include <stdexcept> //todo convert errors to lua-errors and co. Then remove this | ||||||
|  | 
 | ||||||
|  | using namespace DFHack; | ||||||
|  | using namespace df::enums; | ||||||
|  | struct server | ||||||
|  | { | ||||||
|  |     CPassiveSocket *socket; | ||||||
|  |     std::map<int,CActiveSocket*> clients; | ||||||
|  |     int last_client_id; | ||||||
|  |     void close(); | ||||||
|  | }; | ||||||
|  | std::map<int,server> servers; | ||||||
|  | typedef std::map<int,CActiveSocket*> clients_map; | ||||||
|  | clients_map clients; //free clients, i.e. non-server spawned clients
 | ||||||
|  | DFHACK_PLUGIN("luasocket"); | ||||||
|  | 
 | ||||||
|  | // The error messages are taken from the clsocket source code
 | ||||||
|  | const char * translate_socket_error(CSimpleSocket::CSocketError err) { | ||||||
|  |     switch (err) { | ||||||
|  |         case CSimpleSocket::SocketError: | ||||||
|  |             return "Generic socket error translates to error below."; | ||||||
|  |         case CSimpleSocket::SocketSuccess: | ||||||
|  |             return "No socket error."; | ||||||
|  |         case CSimpleSocket::SocketInvalidSocket: | ||||||
|  |             return "Invalid socket handle."; | ||||||
|  |         case CSimpleSocket::SocketInvalidAddress: | ||||||
|  |             return "Invalid destination address specified."; | ||||||
|  |         case CSimpleSocket::SocketInvalidPort: | ||||||
|  |             return "Invalid destination port specified."; | ||||||
|  |         case CSimpleSocket::SocketConnectionRefused: | ||||||
|  |             return "No server is listening at remote address."; | ||||||
|  |         case CSimpleSocket::SocketTimedout: | ||||||
|  |             return "Timed out while attempting operation."; | ||||||
|  |         case CSimpleSocket::SocketEwouldblock: | ||||||
|  |             return "Operation would block if socket were blocking."; | ||||||
|  |         case CSimpleSocket::SocketNotconnected: | ||||||
|  |             return "Currently not connected."; | ||||||
|  |         case CSimpleSocket::SocketEinprogress: | ||||||
|  |             return "Socket is non-blocking and the connection cannot be completed immediately"; | ||||||
|  |         case CSimpleSocket::SocketInterrupted: | ||||||
|  |             return "Call was interrupted by a signal that was caught before a valid connection arrived."; | ||||||
|  |         case CSimpleSocket::SocketConnectionAborted: | ||||||
|  |             return "The connection has been aborted."; | ||||||
|  |         case CSimpleSocket::SocketProtocolError: | ||||||
|  |             return "Invalid protocol for operation."; | ||||||
|  |         case CSimpleSocket::SocketFirewallError: | ||||||
|  |             return "Firewall rules forbid connection."; | ||||||
|  |         case CSimpleSocket::SocketInvalidSocketBuffer: | ||||||
|  |             return "The receive buffer point outside the process's address space."; | ||||||
|  |         case CSimpleSocket::SocketConnectionReset: | ||||||
|  |             return "Connection was forcibly closed by the remote host."; | ||||||
|  |         case CSimpleSocket::SocketAddressInUse: | ||||||
|  |             return "Address already in use."; | ||||||
|  |         case CSimpleSocket::SocketInvalidPointer: | ||||||
|  |             return "Pointer type supplied as argument is invalid."; | ||||||
|  |         case CSimpleSocket::SocketEunknown: | ||||||
|  |             return "Unknown error please report to mark@carrierlabs.com"; | ||||||
|  |         default: | ||||||
|  |             return "No such CSimpleSocket error"; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | void server::close() | ||||||
|  | { | ||||||
|  |     for(auto it=clients.begin();it!=clients.end();it++) | ||||||
|  |     { | ||||||
|  |         CActiveSocket* sock=it->second; | ||||||
|  |         sock->Close(); | ||||||
|  |         delete sock; | ||||||
|  |     } | ||||||
|  |     clients.clear(); | ||||||
|  |     socket->Close(); | ||||||
|  |     delete socket; | ||||||
|  | } | ||||||
|  | std::pair<CActiveSocket*,clients_map*> get_client(int server_id,int client_id) | ||||||
|  | { | ||||||
|  |     std::map<int,CActiveSocket*>* target=&clients; | ||||||
|  |     if(server_id>0) | ||||||
|  |     { | ||||||
|  |         if(servers.count(server_id)==0) | ||||||
|  |         { | ||||||
|  |             throw std::runtime_error("Server with this id does not exist"); | ||||||
|  |         } | ||||||
|  |         server &cur_server=servers[server_id]; | ||||||
|  |         target=&cur_server.clients; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(target->count(client_id)==0) | ||||||
|  |     { | ||||||
|  |         throw std::runtime_error("Client does with this id not exist"); | ||||||
|  |     } | ||||||
|  |     CActiveSocket *sock=(*target)[client_id]; | ||||||
|  |     return std::make_pair(sock,target); | ||||||
|  | } | ||||||
|  | void handle_error(CSimpleSocket::CSocketError err,bool skip_timeout=true) | ||||||
|  | { | ||||||
|  |     if(err==CSimpleSocket::SocketSuccess) | ||||||
|  |         return; | ||||||
|  |     if(err==CSimpleSocket::SocketTimedout && skip_timeout) | ||||||
|  |         return; | ||||||
|  |     throw std::runtime_error(translate_socket_error(err)); | ||||||
|  | } | ||||||
|  | static int lua_socket_bind(std::string ip,int port) | ||||||
|  | { | ||||||
|  |     static int server_id=0; | ||||||
|  |     CPassiveSocket* sock=new CPassiveSocket; | ||||||
|  |     if(!sock->Initialize()) | ||||||
|  |     { | ||||||
|  |         CSimpleSocket::CSocketError err=sock->GetSocketError(); | ||||||
|  |         delete sock; | ||||||
|  |         handle_error(err,false); | ||||||
|  |     } | ||||||
|  |     sock->SetBlocking(); | ||||||
|  |     if(!sock->Listen((uint8_t*)ip.c_str(),port)) | ||||||
|  |     { | ||||||
|  |         handle_error(sock->GetSocketError(),false); | ||||||
|  |     } | ||||||
|  |     server_id++; | ||||||
|  |     server& cur_server=servers[server_id]; | ||||||
|  |     cur_server.socket=sock; | ||||||
|  |     cur_server.last_client_id=0; | ||||||
|  |     return server_id; | ||||||
|  | } | ||||||
|  | static int lua_server_accept(int id,bool fail_on_timeout) | ||||||
|  | { | ||||||
|  |     if(servers.count(id)==0) | ||||||
|  |     { | ||||||
|  |         throw std::runtime_error("Server not bound"); | ||||||
|  |     } | ||||||
|  |     server &cur_server=servers[id]; | ||||||
|  |     CActiveSocket* sock=cur_server.socket->Accept(); | ||||||
|  |     if(!sock) | ||||||
|  |     { | ||||||
|  |         handle_error(sock->GetSocketError(),!fail_on_timeout); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |         cur_server.last_client_id++; | ||||||
|  |         cur_server.clients[cur_server.last_client_id]=sock; | ||||||
|  |         return cur_server.last_client_id; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | static void lua_client_close(int server_id,int client_id) | ||||||
|  | { | ||||||
|  |     auto info=get_client(server_id,client_id); | ||||||
|  | 
 | ||||||
|  |     CActiveSocket *sock=info.first; | ||||||
|  |     std::map<int,CActiveSocket*>* target=info.second; | ||||||
|  | 
 | ||||||
|  |     target->erase(client_id); | ||||||
|  |     CSimpleSocket::CSocketError err=CSimpleSocket::SocketSuccess; | ||||||
|  |     if(!sock->Close()) | ||||||
|  |         err=sock->GetSocketError(); | ||||||
|  |     delete sock; | ||||||
|  |     if(err!=CSimpleSocket::SocketSuccess) | ||||||
|  |     { | ||||||
|  |         throw std::runtime_error(translate_socket_error(err)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | static void lua_server_close(int server_id) | ||||||
|  | { | ||||||
|  |     if(servers.count(server_id)==0) | ||||||
|  |     { | ||||||
|  |         throw std::runtime_error("Server with this id does not exist"); | ||||||
|  |     } | ||||||
|  |     server &cur_server=servers[server_id]; | ||||||
|  |     try{ | ||||||
|  |         cur_server.close(); | ||||||
|  |     } | ||||||
|  |     catch(...) | ||||||
|  |     { | ||||||
|  |         servers.erase(server_id); | ||||||
|  |         throw; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | static std::string lua_client_receive(int server_id,int client_id,int bytes,std::string pattern,bool fail_on_timeout) | ||||||
|  | { | ||||||
|  |     auto info=get_client(server_id,client_id); | ||||||
|  |     CActiveSocket *sock=info.first; | ||||||
|  |     if(bytes>0) | ||||||
|  |     { | ||||||
|  |         if(sock->Receive(bytes)<=0) | ||||||
|  |         { | ||||||
|  |             throw std::runtime_error(translate_socket_error(sock->GetSocketError())); | ||||||
|  |         } | ||||||
|  |         return std::string((char*)sock->GetData(),bytes); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |         std::string ret; | ||||||
|  |         if(pattern=="*a") //??
 | ||||||
|  |         { | ||||||
|  |             while(true) | ||||||
|  |             { | ||||||
|  |                 int received=sock->Receive(1); | ||||||
|  |                 if(received<0) | ||||||
|  |                 { | ||||||
|  |                     handle_error(sock->GetSocketError(),!fail_on_timeout); | ||||||
|  |                     return "";//maybe return partial string?
 | ||||||
|  |                 } | ||||||
|  |                 else if(received==0) | ||||||
|  |                 { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 ret+=(char)*sock->GetData(); | ||||||
|  |             } | ||||||
|  |             return ret; | ||||||
|  |         } | ||||||
|  |         else if (pattern=="" || pattern=="*l") | ||||||
|  |         { | ||||||
|  |             while(true) | ||||||
|  |             { | ||||||
|  |                  | ||||||
|  |                 if(sock->Receive(1)<=0) | ||||||
|  |                 { | ||||||
|  |                     handle_error(sock->GetSocketError(),!fail_on_timeout); | ||||||
|  |                     return "";//maybe return partial string?
 | ||||||
|  |                 } | ||||||
|  |                 char rec=(char)*sock->GetData(); | ||||||
|  |                 if(rec=='\n') | ||||||
|  |                     break; | ||||||
|  |                 ret+=rec; | ||||||
|  |             } | ||||||
|  |             return ret; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             throw std::runtime_error("Unsupported receive pattern"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | static void lua_client_send(int server_id,int client_id,std::string data) | ||||||
|  | { | ||||||
|  |     if(data.size()==0) | ||||||
|  |         return; | ||||||
|  |     std::map<int,CActiveSocket*>* target=&clients; | ||||||
|  |     if(server_id>0) | ||||||
|  |     { | ||||||
|  |         if(servers.count(server_id)==0) | ||||||
|  |         { | ||||||
|  |             throw std::runtime_error("Server with this id does not exist"); | ||||||
|  |         } | ||||||
|  |         server &cur_server=servers[server_id]; | ||||||
|  |         target=&cur_server.clients; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(target->count(client_id)==0) | ||||||
|  |     { | ||||||
|  |         throw std::runtime_error("Client does with this id not exist"); | ||||||
|  |     } | ||||||
|  |     CActiveSocket *sock=(*target)[client_id]; | ||||||
|  |     if(sock->Send((const uint8_t*)data.c_str(),data.size())!=data.size()) | ||||||
|  |     { | ||||||
|  |         throw std::runtime_error(translate_socket_error(sock->GetSocketError())); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | static int lua_socket_connect(std::string ip,int port) | ||||||
|  | { | ||||||
|  |     static int last_client_id=0; | ||||||
|  |     CActiveSocket* sock=new CActiveSocket; | ||||||
|  |     if(!sock->Initialize()) | ||||||
|  |     { | ||||||
|  |         CSimpleSocket::CSocketError err=sock->GetSocketError(); | ||||||
|  |         delete sock; | ||||||
|  |         throw std::runtime_error(translate_socket_error(err)); | ||||||
|  |     } | ||||||
|  |     if(!sock->Open((const uint8_t*)ip.c_str(),port)) | ||||||
|  |     { | ||||||
|  |         CSimpleSocket::CSocketError err=sock->GetSocketError(); | ||||||
|  |         delete sock; | ||||||
|  |         throw std::runtime_error(translate_socket_error(err)); | ||||||
|  |     } | ||||||
|  |     last_client_id++; | ||||||
|  |     clients[last_client_id]=sock; | ||||||
|  |     return last_client_id; | ||||||
|  | } | ||||||
|  | static void lua_socket_set_timeout(int server_id,int client_id,int32_t sec,int32_t msec) | ||||||
|  | { | ||||||
|  |     std::map<int,CActiveSocket*>* target=&clients; | ||||||
|  |     if(server_id>0) | ||||||
|  |     { | ||||||
|  |         if(servers.count(server_id)==0) | ||||||
|  |         { | ||||||
|  |             throw std::runtime_error("Server with this id does not exist"); | ||||||
|  |         } | ||||||
|  |         server &cur_server=servers[server_id]; | ||||||
|  |         if(client_id==-1) | ||||||
|  |         { | ||||||
|  |             cur_server.socket->SetConnectTimeout(sec,msec); | ||||||
|  |             cur_server.socket->SetReceiveTimeout(sec,msec); | ||||||
|  |             cur_server.socket->SetSendTimeout(sec,msec); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         target=&cur_server.clients; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(target->count(client_id)==0) | ||||||
|  |     { | ||||||
|  |         throw std::runtime_error("Client does with this id not exist"); | ||||||
|  |     } | ||||||
|  |     CActiveSocket *sock=(*target)[client_id]; | ||||||
|  |     sock->SetConnectTimeout(sec,msec); | ||||||
|  |     sock->SetReceiveTimeout(sec,msec); | ||||||
|  |     sock->SetSendTimeout(sec,msec); | ||||||
|  | } | ||||||
|  | DFHACK_PLUGIN_LUA_FUNCTIONS { | ||||||
|  |     DFHACK_LUA_FUNCTION(lua_socket_bind), //spawn a server
 | ||||||
|  |     DFHACK_LUA_FUNCTION(lua_socket_connect),//spawn a client (i.e. connection)
 | ||||||
|  |     DFHACK_LUA_FUNCTION(lua_socket_set_timeout), | ||||||
|  |     DFHACK_LUA_FUNCTION(lua_server_accept), | ||||||
|  |     DFHACK_LUA_FUNCTION(lua_server_close), | ||||||
|  |     DFHACK_LUA_FUNCTION(lua_client_close), | ||||||
|  |     DFHACK_LUA_FUNCTION(lua_client_send), | ||||||
|  |     DFHACK_LUA_FUNCTION(lua_client_receive), | ||||||
|  |     DFHACK_LUA_END | ||||||
|  | }; | ||||||
|  | DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) | ||||||
|  | { | ||||||
|  |      | ||||||
|  |     return CR_OK; | ||||||
|  | } | ||||||
|  | DFhackCExport command_result plugin_shutdown ( color_ostream &out ) | ||||||
|  | { | ||||||
|  |     for(auto it=clients.begin();it!=clients.end();it++) | ||||||
|  |     { | ||||||
|  |         CActiveSocket* sock=it->second; | ||||||
|  |         sock->Close(); | ||||||
|  |         delete sock; | ||||||
|  |     } | ||||||
|  |     clients.clear(); | ||||||
|  |     for(auto it=servers.begin();it!=servers.end();it++) | ||||||
|  |     { | ||||||
|  |         it->second.close(); | ||||||
|  |     } | ||||||
|  |     servers.clear(); | ||||||
|  |     return CR_OK; | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue