Web: Rewrite sockets API to avoid standard library

This commit is contained in:
UnknownShadow200 2021-05-23 13:47:51 +10:00
parent bb68bfd0cf
commit e43c4ea690
5 changed files with 335 additions and 48 deletions

View File

@ -90,6 +90,66 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Emscripten license
==============================================================================
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
==============================================================================
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal with the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimers.
Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimers
in the documentation and/or other materials provided with the
distribution.
Neither the names of Mozilla,
nor the names of its contributors may be used to endorse
or promote products derived from this Software without specific prior
written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
==============================================================================
FreeType license
==================
The FreeType Project LICENSE

View File

@ -272,6 +272,7 @@
<ClCompile Include="Graphics.c" />
<ClCompile Include="Gui.c" />
<ClCompile Include="HeldBlockRenderer.c" />
<ClCompile Include="interop_web.c" />
<ClCompile Include="Inventory.c" />
<ClCompile Include="Launcher.c" />
<ClCompile Include="LScreens.c" />

View File

@ -545,5 +545,8 @@
<ClCompile Include="Platform_Posix.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
<ClCompile Include="interop_web.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -10,7 +10,6 @@
#include "Utils.h"
#include "Errors.h"
/* POSIX can be shared between Linux/BSD/macOS */
#include <errno.h>
#include <time.h>
#include <stdlib.h>
@ -18,15 +17,9 @@
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <utime.h>
#include <signal.h>
#include <stdio.h>
const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */
@ -73,9 +66,9 @@ cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) {
return ((end - beg) * sw_freqMul) / sw_freqDiv;
}
extern void interop_Log(const char* msg, int len);
void Platform_Log(const char* msg, int len) {
write(STDOUT_FILENO, msg, len);
write(STDOUT_FILENO, "\n", 1);
interop_Log(msg, len);
}
#define UnixTime_TotalMS(time) ((cc_uint64)time.tv_sec * 1000 + UNIX_EPOCH + (time.tv_usec / 1000))
@ -248,76 +241,98 @@ void Platform_LoadSysFonts(void) { }
/*########################################################################################################################*
*---------------------------------------------------------Socket----------------------------------------------------------*
*#########################################################################################################################*/
extern int interop_SocketCreate(void);
extern int interop_SocketConnect(int sock, const char* addr, int port);
extern int interop_SocketClose(int sock);
extern int interop_SocketSend(int sock, const void* data, int len);
extern int interop_SocketRecv(int sock, void* data, int len);
extern int interop_SocketGetPending(int sock);
extern int interop_SocketGetError(int sock);
extern int interop_SocketPoll(int sock);
cc_result Socket_Create(cc_socket* s) {
*s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
return *s == -1 ? errno : 0;
*s = interop_SocketCreate();
return 0;
}
cc_result Socket_Available(cc_socket s, int* available) {
return ioctl(s, FIONREAD, available);
int res = interop_SocketGetPending(s);
/* returned result is negative for error */
if (res >= 0) {
*available = res; return 0;
} else {
*available = 0; return -res;
}
}
cc_result Socket_SetBlocking(cc_socket s, cc_bool blocking) {
return ERR_NOT_SUPPORTED; /* sockets always async */
}
cc_result Socket_GetError(cc_socket s, cc_result* result) {
socklen_t resultSize = sizeof(cc_result);
return getsockopt(s, SOL_SOCKET, SO_ERROR, result, &resultSize);
int res = interop_SocketGetError(s);
/* returned result is negative for error */
if (res >= 0) {
*result = res; return 0;
} else {
*result = 0; return -res;
}
}
cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) {
struct sockaddr addr;
cc_result res;
addr.sa_family = AF_INET;
Stream_SetU16_BE( (cc_uint8*)&addr.sa_data[0], port);
if (!Utils_ParseIP(ip, (cc_uint8*)&addr.sa_data[2]))
return ERR_INVALID_ARGUMENT;
res = connect(s, &addr, sizeof(addr));
return res == -1 ? errno : 0;
char addr[NATIVE_STR_LEN];
Platform_EncodeUtf8(addr, ip);
return interop_SocketConnect(s, addr, port);
}
cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
/* recv only reads one WebSocket frame at most, hence call it multiple times */
int recvCount = 0, pending;
*modified = 0;
int res; *modified = 0;
while (count && !Socket_Available(s, &pending) && pending) {
recvCount = recv(s, data, count, 0);
if (recvCount == -1) return errno;
while (count) {
res = interop_SocketRecv(s, data, count);
/* returned result is negative for error */
*modified += recvCount;
data += recvCount; count -= recvCount;
if (res >= 0) {
*modified += res;
data += res; count -= res;
} else {
/* EAGAIN when no data available */
if (res == -EAGAIN) break;
return -res;
}
}
return 0;
}
cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
int sentCount = send(s, data, count, 0);
if (sentCount != -1) { *modified = sentCount; return 0; }
*modified = 0; return errno;
int res = interop_SocketSend(s, data, count);
/* returned result is negative for error */
if (res >= 0) {
*modified = res; return 0;
} else {
*modified = 0; return -res;
}
}
cc_result Socket_Close(cc_socket s) {
cc_result res = close(s);
if (res == -1) res = errno;
return res;
/* returned result is negative for error */
return -interop_SocketClose(s);
}
#include <poll.h>
cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
struct pollfd pfd;
int flags;
int res = interop_SocketPoll(s), flags;
/* returned result is negative for error */
pfd.fd = s;
pfd.events = mode == SOCKET_POLL_READ ? POLLIN : POLLOUT;
if (poll(&pfd, 1, 0) == -1) { *success = false; return errno; }
/* to match select, closed socket still counts as readable */
flags = mode == SOCKET_POLL_READ ? (POLLIN | POLLHUP) : POLLOUT;
*success = (pfd.revents & flags) != 0;
return 0;
if (res >= 0) {
flags = mode == SOCKET_POLL_READ ? 0x01 : 0x02;
*success = (res & flags) != 0;
return 0;
} else {
*success = false; return -res;
}
}
@ -397,10 +412,13 @@ EMSCRIPTEN_KEEPALIVE void Platform_LogError(const char* msg) {
}
extern void interop_InitModule(void);
extern void interop_InitSockets(void);
extern void interop_GetIndexedDBError(char* buffer);
void Platform_Init(void) {
char tmp[64+1] = { 0 };
interop_InitModule();
interop_InitSockets();
/* Check if an error occurred when pre-loading IndexedDB */
interop_GetIndexedDBError(tmp);

View File

@ -1,4 +1,8 @@
#include "Core.h"
/* Copyright 2010 The Emscripten Authors. All rights reserved. */
/* Emscripten is available under two separate licenses, the MIT license and the */
/* University of Illinois/NCSA Open Source License. Both these licenses can be */
/* found in the LICENSE file. */
#ifdef CC_BUILD_WEB
#include <emscripten/emscripten.h>
@ -142,6 +146,207 @@ int interop_OpenTab(const char* url) {
return 0;
}
void interop_Log(const char* msg, int len) {
EM_ASM_({ Module.print(UTF8ArrayToString(HEAPU8, $0, $1)); }, msg, len);
}
void interop_InitSockets(void) {
EM_ASM({
window.SOCKETS = {
EBADF:-8,EISCONN:-30,ENOTCONN:-53,EAGAIN:-6,EWOULDBLOCK:-6,EHOSTUNREACH:-23,EINPROGRESS:-26,EALREADY:-7,ECONNRESET:-15,EINVAL:-28,ECONNREFUSED:-14,
sockets = [],
createSocket:function() {
var sock = {
error: null, // Used in getsockopt for SOL_SOCKET/SO_ERROR test
recv_queue: [],
socket: null,
};
sockets.push(sock);
return (sockets.length - 1) | 0;
},
connect:function(fd, addr, port) {
var sock = sockets[fd];
if (!sock) return EBADF;
// early out if we're already connected / in the middle of connecting
var ws = sock.socket;
if (ws) {
if (ws.readyState === ws.CONNECTING) return EALREADY;
return EISCONN;
}
// create the actual websocket object and connect
try {
var parts = addr.split('/');
var url = 'ws://' + parts[0] + ":" + port + "/" + parts.slice(1).join('/');
ws = new WebSocket(url, 'ClassiCube');
ws.binaryType = 'arraybuffer';
} catch (e) {
return EHOSTUNREACH;
}
sock.socket = ws;
ws.onopen = function() {};
ws.onclose = function() {};
ws.onmessage = function(event) {
var data = event.data;
if (typeof data === 'string') {
var encoder = new TextEncoder(); // should be utf-8
data = encoder.encode(data); // make a typed array from the string
} else {
assert(data.byteLength !== undefined); // must receive an ArrayBuffer
if (data.byteLength == 0) {
// An empty ArrayBuffer will emit a pseudo disconnect event
// as recv/recvmsg will return zero which indicates that a socket
// has performed a shutdown although the connection has not been disconnected yet.
return;
} else {
data = new Uint8Array(data); // make a typed array view on the array buffer
}
}
sock.recv_queue.push(data);
};
ws.onerror = function(error) {
// The WebSocket spec only allows a 'simple event' to be thrown on error,
// so we only really know as much as ECONNREFUSED.
sock.error = -ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test.
};
// always "fail" in non-blocking mode
return EINPROGRESS;
},
poll:function(fd) {
var sock = sockets[fd];
if (!sock) return EBADF;
var ws = sock.socket;
if (!ws) return 0;
var mask = 0;
if (sock.recv_queue.length || (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED)) mask |= 1;
if (ws.readyState === ws.OPEN) mask |= 2;
return mask;
},
getPending:function(fd) {
var sock = sockets[fd];
if (!sock) return EBADF;
var bytes = 0;
if (sock.recv_queue.length) {
bytes = sock.recv_queue[0].data.length;
}
return bytes;
},
getError:function(fd) {
var sock = sockets[fd];
if (!sock) return EBADF;
return sock.error || 0;
},
close:function(fd) {
var sock = sockets[fd];
if (!sock) return EBADF;
try {
sock.socket.close();
} catch (e) {
}
delete sock.socket;
return 0;
},
send:function(fd, src, length) {
var sock = sockets[fd];
if (!sock) return EBADF;
var ws = sock.socket;
if (!ws || ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
return ENOTCONN;
} else if (ws.readyState === ws.CONNECTING) {
return EAGAIN;
}
var data = HEAP8.slice(src, src + length);
try {
ws.send(data);
return length;
} catch (e) {
return EINVAL;
}
},
recv:function(fd, dst, length) {
var sock = sockets[fd];
if (!sock) return EBADF;
var packet = sock.recv_queue.shift();
if (!packet) {
var ws = sock.socket;
if (!ws || ws.readyState == = ws.CLOSING || ws.readyState == = ws.CLOSED) {
return ENOTCONN;
} else {
// socket is in a valid state but truly has nothing available
return EAGAIN;
}
}
// packet will be an ArrayBuffer if it's unadulterated, but if it's
// requeued TCP data it'll be an ArrayBufferView
var packetLength = packet.byteLength || packet.length;
var packetOffset = packet.byteOffset || 0;
var packetBuffer = packet.buffer || packet;
var bytesRead = Math.min(length, packetLength);
var msg = new Uint8Array(packetBuffer, packetOffset, bytesRead);
// push back any unread data for TCP connections
if (bytesRead < packetLength) {
var bytesRemaining = packetLength - bytesRead;
packet = new Uint8Array(packetBuffer, packetOffset + bytesRead, bytesRemaining);
sock.recv_queue.unshift(packet);
}
HEAPU8.set(msg.buffer, dst);
return msg.buffer.byteLength;
}
};
});
}
int interop_SocketCreate(void) {
return EM_ASM_INT_V({ return SOCKETS.createSocket(); });
}
int interop_SocketConnect(int sock, const char* addr, int port) {
return EM_ASM_INT({
var str = UTF8ToString($1);
return SOCKETS.connect($0, str, $2);
}, sock, addr, port);
}
int interop_SocketClose(int sock) {
return EM_ASM_INT({ return SOCKETS.close($0); }, sock);
}
int interop_SocketSend(int sock, const void* data, int len) {
return EM_ASM_INT({ return SOCKETS.send($0, $1, $2); }, sock, data, len);
}
int interop_SocketRecv(int sock, void* data, int len) {
return EM_ASM_INT({ return SOCKETS.recv($0, $1, $2); }, sock, data, len);
}
int interop_SocketGetPending(int sock) {
return EM_ASM_INT({ return SOCKETS.getPending($0); }, sock);
}
int interop_SocketGetError(int sock) {
return EM_ASM_INT({ return SOCKETS.getError($0); }, sock);
}
int interop_SocketPoll(int sock) {
return EM_ASM_INT({ return SOCKETS.poll($0); }, sock);
}
/*########################################################################################################################*
*----------------------------------------------------------Window---------------------------------------------------------*