mirror of https://github.com/mgba-emu/mgba.git
Scripting: add socket bindings
This commit is contained in:
parent
a11b103a9c
commit
d852c7c8f0
|
@ -10,15 +10,12 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/core/log.h>
|
||||
#ifdef USE_DEBUGGERS
|
||||
#include <mgba/debugger/debugger.h>
|
||||
#endif
|
||||
#include <mgba/script/macros.h>
|
||||
#include <mgba/script/types.h>
|
||||
|
||||
mLOG_DECLARE_CATEGORY(SCRIPT);
|
||||
|
||||
struct mCore;
|
||||
struct mScriptTextBuffer;
|
||||
mSCRIPT_DECLARE_STRUCT(mCore);
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef M_SCRIPT_SOCKET_H
|
||||
#define M_SCRIPT_SOCKET_H
|
||||
|
||||
enum mSocketErrorCode {
|
||||
mSCRIPT_SOCKERR_UNKNOWN_ERROR = -1,
|
||||
mSCRIPT_SOCKERR_OK = 0,
|
||||
mSCRIPT_SOCKERR_AGAIN,
|
||||
mSCRIPT_SOCKERR_ADDRESS_IN_USE,
|
||||
mSCRIPT_SOCKERR_CONNECTION_REFUSED,
|
||||
mSCRIPT_SOCKERR_DENIED,
|
||||
mSCRIPT_SOCKERR_FAILED,
|
||||
mSCRIPT_SOCKERR_NETWORK_UNREACHABLE,
|
||||
mSCRIPT_SOCKERR_NOT_FOUND,
|
||||
mSCRIPT_SOCKERR_NO_DATA,
|
||||
mSCRIPT_SOCKERR_OUT_OF_MEMORY,
|
||||
mSCRIPT_SOCKERR_TIMEOUT,
|
||||
mSCRIPT_SOCKERR_UNSUPPORTED,
|
||||
};
|
||||
|
||||
#endif
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/script/types.h>
|
||||
#include <mgba-util/table.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
@ -18,6 +19,8 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_CONSTANT_PAIR(NS, CONST) { #CONST, mScriptValueCreateFromSInt(NS ## _ ## CONST) }
|
||||
#define mSCRIPT_KV_SENTINEL { NULL, NULL }
|
||||
|
||||
mLOG_DECLARE_CATEGORY(SCRIPT);
|
||||
|
||||
struct mScriptFrame;
|
||||
struct mScriptFunction;
|
||||
struct mScriptEngineContext;
|
||||
|
@ -83,6 +86,7 @@ struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct
|
|||
void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref);
|
||||
|
||||
void mScriptContextAttachStdlib(struct mScriptContext* context);
|
||||
void mScriptContextAttachSocket(struct mScriptContext* context);
|
||||
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants);
|
||||
void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value);
|
||||
|
||||
|
|
|
@ -113,6 +113,7 @@ void ScriptingController::runCode(const QString& code) {
|
|||
void ScriptingController::init() {
|
||||
mScriptContextInit(&m_scriptContext);
|
||||
mScriptContextAttachStdlib(&m_scriptContext);
|
||||
mScriptContextAttachSocket(&m_scriptContext);
|
||||
mScriptContextRegisterEngines(&m_scriptContext);
|
||||
|
||||
mScriptContextAttachLogger(&m_scriptContext, &m_logger);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
include(ExportDirectory)
|
||||
set(SOURCE_FILES
|
||||
context.c
|
||||
socket.c
|
||||
stdlib.c
|
||||
types.c)
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/internal/script/lua.h>
|
||||
|
||||
#include <mgba/internal/script/socket.h>
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/macros.h>
|
||||
#include <mgba-util/string.h>
|
||||
|
||||
|
@ -51,6 +53,156 @@ static int _luaLenList(lua_State* lua);
|
|||
|
||||
static int _luaRequireShim(lua_State* lua);
|
||||
|
||||
static const char* _socketLuaSource =
|
||||
"socket = {\n"
|
||||
" ERRORS = {},\n"
|
||||
" tcp = function() return socket._create(_socket.create(), socket._tcpMT) end,\n"
|
||||
" bind = function(address, port)\n"
|
||||
" local s = socket.tcp()\n"
|
||||
" local ok, err = s:bind(address, port)\n"
|
||||
" if ok then return s end\n"
|
||||
" return ok, err\n"
|
||||
" end,\n"
|
||||
" connect = function(address, port)\n"
|
||||
" local s = socket.tcp()\n"
|
||||
" local ok, err = s:connect(address, port)\n"
|
||||
" if ok then return s end\n"
|
||||
" return ok, err\n"
|
||||
" end,\n"
|
||||
" _create = function(sock, mt) return setmetatable({\n"
|
||||
" _s = sock,\n"
|
||||
" _callbacks = {},\n"
|
||||
" _nextCallback = 1,\n"
|
||||
" }, mt) end,\n"
|
||||
" _wrap = function(status)\n"
|
||||
" if status == 0 then return 1 end\n"
|
||||
" return nil, socket.ERRORS[status] or ('error#' .. status)\n"
|
||||
" end,\n"
|
||||
" _mt = {\n"
|
||||
" __index = {\n"
|
||||
" close = function(self)\n"
|
||||
" if self._onframecb then\n"
|
||||
" callbacks:remove(self._onframecb)\n"
|
||||
" self._onframecb = nil\n"
|
||||
" end\n"
|
||||
" self._callbacks = {}\n"
|
||||
" return self._s:close()\n"
|
||||
" end,\n"
|
||||
" add = function(self, event, callback)\n"
|
||||
" if not self._callbacks[event] then self._callbacks[event] = {} end\n"
|
||||
" local cbid = self._nextCallback\n"
|
||||
" self._nextCallback = cbid + 1\n"
|
||||
" self._callbacks[event][cbid] = callback\n"
|
||||
" return id\n"
|
||||
" end,\n"
|
||||
" remove = function(self, cbid)\n"
|
||||
" for _, group in pairs(self._callbacks) do\n"
|
||||
" if group[cbid] then\n"
|
||||
" group[cbid] = nil\n"
|
||||
" end\n"
|
||||
" end\n"
|
||||
" end,\n"
|
||||
" _dispatch = function(self, event, ...)\n"
|
||||
" if not self._callbacks[event] then return end\n"
|
||||
" for k, cb in pairs(self._callbacks[event]) do\n"
|
||||
" if cb then\n"
|
||||
" local ok, ret = pcall(cb, self, ...)\n"
|
||||
" if not ok then console:error(ret) end\n"
|
||||
" end\n"
|
||||
" end\n"
|
||||
" end,\n"
|
||||
" },\n"
|
||||
" },\n"
|
||||
" _tcpMT = {\n"
|
||||
" __index = {\n"
|
||||
" _hook = function(self, status)\n"
|
||||
" if status == 0 then\n"
|
||||
" self._onframecb = callbacks:add('frame', function() self:poll() end)\n"
|
||||
" end\n"
|
||||
" return socket._wrap(status)\n"
|
||||
" end,\n"
|
||||
" bind = function(self, address, port)\n"
|
||||
" return socket._wrap(self._s:open(address or '', port))\n"
|
||||
" end,\n"
|
||||
" connect = function(self, address, port)\n"
|
||||
" local status = self._s:connect(address, port)\n"
|
||||
" end,\n"
|
||||
" listen = function(self, backlog)\n"
|
||||
" local status = self._s:listen(backlog or 1)\n"
|
||||
" return self:_hook(status)\n"
|
||||
" end,\n"
|
||||
" accept = function(self)\n"
|
||||
" local client = self._s:accept()\n"
|
||||
" if client.error ~= 0 then\n"
|
||||
" client:close()\n"
|
||||
" return socket._wrap(client.error)\n"
|
||||
" end\n"
|
||||
" local sock = socket._create(client, socket._tcpMT)\n"
|
||||
" sock:_hook(0)\n"
|
||||
" return sock\n"
|
||||
" end,\n"
|
||||
" send = function(self, data, i, j)\n"
|
||||
" local result = self._s:send(string.sub(data, i or 1, j))\n"
|
||||
" if result < 0 then return socket._wrap(self._s.error) end\n"
|
||||
" if i then return result + i - 1 end\n"
|
||||
" return result\n"
|
||||
" end,\n"
|
||||
// TODO: This does not match the API for LuaSocket's receive() implementation
|
||||
" receive = function(self, maxBytes)\n"
|
||||
" local result = self._s:recv(maxBytes)\n"
|
||||
" if (not result or #result == 0) and self._s.error ~= 0 then\n"
|
||||
" return socket._wrap(self._s.error)\n"
|
||||
" elseif not result or #result == 0 then\n"
|
||||
" return nil, 'disconnected'\n"
|
||||
" end\n"
|
||||
" return result or ''\n"
|
||||
" end,\n"
|
||||
" hasdata = function(self)\n"
|
||||
" local status = self._s:select(0)\n"
|
||||
" if status < 0 then\n"
|
||||
" return socket._wrap(self._s.error)\n"
|
||||
" end\n"
|
||||
" return status > 0\n"
|
||||
" end,\n"
|
||||
" poll = function(self)\n"
|
||||
" local status, err = self:hasdata()\n"
|
||||
" if err then\n"
|
||||
" self:_dispatch('error', err)\n"
|
||||
" elseif status then\n"
|
||||
" self:_dispatch('received')\n"
|
||||
" end\n"
|
||||
" end,\n"
|
||||
" },\n"
|
||||
" },\n"
|
||||
" _errMT = {\n"
|
||||
" __index = function (tbl, key)\n"
|
||||
" return rawget(tbl, C.SOCKERR[key])\n"
|
||||
" end,\n"
|
||||
" },\n"
|
||||
"}\n"
|
||||
"setmetatable(socket._tcpMT.__index, socket._mt)\n"
|
||||
"setmetatable(socket.ERRORS, socket._errMT)\n";
|
||||
|
||||
static const struct _mScriptSocketError {
|
||||
enum mSocketErrorCode err;
|
||||
const char* message;
|
||||
} _mScriptSocketErrors[] = {
|
||||
{ mSCRIPT_SOCKERR_UNKNOWN_ERROR, "unknown error" },
|
||||
{ mSCRIPT_SOCKERR_OK, NULL },
|
||||
{ mSCRIPT_SOCKERR_AGAIN, "temporary failure" },
|
||||
{ mSCRIPT_SOCKERR_ADDRESS_IN_USE, "address in use" },
|
||||
{ mSCRIPT_SOCKERR_DENIED, "access denied" },
|
||||
{ mSCRIPT_SOCKERR_UNSUPPORTED, "unsupported" },
|
||||
{ mSCRIPT_SOCKERR_CONNECTION_REFUSED, "connection refused" },
|
||||
{ mSCRIPT_SOCKERR_NETWORK_UNREACHABLE, "network unreachable" },
|
||||
{ mSCRIPT_SOCKERR_TIMEOUT, "timeout" },
|
||||
{ mSCRIPT_SOCKERR_FAILED, "failed" },
|
||||
{ mSCRIPT_SOCKERR_NOT_FOUND, "not found" },
|
||||
{ mSCRIPT_SOCKERR_NO_DATA, "no data" },
|
||||
{ mSCRIPT_SOCKERR_OUT_OF_MEMORY, "out of memory" },
|
||||
};
|
||||
static const int _mScriptSocketNumErrors = sizeof(_mScriptSocketErrors) / sizeof(struct _mScriptSocketError);
|
||||
|
||||
#if LUA_VERSION_NUM < 503
|
||||
#define lua_pushinteger lua_pushnumber
|
||||
#endif
|
||||
|
@ -177,6 +329,26 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS
|
|||
lua_getglobal(luaContext->lua, "require");
|
||||
luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX);
|
||||
|
||||
int status = luaL_dostring(luaContext->lua, _socketLuaSource);
|
||||
if (status) {
|
||||
mLOG(SCRIPT, ERROR, "Error in dostring while initializing sockets: %s\n", lua_tostring(luaContext->lua, -1));
|
||||
lua_pop(luaContext->lua, 1);
|
||||
} else {
|
||||
int i;
|
||||
lua_getglobal(luaContext->lua, "socket");
|
||||
lua_getfield(luaContext->lua, -1, "ERRORS");
|
||||
for (i = 0; i < _mScriptSocketNumErrors; i++) {
|
||||
struct _mScriptSocketError* err = &_mScriptSocketErrors[i];
|
||||
if (err->message) {
|
||||
lua_pushstring(luaContext->lua, err->message);
|
||||
} else {
|
||||
lua_pushnil(luaContext->lua);
|
||||
}
|
||||
lua_seti(luaContext->lua, -2, err->err);
|
||||
}
|
||||
lua_pop(luaContext->lua, 2);
|
||||
}
|
||||
|
||||
return &luaContext->d;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,276 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/script/context.h>
|
||||
|
||||
#include <mgba/internal/script/socket.h>
|
||||
#include <mgba/script/macros.h>
|
||||
#include <mgba-util/socket.h>
|
||||
|
||||
struct mScriptSocket {
|
||||
Socket socket;
|
||||
struct Address address;
|
||||
int32_t error;
|
||||
uint16_t port;
|
||||
};
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptSocket);
|
||||
|
||||
static const struct _mScriptSocketErrorMapping {
|
||||
int32_t nativeError;
|
||||
enum mSocketErrorCode mappedError;
|
||||
} _mScriptSocketErrorMappings[] = {
|
||||
{ EAGAIN, mSCRIPT_SOCKERR_AGAIN },
|
||||
{ EADDRINUSE, mSCRIPT_SOCKERR_ADDRESS_IN_USE },
|
||||
{ ECONNREFUSED, mSCRIPT_SOCKERR_CONNECTION_REFUSED },
|
||||
{ EACCES, mSCRIPT_SOCKERR_DENIED },
|
||||
{ EPERM, mSCRIPT_SOCKERR_DENIED },
|
||||
{ ENOTRECOVERABLE, mSCRIPT_SOCKERR_FAILED },
|
||||
{ ENETUNREACH, mSCRIPT_SOCKERR_NETWORK_UNREACHABLE },
|
||||
{ ETIMEDOUT, mSCRIPT_SOCKERR_TIMEOUT },
|
||||
{ EINVAL, mSCRIPT_SOCKERR_UNSUPPORTED },
|
||||
{ EPROTONOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED },
|
||||
#ifndef USE_GETHOSTBYNAME
|
||||
#ifdef _WIN32
|
||||
{ WSATRY_AGAIN, mSCRIPT_SOCKERR_AGAIN },
|
||||
{ WSANO_RECOVERY, mSCRIPT_SOCKERR_FAILED },
|
||||
{ WSANO_DATA, mSCRIPT_SOCKERR_NO_DATA },
|
||||
{ WSAHOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND },
|
||||
{ WSATYPE_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND },
|
||||
{ WSA_NOT_ENOUGH_MEMORY, mSCRIPT_SOCKERR_OUT_OF_MEMORY },
|
||||
{ WSAEAFNOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED },
|
||||
{ WSAEINVAL, mSCRIPT_SOCKERR_UNSUPPORTED },
|
||||
{ WSAESOCKTNOSUPPORT, mSCRIPT_SOCKERR_UNSUPPORTED },
|
||||
#else
|
||||
{ EAI_AGAIN, mSCRIPT_SOCKERR_AGAIN },
|
||||
{ EAI_FAIL, mSCRIPT_SOCKERR_FAILED },
|
||||
{ EAI_NODATA, mSCRIPT_SOCKERR_NO_DATA },
|
||||
{ EAI_NONAME, mSCRIPT_SOCKERR_NOT_FOUND },
|
||||
{ EAI_MEMORY, mSCRIPT_SOCKERR_OUT_OF_MEMORY },
|
||||
#endif
|
||||
#else
|
||||
{ -TRY_AGAIN, mSCRIPT_SOCKERR_AGAIN },
|
||||
{ -NO_RECOVERY, mSCRIPT_SOCKERR_FAILED },
|
||||
{ -NO_DATA, mSCRIPT_SOCKERR_NO_DATA },
|
||||
{ -HOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND },
|
||||
#endif
|
||||
};
|
||||
static const int _mScriptSocketNumErrorMappings = sizeof(_mScriptSocketErrorMappings) / sizeof(struct _mScriptSocketErrorMapping);
|
||||
|
||||
static void _mScriptSocketSetError(struct mScriptSocket* ssock, int32_t err) {
|
||||
if (!err) {
|
||||
ssock->error = mSCRIPT_SOCKERR_OK;
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < _mScriptSocketNumErrorMappings; i++) {
|
||||
if (_mScriptSocketErrorMappings[i].nativeError == err) {
|
||||
ssock->error = _mScriptSocketErrorMappings[i].mappedError;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ssock->error = mSCRIPT_SOCKERR_UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
static void _mScriptSocketSetSocket(struct mScriptSocket* ssock, Socket socket) {
|
||||
if (SOCKET_FAILED(socket)) {
|
||||
ssock->socket = INVALID_SOCKET;
|
||||
_mScriptSocketSetError(ssock, SocketError());
|
||||
} else {
|
||||
ssock->socket = socket;
|
||||
ssock->error = mSCRIPT_SOCKERR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
struct mScriptValue* _mScriptSocketCreate() {
|
||||
struct mScriptSocket client = {
|
||||
.socket = INVALID_SOCKET,
|
||||
.error = mSCRIPT_SOCKERR_OK,
|
||||
.port = 0
|
||||
};
|
||||
|
||||
struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptSocket));
|
||||
result->value.opaque = calloc(1, sizeof(struct mScriptSocket));
|
||||
*(struct mScriptSocket*) result->value.opaque = client;
|
||||
result->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
|
||||
return result;
|
||||
}
|
||||
|
||||
void _mScriptSocketClose(struct mScriptSocket* ssock) {
|
||||
if (!SOCKET_FAILED(ssock->socket)) {
|
||||
SocketClose(ssock->socket);
|
||||
}
|
||||
}
|
||||
|
||||
struct mScriptValue* _mScriptSocketAccept(struct mScriptSocket* ssock) {
|
||||
struct mScriptValue* value = _mScriptSocketCreate();
|
||||
struct mScriptSocket* client = (struct mScriptSocket*) value->value.opaque;
|
||||
_mScriptSocketSetSocket(client, SocketAccept(ssock->socket, &client->address));
|
||||
if (!client->error) {
|
||||
SocketSetBlocking(client->socket, false);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int32_t _mScriptSocketOpen(struct mScriptSocket* ssock, const char* addressStr, uint16_t port) {
|
||||
struct Address* addr = NULL;
|
||||
if (addressStr && addressStr[0]) {
|
||||
int32_t err = SocketResolveHost(addressStr, &ssock->address);
|
||||
if (err) {
|
||||
_mScriptSocketSetError(ssock, err);
|
||||
return err;
|
||||
}
|
||||
addr = &ssock->address;
|
||||
}
|
||||
ssock->port = port;
|
||||
_mScriptSocketSetSocket(ssock, SocketOpenTCP(port, addr));
|
||||
if (!ssock->error) {
|
||||
SocketSetBlocking(ssock->socket, false);
|
||||
}
|
||||
return ssock->error;
|
||||
}
|
||||
|
||||
int32_t _mScriptSocketConnect(struct mScriptSocket* ssock, const char* addressStr, uint16_t port) {
|
||||
int32_t err = SocketResolveHost(addressStr, &ssock->address);
|
||||
if (err) {
|
||||
_mScriptSocketSetError(ssock, err);
|
||||
return err;
|
||||
}
|
||||
ssock->port = port;
|
||||
_mScriptSocketSetSocket(ssock, SocketConnectTCP(port, &ssock->address));
|
||||
if (!ssock->error) {
|
||||
SocketSetBlocking(ssock->socket, false);
|
||||
}
|
||||
return ssock->error;
|
||||
}
|
||||
|
||||
int32_t _mScriptSocketListen(struct mScriptSocket* ssock, uint32_t queueLength) {
|
||||
_mScriptSocketSetError(ssock, SocketListen(ssock->socket, queueLength));
|
||||
return ssock->error;
|
||||
}
|
||||
|
||||
int32_t _mScriptSocketSend(struct mScriptSocket* ssock, struct mScriptString* data) {
|
||||
ssize_t written = SocketSend(ssock->socket, data->buffer, data->size);
|
||||
if (written < 0) {
|
||||
_mScriptSocketSetError(ssock, SocketError());
|
||||
return -ssock->error;
|
||||
}
|
||||
ssock->error = mSCRIPT_SOCKERR_OK;
|
||||
return written;
|
||||
}
|
||||
|
||||
struct mScriptValue* _mScriptSocketRecv(struct mScriptSocket* ssock, uint32_t maxBytes) {
|
||||
struct mScriptValue* value = mScriptStringCreateEmpty(maxBytes);
|
||||
struct mScriptString* data = value->value.string;
|
||||
ssize_t bytes = SocketRecv(ssock->socket, data->buffer, maxBytes);
|
||||
if (bytes <= 0) {
|
||||
data->size = 0;
|
||||
_mScriptSocketSetError(ssock, SocketError());
|
||||
} else {
|
||||
data->size = bytes;
|
||||
ssock->error = mSCRIPT_SOCKERR_OK;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// This works sufficiently well for a single socket, but it could be better.
|
||||
// Ideally, all sockets would be tracked and selected on together for efficiency.
|
||||
uint32_t _mScriptSocketSelectOne(struct mScriptSocket* ssock, int64_t timeoutMillis) {
|
||||
Socket reads[] = { ssock->socket };
|
||||
Socket errors[] = { ssock->socket };
|
||||
int result = SocketPoll(1, reads, NULL, errors, timeoutMillis);
|
||||
if (!result) {
|
||||
return 0;
|
||||
} else if (errors[0] != INVALID_SOCKET) {
|
||||
_mScriptSocketSetError(ssock, SocketError());
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
mSCRIPT_BIND_FUNCTION(mScriptSocketCreate_Binding, W(mScriptSocket), _mScriptSocketCreate, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptSocket, close, _mScriptSocketClose, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, W(mScriptSocket), accept, _mScriptSocketAccept, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, open, _mScriptSocketOpen, 2, CHARP, address, U16, port);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, connect, _mScriptSocketConnect, 2, CHARP, address, U16, port);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptSocket, S32, listen, _mScriptSocketListen, 1, U32, queueLength);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, send, _mScriptSocketSend, 1, STR, data);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, WSTR, recv, _mScriptSocketRecv, 1, U32, maxBytes);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptSocket, S32, select, _mScriptSocketSelectOne, 1, S64, timeoutMillis);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mScriptSocket)
|
||||
mSCRIPT_DEFINE_CLASS_DOCSTRING("An internal implementation of a TCP network socket.")
|
||||
mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(mScriptSocket, close)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Closes the socket. If the socket is already closed, this function does nothing.")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, close)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Creates a new socket for an incoming connection from a listening server socket.")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, accept)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Binds the socket to a specified address and port. "
|
||||
"If no address is specified, the socket is bound to all network interfaces."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, open)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Opens a TCP connection to the specified address and port.\n"
|
||||
"**Caution:** This is a blocking call. The emulator will not respond until "
|
||||
"the connection either succeeds or fails."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, connect)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Begins listening for incoming connections. The socket must have first been "
|
||||
"bound with the `open` function."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, listen)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Sends data over a socket. Returns the number of bytes written, or -1 if an "
|
||||
"error occurs."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, send)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Reads data from a socket, up to the specified number of bytes. "
|
||||
"If the socket has been disconnected, this function returns an empty string. "
|
||||
"Use `select` to test if data is available to be read."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, recv)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Checks the status of the socket. "
|
||||
"Returns 1 if data is available to be read. "
|
||||
"Returns -1 if an error has occurred on the socket."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptSocket, select)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"One of the `SOCKERR` constants describing the last error on the socket."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptSocket, S32, error)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptSocket, listen)
|
||||
mSCRIPT_S32(1)
|
||||
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||
|
||||
|
||||
void mScriptContextAttachSocket(struct mScriptContext* context) {
|
||||
mScriptContextExportNamespace(context, "_socket", (struct mScriptKVPair[]) {
|
||||
mSCRIPT_KV_PAIR(create, &mScriptSocketCreate_Binding),
|
||||
mSCRIPT_KV_SENTINEL
|
||||
});
|
||||
mScriptContextSetDocstring(context, "_socket", "Basic TCP sockets library");
|
||||
mScriptContextSetDocstring(context, "_socket.create", "Creates a new socket object");
|
||||
mScriptContextExportConstants(context, "SOCKERR", (struct mScriptKVPair[]) {
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, UNKNOWN_ERROR),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, OK),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, AGAIN),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, ADDRESS_IN_USE),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, CONNECTION_REFUSED),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, DENIED),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, FAILED),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NETWORK_UNREACHABLE),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NOT_FOUND),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, NO_DATA),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, OUT_OF_MEMORY),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, TIMEOUT),
|
||||
mSCRIPT_CONSTANT_PAIR(mSCRIPT_SOCKERR, UNSUPPORTED),
|
||||
mSCRIPT_KV_SENTINEL
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue