/*** The `socket` module. Almost like luasocket, but for the TCP part only. The UDP part is only without connection. @module ctr.socket @usage local socket = require("ctr.socket") */ #include <3ds.h> #include <3ds/types.h> #include <3ds/services/soc.h> #include #include #include #include #include #include #include #include #include #include #include typedef struct { int socket; struct sockaddr_in addr; struct hostent *host; // only user for client sockets } socket_userdata; /*** Initialize the socket module @function init @tparam[opt=0x100000] number buffer size (in bytes), must be a multiple of 0x1000 */ static int socket_init(lua_State *L) { u32 size = luaL_optinteger(L, 1, 0x100000); Result ret = SOC_Initialize((u32*)memalign(0x1000, size), size); if (ret) { lua_pushboolean(L, false); lua_pushinteger(L, ret); return 2; } lua_pushboolean(L, true); return 1; } /*** Disable the socket module. Must be called before exiting ctrµLua. @function shutdown */ static int socket_shutdown(lua_State *L) { SOC_Shutdown(); return 0; } /*** Return a TCP socket. @function tcp @treturn TCPMaster TCP socket */ static int socket_tcp(lua_State *L) { socket_userdata *userdata = lua_newuserdata(L, sizeof(*userdata)); luaL_getmetatable(L, "LSocket"); lua_setmetatable(L, -2); userdata->socket = socket(AF_INET, SOCK_STREAM, 0); if (userdata->socket < 0) { lua_pushnil(L); lua_pushstring(L, "Failed to create a TCP socket"); return 2; } userdata->addr.sin_family = AF_INET; return 1; } /*** Return an UDP socket. @function udp @treturn UDPMaster UDP socket */ static int socket_udp(lua_State *L) { socket_userdata *userdata = lua_newuserdata(L, sizeof(*userdata)); luaL_getmetatable(L, "LSocket"); lua_setmetatable(L, -2); userdata->socket = socket(AF_INET, SOCK_DGRAM, 0); if (userdata->socket < 0) { lua_pushnil(L); lua_pushstring(L, "Failed to create a TCP socket"); return 2; } userdata->addr.sin_family = AF_INET; return 1; } /*** All sockets @section sockets */ /*** Bind a socket. The socket object become a socketServer object. @function :bind @tparam number port the port to bind the socket on. */ static int socket_bind(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); int port = luaL_checkinteger(L, 2); userdata->addr.sin_addr.s_addr = htonl(INADDR_ANY); userdata->addr.sin_port = htons(port); bind(userdata->socket, (struct sockaddr*)&userdata->addr, sizeof(userdata->addr)); return 0; } /*** Close an existing socket. @function :close */ static int socket_close(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); closesocket(userdata->socket); return 0; } /*** TCP Sockets @section TCP */ /*** Accept a connection on a server. @function :accept @treturn TCPClient tcp client object, or nil. */ static int socket_accept(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); socket_userdata *client = lua_newuserdata(L, sizeof(*client)); luaL_getmetatable(L, "LSocket"); lua_setmetatable(L, -2); socklen_t addrSize = sizeof(client->addr); client->socket = accept(userdata->socket, (struct sockaddr*)&client->addr, &addrSize); if (client->socket < 0) { lua_pushnil(L); return 1; } return 1; } /*** Connect a socket to a server. The TCP object becomes a TCPClient object. @function :connect @tparam string host address of the host @tparam number port port of the server @treturn[1] boolean true if success @treturn[2] boolean false if failed @treturn[2] string error string */ static int socket_connect(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); char *addr = (char*)luaL_checkstring(L, 2); int port = luaL_checkinteger(L, 3); userdata->host = gethostbyname(addr); if (userdata->host == NULL) { lua_pushnil(L); lua_pushstring(L, "No such host"); return 2; } userdata->addr.sin_port = htons(port); bcopy((char*)userdata->host->h_addr, (char*)&userdata->addr.sin_addr.s_addr, userdata->host->h_length); if (connect(userdata->socket, (const struct sockaddr*)&userdata->addr, sizeof(userdata->addr)) < 0) { lua_pushnil(L); lua_pushstring(L, "Connection failed"); return 2; } lua_pushboolean(L, 1); return 1; } /*** Open the socket for connections. @function :listen @tparam[opt=16] number max maximum number of simultaneous connections */ static int socket_listen(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); int max = luaL_optinteger(L, 2, 16); listen(userdata->socket, max); return 0; } /*** Receive some data from the socket. If no data is avaible, it returns an empty string (non-blocking). @function :receive @tparam[opt="l"] number/string size amount of bytes to receive; or "a" to receive everything, "l" to receive the next line, skipping the end of line, "L" to receive the next line, keeping the end of line. @treturn string data */ static int socket_receive(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); int count = 0; int flags = 0; if (lua_isnumber(L, 2)) { count = luaL_checkinteger(L, 2); } else { const char *p = luaL_optstring(L, 2, "l"); if (*p == 'a') { count = SIZE_MAX/2; } else if (*p == 'l') { luaL_Buffer b; luaL_buffinit(L, &b); char buff; while (recv(userdata->socket, &buff, 1, flags) > 0 && buff != '\n') luaL_addchar(&b, buff); luaL_pushresult(&b); return 1; } else if (*p == 'L') { luaL_Buffer b; luaL_buffinit(L, &b); char buff; while (buff != '\n' && recv(userdata->socket, &buff, 1, flags) > 0) luaL_addchar(&b, buff); luaL_pushresult(&b); return 1; } else { return luaL_argerror(L, 2, "invalid format"); } } char *buff = malloc(count+1); int len = recv(userdata->socket, buff, count, flags); *(buff+len) = 0x0; // text end lua_pushstring(L, buff); return 1; } /*** Send some data over the TCP socket. @function :send @tparam string data data to send @treturn number amount of data sent */ static int socket_send(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); size_t size = 0; char *data = (char*)luaL_checklstring(L, 2, &size); size_t sent = send(userdata->socket, data, size, 0); lua_pushinteger(L, sent); return 1; } /*** UDP sockets @section UDP */ /*** Receive some data from a server. @function :receivefrom @tparam number count amount of data to receive @tparam string host host name @tparam number port port @treturn string data */ static int socket_receivefrom(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); int count = luaL_checkinteger(L, 2); size_t namesize = 0; char *hostname = (char*)luaL_optlstring(L, 3, NULL, &namesize); int port = luaL_optinteger(L, 4, 0); struct sockaddr_in from = {0}; if (hostname != NULL) { // For a server struct hostent *hostinfo = gethostbyname(hostname); if (hostinfo == NULL) { lua_pushnil(L); return 1; } from.sin_addr = *(struct in_addr*)hostinfo->h_addr; from.sin_port = htons(port); from.sin_family = AF_INET; } char* buffer = malloc(count+1); int n = recvfrom(userdata->socket, buffer, count, 0, (struct sockaddr*)&from, NULL); *(buffer+n) = 0x0; lua_pushstring(L, buffer); if (hostname != NULL) { return 1; } else { lua_pushstring(L, inet_ntoa(from.sin_addr)); lua_pushinteger(L, ntohs(from.sin_port)); return 3; } } /*** Send some data to a server. @function :sendto @tparam string data data to send @tparam string host host name @tparam number port port */ static int socket_sendto(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); size_t datasize = 0; char *data = (char*)luaL_checklstring(L, 2, &datasize); size_t namesize = 0; char *hostname = (char*)luaL_checklstring(L, 3, &namesize); int port = luaL_checkinteger(L, 4); struct hostent *hostinfo = gethostbyname(hostname); if (hostinfo == NULL) { lua_pushnil(L); return 1; } struct sockaddr_in to = {0}; to.sin_addr = *(struct in_addr*)hostinfo->h_addr; to.sin_port = htons(port); to.sin_family = AF_INET; sendto(userdata->socket, data, datasize, 0, (struct sockaddr*)&to, sizeof(to)); return 0; } // module functions static const struct luaL_Reg socket_functions[] = { {"init", socket_init }, {"shutdown", socket_shutdown}, {"tcp", socket_tcp }, {"udp", socket_udp }, {NULL, NULL} }; // object static const struct luaL_Reg socket_methods[] = { {"accept", socket_accept }, {"bind", socket_bind }, {"close", socket_close }, {"__gc", socket_close }, {"connect", socket_connect }, {"listen", socket_listen }, {"receive", socket_receive }, {"receivefrom", socket_receivefrom}, {"send", socket_send }, {"sendto", socket_sendto }, {NULL, NULL} }; int luaopen_socket_lib(lua_State *L) { luaL_newmetatable(L, "LSocket"); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); luaL_setfuncs(L, socket_methods, 0); luaL_newlib(L, socket_functions); return 1; } void load_socket_lib(lua_State *L) { luaL_requiref(L, "ctr.socket", luaopen_socket_lib, false); }