turn into a cell package
This commit is contained in:
588
net/enet.c
Normal file
588
net/enet.c
Normal file
@@ -0,0 +1,588 @@
|
||||
#include "cell.h"
|
||||
#define ENET_IMPLEMENTATION
|
||||
#include "enet.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
static JSClassID enet_host_id;
|
||||
static JSClassID enet_peer_class_id;
|
||||
|
||||
static void js_enet_host_finalizer(JSRuntime *rt, JSValue val)
|
||||
{
|
||||
ENetHost *host = JS_GetOpaque(val, enet_host_id);
|
||||
if (host) enet_host_destroy(host);
|
||||
}
|
||||
|
||||
static void js_enet_peer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
|
||||
JS_MarkValue(rt, *(JSValue*)peer->data, mark_func);
|
||||
}
|
||||
|
||||
static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
|
||||
JS_FreeValueRT(rt, *(JSValue*)peer->data);
|
||||
free(peer->data);
|
||||
}
|
||||
|
||||
// Initialize the ENet library. Must be called before using any ENet functionality.
|
||||
static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (enet_initialize() != 0) return JS_ThrowInternalError(ctx, "Error initializing ENet");
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
|
||||
// need any ENet functionality.
|
||||
static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
enet_deinitialize();
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Create an ENet host for either a client-like unbound host or a server bound to a specific
|
||||
// address and port:
|
||||
//
|
||||
// - If no argument is provided, creates an unbound "client-like" host with default settings
|
||||
// (maximum 32 peers, 2 channels, unlimited bandwidth).
|
||||
// - If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
|
||||
// that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
|
||||
//
|
||||
// Throws an error if host creation fails for any reason.
|
||||
static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetHost *host;
|
||||
ENetAddress address;
|
||||
ENetAddress *send = &address;
|
||||
size_t peer_count = 1000;
|
||||
size_t channel_limit = 0;
|
||||
enet_uint32 incoming_bandwidth = 0;
|
||||
enet_uint32 outgoing_bandwidth = 0;
|
||||
JSValue obj;
|
||||
|
||||
if (argc < 1 || !JS_IsObject(argv[0])) {
|
||||
host = enet_host_create(NULL, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth);
|
||||
if (!host) return JS_ThrowInternalError(ctx, "Failed to create ENet client host");
|
||||
goto wrap;
|
||||
}
|
||||
|
||||
JSValue config_obj = argv[0];
|
||||
JSValue addr_val = JS_GetPropertyStr(ctx, config_obj, "address");
|
||||
const char *addr_str = JS_IsString(addr_val) ? JS_ToCString(ctx, addr_val) : NULL;
|
||||
JS_FreeValue(ctx, addr_val);
|
||||
|
||||
if (!addr_str)
|
||||
send = NULL;
|
||||
else {
|
||||
JSValue port_val = JS_GetPropertyStr(ctx, config_obj, "port");
|
||||
int32_t port32 = 0;
|
||||
JS_ToInt32(ctx, &port32, port_val);
|
||||
JS_FreeValue(ctx, port_val);
|
||||
|
||||
if (strcmp(addr_str, "any") == 0)
|
||||
address.host = ENET_HOST_ANY;
|
||||
else if (strcmp(addr_str, "broadcast") == 0)
|
||||
enet_address_set_host_ip(&address, "255.255.255.255");
|
||||
else {
|
||||
int err = enet_address_set_host_ip(&address, addr_str);
|
||||
if (err != 0) {
|
||||
JS_FreeCString(ctx, addr_str);
|
||||
return JS_ThrowInternalError(ctx, "Failed to set host IP from '%s'. Error: %d", addr_str, err);
|
||||
}
|
||||
}
|
||||
address.port = (enet_uint16)port32;
|
||||
JS_FreeCString(ctx, addr_str);
|
||||
}
|
||||
|
||||
JSValue chan_val = JS_GetPropertyStr(ctx, config_obj, "channels");
|
||||
JS_ToUint32(ctx, &channel_limit, chan_val);
|
||||
JS_FreeValue(ctx, chan_val);
|
||||
|
||||
JSValue in_bw_val = JS_GetPropertyStr(ctx, config_obj, "incoming_bandwidth");
|
||||
JS_ToUint32(ctx, &incoming_bandwidth, in_bw_val);
|
||||
JS_FreeValue(ctx, in_bw_val);
|
||||
|
||||
JSValue out_bw_val = JS_GetPropertyStr(ctx, config_obj, "outgoing_bandwidth");
|
||||
JS_ToUint32(ctx, &outgoing_bandwidth, out_bw_val);
|
||||
JS_FreeValue(ctx, out_bw_val);
|
||||
|
||||
host = enet_host_create(send, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth);
|
||||
if (!host) return JS_ThrowInternalError(ctx, "Failed to create ENet host");
|
||||
|
||||
wrap:
|
||||
obj = JS_NewObjectClass(ctx, enet_host_id);
|
||||
if (JS_IsException(obj)) {
|
||||
enet_host_destroy(host);
|
||||
return obj;
|
||||
}
|
||||
JS_SetOpaque(obj, host);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Helper function to get a JSValue for an ENetPeer.
|
||||
static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer)
|
||||
{
|
||||
if (!peer->data) {
|
||||
peer->data = malloc(sizeof(JSValue));
|
||||
*(JSValue*)peer->data = JS_NewObjectClass(ctx, enet_peer_class_id);
|
||||
JS_SetOpaque(*(JSValue*)peer->data, peer);
|
||||
}
|
||||
return JS_DupValue(ctx, *(JSValue*)peer->data);
|
||||
}
|
||||
|
||||
// Poll for and process any available network events (connect, receive, disconnect, or none)
|
||||
// from this host, calling the provided callback for each event. This function loops until
|
||||
// no more events are available in the current timeframe.
|
||||
//
|
||||
// :param callback: A function called once for each available event, receiving an event
|
||||
// object as its single argument.
|
||||
// :param timeout: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
|
||||
// :return: None
|
||||
static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
||||
if (!host) return JS_EXCEPTION;
|
||||
|
||||
if (argc < 1 || !JS_IsFunction(ctx, argv[0])) return JS_ThrowTypeError(ctx, "Expected a callback function as first argument");
|
||||
JSValue callback = JS_DupValue(ctx, argv[0]);
|
||||
|
||||
double secs;
|
||||
JS_ToFloat64(ctx, &secs, argv[1]);
|
||||
|
||||
ENetEvent event;
|
||||
while (enet_host_service(host, &event, secs*1000.0f) > 0) {
|
||||
JSValue event_obj = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, event_obj, "peer", peer_get_value(ctx, event.peer));
|
||||
|
||||
switch (event.type) {
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "connect"));
|
||||
break;
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "receive"));
|
||||
JS_SetPropertyStr(ctx, event_obj, "channelID", JS_NewInt32(ctx, event.channelID));
|
||||
|
||||
// Pass raw data as string or ArrayBuffer
|
||||
if (event.packet->dataLength > 0) {
|
||||
JSValue data_val = js_new_blob_stoned_copy(ctx, event.packet->data, event.packet->dataLength);
|
||||
JS_SetPropertyStr(ctx, event_obj, "data", data_val);
|
||||
}
|
||||
enet_packet_destroy(event.packet);
|
||||
break;
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "disconnect"));
|
||||
break;
|
||||
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
|
||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "disconnect_timeout"));
|
||||
break;
|
||||
case ENET_EVENT_TYPE_NONE:
|
||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "none"));
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: raise exception?
|
||||
JS_FreeValue(ctx, event_obj);
|
||||
}
|
||||
|
||||
JS_FreeValue(ctx, callback);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Initiate a connection from this host to a remote server. Throws an error if the
|
||||
// connection cannot be started.
|
||||
//
|
||||
// :param hostname: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
|
||||
// :param port: The port number to connect to.
|
||||
// :return: An ENetPeer object representing the connection.
|
||||
static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
||||
if (!host) return JS_EXCEPTION;
|
||||
|
||||
if (argc < 2) return JS_ThrowTypeError(ctx, "Expected 2 arguments: hostname, port");
|
||||
|
||||
const char *hostname = JS_ToCString(ctx, argv[0]);
|
||||
if (!hostname) return JS_EXCEPTION;
|
||||
int port;
|
||||
JS_ToInt32(ctx, &port, argv[1]);
|
||||
|
||||
ENetAddress address;
|
||||
enet_address_set_host(&address, hostname);
|
||||
JS_FreeCString(ctx, hostname);
|
||||
address.port = port;
|
||||
|
||||
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
|
||||
if (!peer) return JS_ThrowInternalError(ctx, "No available peers for initiating an ENet connection");
|
||||
|
||||
return peer_get_value(ctx, peer);
|
||||
}
|
||||
|
||||
// Flush all pending outgoing packets for this host immediately.
|
||||
//
|
||||
// :return: None
|
||||
static JSValue js_enet_host_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
||||
if (!host) return JS_EXCEPTION;
|
||||
enet_host_flush(host);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Broadcast a string or ArrayBuffer to all connected peers on channel 0.
|
||||
//
|
||||
// :param data: A string or ArrayBuffer to broadcast to all peers.
|
||||
// :return: None
|
||||
static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
||||
if (!host) return JS_EXCEPTION;
|
||||
|
||||
if (argc < 1) return JS_ThrowTypeError(ctx, "Expected a string or ArrayBuffer to broadcast");
|
||||
|
||||
const char *data_str = NULL;
|
||||
size_t data_len = 0;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
|
||||
if (!data_str) return JS_EXCEPTION;
|
||||
} else if (js_is_blob(ctx,argv[0])) {
|
||||
buf = js_get_blob_data(ctx, &data_len, argv[0]);
|
||||
if (!buf) return JS_EXCEPTION;
|
||||
} else {
|
||||
return JS_ThrowTypeError(ctx, "broadcast() only accepts a string or ArrayBuffer");
|
||||
}
|
||||
|
||||
ENetPacket *packet = enet_packet_create(data_str ? (const void*)data_str : (const void*)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
|
||||
if (data_str) JS_FreeCString(ctx, data_str);
|
||||
if (!packet) return JS_ThrowInternalError(ctx, "Failed to create ENet packet");
|
||||
|
||||
enet_host_broadcast(host, 0, packet);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue js_enet_host_get_port(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetHost *host = JS_GetOpaque(self, enet_host_id);
|
||||
if (!host) return JS_EXCEPTION;
|
||||
return JS_NewInt32(js, host->address.port);
|
||||
}
|
||||
|
||||
static JSValue js_enet_host_get_address(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetHost *me = JS_GetOpaque(self, enet_host_id);
|
||||
if (!me) return JS_EXCEPTION;
|
||||
|
||||
char ip_str[128];
|
||||
if (enet_address_get_host_ip(&me->address, ip_str, sizeof(ip_str)) != 0)
|
||||
return JS_NULL;
|
||||
|
||||
return JS_NewString(js, ip_str);
|
||||
}
|
||||
|
||||
// Peer-level operations
|
||||
// Request a graceful disconnection from this peer. The connection will close after
|
||||
// pending data is sent.
|
||||
//
|
||||
// :return: None
|
||||
static JSValue js_enet_peer_disconnect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
enet_peer_disconnect(peer, 0);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Send a string or ArrayBuffer to this peer on channel 0.
|
||||
//
|
||||
// :param data: A string or ArrayBuffer to send.
|
||||
// :return: None
|
||||
static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
|
||||
if (argc < 1) return JS_ThrowTypeError(ctx, "Expected a string or ArrayBuffer to send");
|
||||
|
||||
const char *data_str = NULL;
|
||||
size_t data_len = 0;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
|
||||
if (!data_str) return JS_EXCEPTION;
|
||||
} else if (js_is_blob(ctx,argv[0])) {
|
||||
buf = js_get_blob_data(ctx, &data_len, argv[0]);
|
||||
if (!buf) return JS_EXCEPTION;
|
||||
} else {
|
||||
return JS_ThrowTypeError(ctx, "send() only accepts a string or ArrayBuffer");
|
||||
}
|
||||
|
||||
ENetPacket *packet = enet_packet_create(data_str ? (const void*)data_str : (const void*)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
|
||||
if (data_str) JS_FreeCString(ctx, data_str);
|
||||
if (!packet) return JS_ThrowInternalError(ctx, "Failed to create ENet packet");
|
||||
|
||||
if (enet_peer_send(peer, 0, packet) < 0) return JS_ThrowInternalError(ctx, "Unable to send packet");
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Immediately terminate the connection to this peer, discarding any pending data.
|
||||
//
|
||||
// :return: None
|
||||
static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
enet_peer_disconnect_now(peer, 0);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Request a disconnection from this peer after all queued packets are sent.
|
||||
//
|
||||
// :return: None
|
||||
static JSValue js_enet_peer_disconnect_later(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
enet_peer_disconnect_later(peer, 0);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Reset this peer's connection, immediately dropping it and clearing its internal state.
|
||||
//
|
||||
// :return: None
|
||||
static JSValue js_enet_peer_reset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
enet_peer_reset(peer);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Send a ping request to this peer to measure latency.
|
||||
//
|
||||
// :return: None
|
||||
static JSValue js_enet_peer_ping(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
enet_peer_ping(peer);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
|
||||
// rate based on packet loss or congestion.
|
||||
//
|
||||
// :param interval: The interval (ms) between throttle adjustments.
|
||||
// :param acceleration: The factor to increase sending speed when conditions improve.
|
||||
// :param deceleration: The factor to decrease sending speed when conditions worsen.
|
||||
// :return: None
|
||||
static JSValue js_enet_peer_throttle_configure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
|
||||
int interval, acceleration, deceleration;
|
||||
if (argc < 3 || JS_ToInt32(ctx, &interval, argv[0]) || JS_ToInt32(ctx, &acceleration, argv[1]) || JS_ToInt32(ctx, &deceleration, argv[2]))
|
||||
return JS_ThrowTypeError(ctx, "Expected 3 int arguments: interval, acceleration, deceleration");
|
||||
|
||||
enet_peer_throttle_configure(peer, interval, acceleration, deceleration);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_timeout(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
|
||||
int timeout_limit, timeout_min, timeout_max;
|
||||
if (argc < 3 || JS_ToInt32(ctx, &timeout_limit, argv[0]) || JS_ToInt32(ctx, &timeout_min, argv[1]) || JS_ToInt32(ctx, &timeout_max, argv[2]))
|
||||
return JS_ThrowTypeError(ctx, "Expected 3 integer arguments: timeout_limit, timeout_min, timeout_max");
|
||||
|
||||
enet_peer_timeout(peer, timeout_limit, timeout_min, timeout_max);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Class definitions
|
||||
static JSClassDef enet_host = {
|
||||
"ENetHost",
|
||||
.finalizer = js_enet_host_finalizer,
|
||||
};
|
||||
|
||||
static JSClassDef enet_peer_class = {
|
||||
"ENetPeer",
|
||||
.finalizer = js_enet_peer_finalizer,
|
||||
.gc_mark = js_enet_peer_mark
|
||||
};
|
||||
|
||||
JSValue js_enet_resolve_hostname(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
// TODO: implement
|
||||
const char *hostname = JS_ToCString(js, argv[0]);
|
||||
JS_FreeCString(js, hostname);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_enet_funcs[] = {
|
||||
JS_CFUNC_DEF("initialize", 0, js_enet_initialize),
|
||||
JS_CFUNC_DEF("deinitialize", 0, js_enet_deinitialize),
|
||||
JS_CFUNC_DEF("create_host", 1, js_enet_host_create),
|
||||
JS_CFUNC_DEF("resolve_hostname", 1, js_enet_resolve_hostname),
|
||||
};
|
||||
|
||||
static const JSCFunctionListEntry js_enet_host_funcs[] = {
|
||||
JS_CFUNC_DEF("service", 2, js_enet_host_service),
|
||||
JS_CFUNC_DEF("connect", 2, js_enet_host_connect),
|
||||
JS_CFUNC_DEF("flush", 0, js_enet_host_flush),
|
||||
JS_CFUNC_DEF("broadcast", 1, js_enet_host_broadcast),
|
||||
JS_CGETSET_DEF("port", js_enet_host_get_port, NULL),
|
||||
JS_CGETSET_DEF("address", js_enet_host_get_address, NULL),
|
||||
};
|
||||
|
||||
static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
return JS_NewInt32(ctx, peer->roundTripTime);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_incoming_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
if (peer->incomingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
|
||||
return JS_NewInt32(ctx, peer->incomingBandwidth);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
if (peer->outgoingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
|
||||
return JS_NewInt32(ctx, peer->outgoingBandwidth);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_last_send_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
return JS_NewInt32(ctx, peer->lastSendTime);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_last_receive_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_EXCEPTION;
|
||||
return JS_NewInt32(ctx, peer->lastReceiveTime);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_mtu(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
||||
return JS_NewInt32(ctx, peer->mtu);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_outgoing_data_total(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
||||
return JS_NewInt32(ctx, peer->outgoingDataTotal);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_incoming_data_total(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
||||
return JS_NewInt32(ctx, peer->incomingDataTotal);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_rtt_variance(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
||||
return JS_NewInt32(ctx, peer->roundTripTimeVariance);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_packet_loss(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
||||
return JS_NewInt32(ctx, peer->packetLoss);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_state(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_NewInt32(ctx, -1);
|
||||
return JS_NewInt32(ctx, peer->state);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_reliable_data_in_transit(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
||||
return JS_NewInt32(ctx, peer->reliableDataInTransit);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_port(JSContext *js, JSValueConst self)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id);
|
||||
return JS_NewUint32(js, peer->address.port);
|
||||
}
|
||||
|
||||
static JSValue js_enet_peer_get_address(JSContext *js, JSValueConst self)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id);
|
||||
char ip_str[128];
|
||||
if (enet_address_get_host_ip(&peer->address, ip_str, sizeof(ip_str)) != 0)
|
||||
return JS_NULL;
|
||||
|
||||
return JS_NewString(js, ip_str);
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_enet_peer_funcs[] = {
|
||||
JS_CFUNC_DEF("send", 1, js_enet_peer_send),
|
||||
JS_CFUNC_DEF("disconnect", 0, js_enet_peer_disconnect),
|
||||
JS_CFUNC_DEF("disconnect_now", 0, js_enet_peer_disconnect_now),
|
||||
JS_CFUNC_DEF("disconnect_later", 0, js_enet_peer_disconnect_later),
|
||||
JS_CFUNC_DEF("reset", 0, js_enet_peer_reset),
|
||||
JS_CFUNC_DEF("ping", 0, js_enet_peer_ping),
|
||||
JS_CFUNC_DEF("throttle_configure", 3, js_enet_peer_throttle_configure),
|
||||
JS_CFUNC_DEF("timeout", 3, js_enet_peer_timeout),
|
||||
JS_CGETSET_DEF("rtt", js_enet_peer_get_rtt, NULL),
|
||||
JS_CGETSET_DEF("incoming_bandwidth", js_enet_peer_get_incoming_bandwidth, NULL),
|
||||
JS_CGETSET_DEF("outgoing_bandwidth", js_enet_peer_get_outgoing_bandwidth, NULL),
|
||||
JS_CGETSET_DEF("last_send_time", js_enet_peer_get_last_send_time, NULL),
|
||||
JS_CGETSET_DEF("last_receive_time", js_enet_peer_get_last_receive_time, NULL),
|
||||
JS_CGETSET_DEF("mtu", js_enet_peer_get_mtu, NULL),
|
||||
JS_CGETSET_DEF("outgoing_data_total", js_enet_peer_get_outgoing_data_total, NULL),
|
||||
JS_CGETSET_DEF("incoming_data_total", js_enet_peer_get_incoming_data_total, NULL),
|
||||
JS_CGETSET_DEF("rtt_variance", js_enet_peer_get_rtt_variance, NULL),
|
||||
JS_CGETSET_DEF("packet_loss", js_enet_peer_get_packet_loss, NULL),
|
||||
JS_CGETSET_DEF("state", js_enet_peer_get_state, NULL),
|
||||
JS_CGETSET_DEF("reliable_data_in_transit", js_enet_peer_get_reliable_data_in_transit, NULL),
|
||||
JS_CGETSET_DEF("port", js_enet_peer_get_port, NULL),
|
||||
JS_CGETSET_DEF("address", js_enet_peer_get_address, NULL),
|
||||
};
|
||||
|
||||
JSValue js_enet_use(JSContext *ctx)
|
||||
{
|
||||
JS_NewClassID(&enet_host_id);
|
||||
JS_NewClass(JS_GetRuntime(ctx), enet_host_id, &enet_host);
|
||||
JSValue host_proto = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
|
||||
JS_SetClassProto(ctx, enet_host_id, host_proto);
|
||||
|
||||
JS_NewClassID(&enet_peer_class_id);
|
||||
JS_NewClass(JS_GetRuntime(ctx), enet_peer_class_id, &enet_peer_class);
|
||||
JSValue peer_proto = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
|
||||
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);
|
||||
|
||||
JSValue export_obj = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs));
|
||||
return export_obj;
|
||||
}
|
||||
9
net/enet_playdate.c
Normal file
9
net/enet_playdate.c
Normal file
@@ -0,0 +1,9 @@
|
||||
// enet_playdate.c - ENet stub for Playdate
|
||||
// ENet networking is not supported on Playdate, so this module returns an empty object.
|
||||
|
||||
#include "cell.h"
|
||||
|
||||
JSValue js_enet_use(JSContext *js) {
|
||||
// Return empty object - ENet is not available on Playdate
|
||||
return JS_NewObject(js);
|
||||
}
|
||||
327
net/http.c
Normal file
327
net/http.c
Normal file
@@ -0,0 +1,327 @@
|
||||
#include "cell.h"
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef unsigned char par_byte;
|
||||
|
||||
static void par_easycurl_init(unsigned int flags) {
|
||||
(void)flags;
|
||||
}
|
||||
|
||||
static int par_easycurl_to_memory(char const *url, par_byte **data, int *nbytes) {
|
||||
if (!url || !data || !nbytes) return 0;
|
||||
*data = NULL;
|
||||
*nbytes = 0;
|
||||
|
||||
int success = 0;
|
||||
HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL;
|
||||
wchar_t *wUrl = NULL, *wHost = NULL, *wPath = NULL;
|
||||
char *buffer = NULL;
|
||||
int bufferSize = 0;
|
||||
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, url, -1, NULL, 0);
|
||||
if (len <= 0) goto cleanup;
|
||||
wUrl = (wchar_t *)malloc(len * sizeof(wchar_t));
|
||||
if (!wUrl) goto cleanup;
|
||||
MultiByteToWideChar(CP_UTF8, 0, url, -1, wUrl, len);
|
||||
|
||||
URL_COMPONENTS urlComp;
|
||||
ZeroMemory(&urlComp, sizeof(urlComp));
|
||||
urlComp.dwStructSize = sizeof(urlComp);
|
||||
urlComp.dwHostNameLength = (DWORD)-1;
|
||||
urlComp.dwUrlPathLength = (DWORD)-1;
|
||||
|
||||
if (!WinHttpCrackUrl(wUrl, (DWORD)wcslen(wUrl), 0, &urlComp)) goto cleanup;
|
||||
|
||||
wHost = (wchar_t *)malloc((urlComp.dwHostNameLength + 1) * sizeof(wchar_t));
|
||||
if (!wHost) goto cleanup;
|
||||
wcsncpy(wHost, urlComp.lpszHostName, urlComp.dwHostNameLength);
|
||||
wHost[urlComp.dwHostNameLength] = L'\0';
|
||||
|
||||
wPath = (wchar_t *)malloc((urlComp.dwUrlPathLength + 1) * sizeof(wchar_t));
|
||||
if (!wPath) goto cleanup;
|
||||
wcsncpy(wPath, urlComp.lpszUrlPath, urlComp.dwUrlPathLength);
|
||||
wPath[urlComp.dwUrlPathLength] = L'\0';
|
||||
|
||||
hSession = WinHttpOpen(L"cell/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (!hSession) goto cleanup;
|
||||
|
||||
hConnect = WinHttpConnect(hSession, wHost, urlComp.nPort, 0);
|
||||
if (!hConnect) goto cleanup;
|
||||
|
||||
DWORD dwFlags = (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
|
||||
|
||||
hRequest = WinHttpOpenRequest(hConnect, L"GET", wPath, NULL,
|
||||
WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, dwFlags);
|
||||
if (!hRequest) goto cleanup;
|
||||
|
||||
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) goto cleanup;
|
||||
|
||||
if (!WinHttpReceiveResponse(hRequest, NULL)) goto cleanup;
|
||||
|
||||
DWORD dwStatusCode = 0;
|
||||
DWORD dwSize = sizeof(dwStatusCode);
|
||||
if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (dwStatusCode >= 400) goto cleanup;
|
||||
|
||||
buffer = (char *)malloc(1);
|
||||
if (!buffer) goto cleanup;
|
||||
buffer[0] = 0;
|
||||
|
||||
for (;;) {
|
||||
DWORD dwAvailable = 0;
|
||||
if (!WinHttpQueryDataAvailable(hRequest, &dwAvailable)) goto cleanup;
|
||||
if (dwAvailable == 0) break;
|
||||
|
||||
char *newBuffer = (char *)realloc(buffer, bufferSize + dwAvailable + 1);
|
||||
if (!newBuffer) goto cleanup;
|
||||
buffer = newBuffer;
|
||||
|
||||
DWORD dwDownloaded = 0;
|
||||
if (!WinHttpReadData(hRequest, buffer + bufferSize, dwAvailable, &dwDownloaded)) goto cleanup;
|
||||
bufferSize += dwDownloaded;
|
||||
buffer[bufferSize] = 0;
|
||||
}
|
||||
|
||||
*data = (par_byte *)buffer;
|
||||
*nbytes = bufferSize;
|
||||
success = 1;
|
||||
buffer = NULL;
|
||||
|
||||
cleanup:
|
||||
if (wUrl) free(wUrl);
|
||||
if (wHost) free(wHost);
|
||||
if (wPath) free(wPath);
|
||||
if (buffer) free(buffer);
|
||||
if (hRequest) WinHttpCloseHandle(hRequest);
|
||||
if (hConnect) WinHttpCloseHandle(hConnect);
|
||||
if (hSession) WinHttpCloseHandle(hSession);
|
||||
return success;
|
||||
}
|
||||
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
#include <emscripten/fetch.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef unsigned char par_byte;
|
||||
|
||||
static void par_easycurl_init(unsigned int flags) {
|
||||
(void)flags;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
par_byte *data;
|
||||
int nbytes;
|
||||
int complete;
|
||||
int success;
|
||||
} fetch_result;
|
||||
|
||||
static void on_fetch_success(emscripten_fetch_t *fetch) {
|
||||
fetch_result *res = (fetch_result *)fetch->userData;
|
||||
res->nbytes = fetch->numBytes;
|
||||
res->data = (par_byte *)malloc((size_t)res->nbytes + 1);
|
||||
if (!res->data) {
|
||||
res->success = 0;
|
||||
} else {
|
||||
memcpy(res->data, fetch->data, (size_t)res->nbytes);
|
||||
res->data[res->nbytes] = 0;
|
||||
res->success = 1;
|
||||
}
|
||||
res->complete = 1;
|
||||
emscripten_fetch_close(fetch);
|
||||
}
|
||||
|
||||
static void on_fetch_failure(emscripten_fetch_t *fetch) {
|
||||
fetch_result *res = (fetch_result *)fetch->userData;
|
||||
res->success = 0;
|
||||
res->complete = 1;
|
||||
emscripten_fetch_close(fetch);
|
||||
}
|
||||
|
||||
static int par_easycurl_to_memory(char const *url, par_byte **data, int *nbytes) {
|
||||
if (!url || !data || !nbytes) return 0;
|
||||
*data = NULL;
|
||||
*nbytes = 0;
|
||||
|
||||
fetch_result res = {0};
|
||||
emscripten_fetch_attr_t attr;
|
||||
emscripten_fetch_attr_init(&attr);
|
||||
strcpy(attr.requestMethod, "GET");
|
||||
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
|
||||
attr.userData = &res;
|
||||
attr.onsuccess = on_fetch_success;
|
||||
attr.onerror = on_fetch_failure;
|
||||
attr.attributes |= EMSCRIPTEN_FETCH_SYNCHRONOUS;
|
||||
|
||||
emscripten_fetch_t *fetch = emscripten_fetch(&attr, url);
|
||||
if (!fetch) return 0;
|
||||
|
||||
if (!res.complete || !res.success) return 0;
|
||||
|
||||
*data = res.data;
|
||||
*nbytes = res.nbytes;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CFNetwork/CFNetwork.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef unsigned char par_byte;
|
||||
|
||||
static void par_easycurl_init(unsigned int flags) {
|
||||
(void)flags;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
CFMutableDataRef data;
|
||||
int success;
|
||||
int complete;
|
||||
} cf_result;
|
||||
|
||||
static void cf_response_callback(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo) {
|
||||
cf_result *res = (cf_result *)clientCallBackInfo;
|
||||
if (type == kCFStreamEventHasBytesAvailable) {
|
||||
UInt8 buffer[4096];
|
||||
CFIndex bytesRead = CFReadStreamRead(stream, buffer, sizeof(buffer));
|
||||
if (bytesRead > 0) {
|
||||
CFDataAppendBytes(res->data, buffer, bytesRead);
|
||||
}
|
||||
} else if (type == kCFStreamEventEndEncountered) {
|
||||
res->success = 1;
|
||||
res->complete = 1;
|
||||
CFReadStreamClose(stream);
|
||||
} else if (type == kCFStreamEventErrorOccurred) {
|
||||
res->success = 0;
|
||||
res->complete = 1;
|
||||
CFReadStreamClose(stream);
|
||||
}
|
||||
}
|
||||
|
||||
static int par_easycurl_to_memory(char const *url, par_byte **data, int *nbytes) {
|
||||
if (!url || !data || !nbytes) return 0;
|
||||
*data = NULL;
|
||||
*nbytes = 0;
|
||||
|
||||
CFStringRef cfurl = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8);
|
||||
if (!cfurl) return 0;
|
||||
CFURLRef cfurlRef = CFURLCreateWithString(NULL, cfurl, NULL);
|
||||
CFRelease(cfurl);
|
||||
if (!cfurlRef) return 0;
|
||||
|
||||
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), cfurlRef, kCFHTTPVersion1_1);
|
||||
CFRelease(cfurlRef);
|
||||
if (!request) return 0;
|
||||
|
||||
CFReadStreamRef stream = CFReadStreamCreateForHTTPRequest(NULL, request);
|
||||
CFRelease(request);
|
||||
if (!stream) return 0;
|
||||
|
||||
cf_result res;
|
||||
res.data = CFDataCreateMutable(NULL, 0);
|
||||
res.success = 0;
|
||||
res.complete = 0;
|
||||
if (!res.data) {
|
||||
CFRelease(stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CFStreamClientContext ctx = {0, &res, NULL, NULL, NULL};
|
||||
CFOptionFlags events = kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
|
||||
if (!CFReadStreamSetClient(stream, events, cf_response_callback, &ctx)) {
|
||||
CFRelease(stream);
|
||||
CFRelease(res.data);
|
||||
return 0;
|
||||
}
|
||||
CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
if (!CFReadStreamOpen(stream)) {
|
||||
CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
CFRelease(stream);
|
||||
CFRelease(res.data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (!res.complete) {
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, false);
|
||||
}
|
||||
|
||||
CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
CFRelease(stream);
|
||||
|
||||
if (!res.success) {
|
||||
CFRelease(res.data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CFIndex len = CFDataGetLength(res.data);
|
||||
par_byte *bytes = (par_byte *)malloc((size_t)len + 1);
|
||||
if (!bytes) {
|
||||
CFRelease(res.data);
|
||||
return 0;
|
||||
}
|
||||
CFDataGetBytes(res.data, CFRangeMake(0, len), bytes);
|
||||
bytes[len] = 0;
|
||||
CFRelease(res.data);
|
||||
|
||||
*data = bytes;
|
||||
*nbytes = (int)len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#else
|
||||
#define PAR_EASYCURL_IMPLEMENTATION
|
||||
#include "par_easycurl.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
|
||||
static JSValue js_fetch_picoparser(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1 || !JS_IsString(argv[0]))
|
||||
return JS_ThrowTypeError(ctx, "fetch: URL string required");
|
||||
|
||||
const char *url = JS_ToCString(ctx, argv[0]);
|
||||
if (!url) return JS_ThrowTypeError(ctx, "fetch: invalid URL");
|
||||
|
||||
par_byte *data = NULL;
|
||||
int nbytes = 0;
|
||||
if (!par_easycurl_to_memory(url, &data, &nbytes)) {
|
||||
JS_FreeCString(ctx, url);
|
||||
return JS_ThrowTypeError(ctx, "fetch: failed to fetch URL");
|
||||
}
|
||||
|
||||
JS_FreeCString(ctx, url);
|
||||
|
||||
// Return a Blob wrapping the data
|
||||
JSValue blob = js_new_blob_stoned_copy(ctx, data, (size_t)nbytes);
|
||||
free(data); // par_easycurl allocates with malloc, so we free it
|
||||
return blob;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_http_funcs[] = {
|
||||
JS_CFUNC_DEF("fetch", 2, js_fetch_picoparser),
|
||||
};
|
||||
|
||||
JSValue js_http_use(JSContext *js) {
|
||||
par_easycurl_init(0); // Initialize platform HTTP backend
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, obj, js_http_funcs,
|
||||
sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
|
||||
return obj;
|
||||
}
|
||||
248
net/http_playdate.c
Normal file
248
net/http_playdate.c
Normal file
@@ -0,0 +1,248 @@
|
||||
// http_playdate.c - HTTP module for Playdate using Playdate Network API
|
||||
// Note: Playdate HTTP does not support SSL/HTTPS
|
||||
|
||||
#include "cell.h"
|
||||
#include "pd_api.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Global Playdate API pointers - defined in main_playdate.c
|
||||
extern const struct playdate_network *pd_network;
|
||||
extern const struct playdate_sys *pd_sys;
|
||||
|
||||
#if TARGET_EXTENSION
|
||||
|
||||
// Context for async HTTP fetch
|
||||
typedef struct {
|
||||
JSContext *js;
|
||||
HTTPConnection *conn;
|
||||
uint8_t *data;
|
||||
size_t data_len;
|
||||
size_t data_cap;
|
||||
int complete;
|
||||
int success;
|
||||
int status_code;
|
||||
} http_fetch_ctx;
|
||||
|
||||
static void http_response_callback(HTTPConnection *conn) {
|
||||
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
|
||||
if (!ctx) return;
|
||||
|
||||
ctx->status_code = pd_network->http->getResponseStatus(conn);
|
||||
|
||||
// Read all available data
|
||||
while (1) {
|
||||
size_t avail = pd_network->http->getBytesAvailable(conn);
|
||||
if (avail == 0) break;
|
||||
|
||||
// Grow buffer if needed
|
||||
if (ctx->data_len + avail > ctx->data_cap) {
|
||||
size_t new_cap = ctx->data_cap * 2;
|
||||
if (new_cap < ctx->data_len + avail) new_cap = ctx->data_len + avail + 4096;
|
||||
uint8_t *new_data = realloc(ctx->data, new_cap);
|
||||
if (!new_data) {
|
||||
ctx->success = 0;
|
||||
ctx->complete = 1;
|
||||
return;
|
||||
}
|
||||
ctx->data = new_data;
|
||||
ctx->data_cap = new_cap;
|
||||
}
|
||||
|
||||
int read = pd_network->http->read(conn, ctx->data + ctx->data_len, (unsigned int)avail);
|
||||
if (read < 0) {
|
||||
ctx->success = 0;
|
||||
ctx->complete = 1;
|
||||
return;
|
||||
}
|
||||
ctx->data_len += read;
|
||||
}
|
||||
}
|
||||
|
||||
static void http_complete_callback(HTTPConnection *conn) {
|
||||
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
|
||||
if (!ctx) return;
|
||||
|
||||
// Read any remaining data
|
||||
http_response_callback(conn);
|
||||
|
||||
ctx->success = (ctx->status_code >= 200 && ctx->status_code < 400);
|
||||
ctx->complete = 1;
|
||||
}
|
||||
|
||||
static void http_closed_callback(HTTPConnection *conn) {
|
||||
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
|
||||
if (!ctx) return;
|
||||
ctx->complete = 1;
|
||||
}
|
||||
|
||||
// Parse URL into host, port, path, and check if HTTPS
|
||||
static int parse_url(const char *url, char **host, int *port, char **path, int *is_https) {
|
||||
*host = NULL;
|
||||
*path = NULL;
|
||||
*port = 80;
|
||||
*is_https = 0;
|
||||
|
||||
const char *p = url;
|
||||
|
||||
// Check scheme
|
||||
if (strncmp(p, "https://", 8) == 0) {
|
||||
*is_https = 1;
|
||||
*port = 443;
|
||||
p += 8;
|
||||
} else if (strncmp(p, "http://", 7) == 0) {
|
||||
p += 7;
|
||||
} else {
|
||||
return -1; // Invalid scheme
|
||||
}
|
||||
|
||||
// Find end of host (either :, /, or end of string)
|
||||
const char *host_start = p;
|
||||
const char *host_end = p;
|
||||
while (*host_end && *host_end != ':' && *host_end != '/') host_end++;
|
||||
|
||||
size_t host_len = host_end - host_start;
|
||||
*host = malloc(host_len + 1);
|
||||
if (!*host) return -1;
|
||||
memcpy(*host, host_start, host_len);
|
||||
(*host)[host_len] = '\0';
|
||||
|
||||
p = host_end;
|
||||
|
||||
// Check for port
|
||||
if (*p == ':') {
|
||||
p++;
|
||||
*port = atoi(p);
|
||||
while (*p && *p != '/') p++;
|
||||
}
|
||||
|
||||
// Get path (default to "/" if none)
|
||||
if (*p == '/') {
|
||||
*path = strdup(p);
|
||||
} else {
|
||||
*path = strdup("/");
|
||||
}
|
||||
|
||||
if (!*path) {
|
||||
free(*host);
|
||||
*host = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
|
||||
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1 || !JS_IsString(argv[0]))
|
||||
return JS_ThrowTypeError(ctx, "fetch: URL string required");
|
||||
|
||||
if (!pd_network || !pd_network->http) {
|
||||
return JS_ThrowInternalError(ctx, "fetch: Playdate network API not available");
|
||||
}
|
||||
|
||||
const char *url = JS_ToCString(ctx, argv[0]);
|
||||
if (!url) return JS_ThrowTypeError(ctx, "fetch: invalid URL");
|
||||
|
||||
char *host = NULL;
|
||||
char *path = NULL;
|
||||
int port = 80;
|
||||
int is_https = 0;
|
||||
|
||||
if (parse_url(url, &host, &port, &path, &is_https) < 0) {
|
||||
JS_FreeCString(ctx, url);
|
||||
return JS_ThrowTypeError(ctx, "fetch: failed to parse URL");
|
||||
}
|
||||
|
||||
JS_FreeCString(ctx, url);
|
||||
|
||||
// Playdate doesn't support HTTPS
|
||||
if (is_https) {
|
||||
free(host);
|
||||
free(path);
|
||||
return JS_ThrowTypeError(ctx, "fetch: HTTPS not supported on Playdate");
|
||||
}
|
||||
|
||||
// Create HTTP connection
|
||||
HTTPConnection *conn = pd_network->http->newConnection(host, port, 0);
|
||||
free(host);
|
||||
|
||||
if (!conn) {
|
||||
free(path);
|
||||
return JS_ThrowInternalError(ctx, "fetch: failed to create connection");
|
||||
}
|
||||
|
||||
// Set up context
|
||||
http_fetch_ctx fetch_ctx = {0};
|
||||
fetch_ctx.js = ctx;
|
||||
fetch_ctx.conn = conn;
|
||||
fetch_ctx.data = malloc(4096);
|
||||
fetch_ctx.data_cap = 4096;
|
||||
fetch_ctx.data_len = 0;
|
||||
fetch_ctx.complete = 0;
|
||||
fetch_ctx.success = 0;
|
||||
|
||||
if (!fetch_ctx.data) {
|
||||
pd_network->http->release(conn);
|
||||
free(path);
|
||||
return JS_ThrowInternalError(ctx, "fetch: malloc failed");
|
||||
}
|
||||
|
||||
pd_network->http->setUserdata(conn, &fetch_ctx);
|
||||
pd_network->http->setResponseCallback(conn, http_response_callback);
|
||||
pd_network->http->setRequestCompleteCallback(conn, http_complete_callback);
|
||||
pd_network->http->setConnectionClosedCallback(conn, http_closed_callback);
|
||||
pd_network->http->setConnectTimeout(conn, 30000); // 30 second timeout
|
||||
pd_network->http->setReadTimeout(conn, 30000);
|
||||
|
||||
// Start the GET request
|
||||
PDNetErr err = pd_network->http->get(conn, path, NULL, 0);
|
||||
free(path);
|
||||
|
||||
if (err != NET_OK) {
|
||||
free(fetch_ctx.data);
|
||||
pd_network->http->release(conn);
|
||||
return JS_ThrowInternalError(ctx, "fetch: request failed with error %d", err);
|
||||
}
|
||||
|
||||
// Poll until complete (blocking)
|
||||
// Note: This is a simple blocking implementation. In a real game,
|
||||
// you'd want to use async callbacks instead.
|
||||
while (!fetch_ctx.complete) {
|
||||
// Small delay to avoid busy-waiting
|
||||
pd_sys->delay(10);
|
||||
}
|
||||
|
||||
pd_network->http->close(conn);
|
||||
pd_network->http->release(conn);
|
||||
|
||||
if (!fetch_ctx.success) {
|
||||
free(fetch_ctx.data);
|
||||
return JS_ThrowTypeError(ctx, "fetch: request failed (status %d)", fetch_ctx.status_code);
|
||||
}
|
||||
|
||||
// Return a Blob wrapping the data
|
||||
JSValue blob = js_new_blob_stoned_copy(ctx, fetch_ctx.data, fetch_ctx.data_len);
|
||||
free(fetch_ctx.data);
|
||||
return blob;
|
||||
}
|
||||
|
||||
#else
|
||||
// Simulator/non-extension build - provide stub
|
||||
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
return JS_ThrowInternalError(ctx, "fetch: not available in simulator");
|
||||
}
|
||||
#endif
|
||||
|
||||
static const JSCFunctionListEntry js_http_funcs[] = {
|
||||
JS_CFUNC_DEF("fetch", 2, js_fetch_playdate),
|
||||
};
|
||||
|
||||
JSValue js_http_use(JSContext *js) {
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, obj, js_http_funcs,
|
||||
sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
|
||||
return obj;
|
||||
}
|
||||
620
net/socket.c
Normal file
620
net/socket.c
Normal file
@@ -0,0 +1,620 @@
|
||||
#include "cell.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#define close closesocket
|
||||
#define SHUT_RD SD_RECEIVE
|
||||
#define SHUT_WR SD_SEND
|
||||
#define SHUT_RDWR SD_BOTH
|
||||
#ifndef AF_UNIX
|
||||
#define AF_UNIX 1
|
||||
#endif
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Helper to convert JS value to file descriptor
|
||||
static int js2fd(JSContext *ctx, JSValueConst val)
|
||||
{
|
||||
int fd;
|
||||
if (JS_ToInt32(ctx, &fd, val) < 0) {
|
||||
JS_ThrowTypeError(ctx, "Expected file descriptor number");
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
// SOCKET FUNCTIONS
|
||||
|
||||
JSC_CCALL(socket_getaddrinfo,
|
||||
const char *node = NULL;
|
||||
const char *service = NULL;
|
||||
struct addrinfo hints, *res;
|
||||
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if (!JS_IsNull(argv[0]) && !JS_IsNull(argv[0]))
|
||||
node = JS_ToCString(js, argv[0]);
|
||||
|
||||
if (!JS_IsNull(argv[1]) && !JS_IsNull(argv[1]))
|
||||
service = JS_ToCString(js, argv[1]);
|
||||
|
||||
// Parse optional hints object
|
||||
if (argc > 2 && JS_IsObject(argv[2])) {
|
||||
JSValue val;
|
||||
|
||||
val = JS_GetPropertyStr(js, argv[2], "family");
|
||||
if (!JS_IsNull(val)) {
|
||||
const char *family = JS_ToCString(js, val);
|
||||
if (strcmp(family, "AF_INET") == 0) hints.ai_family = AF_INET;
|
||||
else if (strcmp(family, "AF_INET6") == 0) hints.ai_family = AF_INET6;
|
||||
JS_FreeCString(js, family);
|
||||
}
|
||||
JS_FreeValue(js, val);
|
||||
|
||||
val = JS_GetPropertyStr(js, argv[2], "socktype");
|
||||
if (!JS_IsNull(val)) {
|
||||
const char *socktype = JS_ToCString(js, val);
|
||||
if (strcmp(socktype, "SOCK_STREAM") == 0) hints.ai_socktype = SOCK_STREAM;
|
||||
else if (strcmp(socktype, "SOCK_DGRAM") == 0) hints.ai_socktype = SOCK_DGRAM;
|
||||
JS_FreeCString(js, socktype);
|
||||
}
|
||||
JS_FreeValue(js, val);
|
||||
|
||||
val = JS_GetPropertyStr(js, argv[2], "flags");
|
||||
if (!JS_IsNull(val)) {
|
||||
hints.ai_flags = js2number(js, val);
|
||||
}
|
||||
JS_FreeValue(js, val);
|
||||
|
||||
val = JS_GetPropertyStr(js, argv[2], "passive");
|
||||
if (JS_ToBool(js, val)) {
|
||||
hints.ai_flags |= AI_PASSIVE;
|
||||
}
|
||||
JS_FreeValue(js, val);
|
||||
}
|
||||
|
||||
int status = getaddrinfo(node, service, &hints, &res);
|
||||
|
||||
if (node) JS_FreeCString(js, node);
|
||||
if (service) JS_FreeCString(js, service);
|
||||
|
||||
if (status != 0) {
|
||||
return JS_ThrowReferenceError(js, "getaddrinfo error: %s", gai_strerror(status));
|
||||
}
|
||||
|
||||
// Convert linked list to JS array
|
||||
ret = JS_NewArray(js);
|
||||
int idx = 0;
|
||||
for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
|
||||
JSValue info = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, info, "family", JS_NewInt32(js, p->ai_family));
|
||||
JS_SetPropertyStr(js, info, "socktype", JS_NewInt32(js, p->ai_socktype));
|
||||
JS_SetPropertyStr(js, info, "protocol", JS_NewInt32(js, p->ai_protocol));
|
||||
|
||||
// Convert address to string
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
void *addr;
|
||||
if (p->ai_family == AF_INET) {
|
||||
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
|
||||
addr = &(ipv4->sin_addr);
|
||||
} else {
|
||||
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
|
||||
addr = &(ipv6->sin6_addr);
|
||||
}
|
||||
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
|
||||
JS_SetPropertyStr(js, info, "address", JS_NewString(js, ipstr));
|
||||
|
||||
// Store the addrinfo for later use
|
||||
struct addrinfo *copy = malloc(sizeof(struct addrinfo));
|
||||
memcpy(copy, p, sizeof(struct addrinfo));
|
||||
copy->ai_addr = malloc(p->ai_addrlen);
|
||||
memcpy(copy->ai_addr, p->ai_addr, p->ai_addrlen);
|
||||
copy->ai_next = NULL;
|
||||
if (p->ai_canonname) {
|
||||
copy->ai_canonname = strdup(p->ai_canonname);
|
||||
}
|
||||
|
||||
// Store the addrinfo pointer as an internal property
|
||||
// We'll need to handle this differently since we can't wrap it
|
||||
// For now, we'll skip storing the raw addrinfo
|
||||
JS_SetPropertyUint32(js, ret, idx++, info);
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_socket,
|
||||
int domain = AF_INET;
|
||||
int type = SOCK_STREAM;
|
||||
int protocol = 0;
|
||||
|
||||
// Parse domain
|
||||
if (JS_IsString(argv[0])) {
|
||||
const char *domain_str = JS_ToCString(js, argv[0]);
|
||||
if (strcmp(domain_str, "AF_INET") == 0) domain = AF_INET;
|
||||
else if (strcmp(domain_str, "AF_INET6") == 0) domain = AF_INET6;
|
||||
else if (strcmp(domain_str, "AF_UNIX") == 0) domain = AF_UNIX;
|
||||
JS_FreeCString(js, domain_str);
|
||||
} else if (JS_IsNumber(argv[0])) {
|
||||
domain = js2number(js, argv[0]);
|
||||
}
|
||||
|
||||
// Parse type
|
||||
if (argc > 1) {
|
||||
if (JS_IsString(argv[1])) {
|
||||
const char *type_str = JS_ToCString(js, argv[1]);
|
||||
if (strcmp(type_str, "SOCK_STREAM") == 0) type = SOCK_STREAM;
|
||||
else if (strcmp(type_str, "SOCK_DGRAM") == 0) type = SOCK_DGRAM;
|
||||
JS_FreeCString(js, type_str);
|
||||
} else if (JS_IsNumber(argv[1])) {
|
||||
type = js2number(js, argv[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse protocol
|
||||
if (argc > 2) {
|
||||
protocol = js2number(js, argv[2]);
|
||||
}
|
||||
|
||||
int sockfd = socket(domain, type, protocol);
|
||||
if (sockfd < 0) {
|
||||
return JS_ThrowReferenceError(js, "socket failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
return JS_NewInt32(js, sockfd);
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_bind,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
// For now, we'll only support manual address parsing
|
||||
{
|
||||
// Try to parse address and port manually
|
||||
const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[1], "address"));
|
||||
int port = js2number(js, JS_GetPropertyStr(js, argv[1], "port"));
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) {
|
||||
JS_FreeCString(js, addr_str);
|
||||
return JS_ThrowReferenceError(js, "Invalid address");
|
||||
}
|
||||
JS_FreeCString(js, addr_str);
|
||||
|
||||
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
return JS_ThrowReferenceError(js, "bind failed: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_connect,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
// For now, we'll only support manual address parsing
|
||||
{
|
||||
// Try to parse address and port manually
|
||||
const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[1], "address"));
|
||||
int port = js2number(js, JS_GetPropertyStr(js, argv[1], "port"));
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) {
|
||||
JS_FreeCString(js, addr_str);
|
||||
return JS_ThrowReferenceError(js, "Invalid address");
|
||||
}
|
||||
JS_FreeCString(js, addr_str);
|
||||
|
||||
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
return JS_ThrowReferenceError(js, "connect failed: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_listen,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
int backlog = 10;
|
||||
if (argc > 1) {
|
||||
backlog = js2number(js, argv[1]);
|
||||
}
|
||||
|
||||
if (listen(sockfd, backlog) < 0) {
|
||||
return JS_ThrowReferenceError(js, "listen failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_accept,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
struct sockaddr_storage their_addr;
|
||||
socklen_t addr_size = sizeof their_addr;
|
||||
|
||||
int new_sockfd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);
|
||||
if (new_sockfd < 0) {
|
||||
return JS_ThrowReferenceError(js, "accept failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
ret = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, ret, "socket", JS_NewInt32(js, new_sockfd));
|
||||
|
||||
// Get peer address info
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
int port;
|
||||
if (their_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&their_addr;
|
||||
inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
|
||||
port = ntohs(s->sin_port);
|
||||
} else {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&their_addr;
|
||||
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
|
||||
port = ntohs(s->sin6_port);
|
||||
}
|
||||
|
||||
JSValue addr_info = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, addr_info, "address", JS_NewString(js, ipstr));
|
||||
JS_SetPropertyStr(js, addr_info, "port", JS_NewInt32(js, port));
|
||||
JS_SetPropertyStr(js, ret, "address", addr_info);
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_send,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
size_t len;
|
||||
ssize_t sent;
|
||||
int flags = 0;
|
||||
|
||||
if (argc > 2) {
|
||||
flags = js2number(js, argv[2]);
|
||||
}
|
||||
|
||||
if (JS_IsString(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
sent = send(sockfd, data, len, flags);
|
||||
JS_FreeCString(js, data);
|
||||
} else {
|
||||
unsigned char *data = js_get_blob_data(js, &len, argv[1]);
|
||||
if (data == -1)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (len == 0)
|
||||
return JS_ThrowReferenceError(js, "No data to send");
|
||||
|
||||
sent = send(sockfd, (const char *)data, len, flags);
|
||||
}
|
||||
|
||||
if (sent < 0) {
|
||||
return JS_ThrowReferenceError(js, "send failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
return JS_NewInt64(js, sent);
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_recv,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
size_t len = 4096;
|
||||
if (argc > 1) {
|
||||
len = js2number(js, argv[1]);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (argc > 2) {
|
||||
flags = js2number(js, argv[2]);
|
||||
}
|
||||
|
||||
void *buf = malloc(len);
|
||||
if (!buf) {
|
||||
return JS_ThrowReferenceError(js, "malloc failed");
|
||||
}
|
||||
|
||||
ssize_t received = recv(sockfd, buf, len, flags);
|
||||
if (received < 0) {
|
||||
free(buf);
|
||||
return JS_ThrowReferenceError(js, "recv failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
ret = js_new_blob_stoned_copy(js, buf, received);
|
||||
free(buf);
|
||||
return ret;
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_sendto,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
size_t len;
|
||||
ssize_t sent;
|
||||
int flags = 0;
|
||||
|
||||
if (argc > 3) {
|
||||
flags = js2number(js, argv[3]);
|
||||
}
|
||||
|
||||
// Get destination address
|
||||
struct sockaddr *to_addr;
|
||||
socklen_t to_len;
|
||||
|
||||
// For now, we'll only support manual address parsing
|
||||
{
|
||||
// Try to parse address and port manually
|
||||
const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[2], "address"));
|
||||
int port = js2number(js, JS_GetPropertyStr(js, argv[2], "port"));
|
||||
|
||||
static struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) {
|
||||
JS_FreeCString(js, addr_str);
|
||||
return JS_ThrowReferenceError(js, "Invalid address");
|
||||
}
|
||||
JS_FreeCString(js, addr_str);
|
||||
|
||||
to_addr = (struct sockaddr *)&addr;
|
||||
to_len = sizeof(addr);
|
||||
}
|
||||
|
||||
if (JS_IsString(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
sent = sendto(sockfd, data, len, flags, to_addr, to_len);
|
||||
JS_FreeCString(js, data);
|
||||
} else {
|
||||
unsigned char *data = js_get_blob_data(js, &len, argv[1]);
|
||||
if (data == (unsigned char *)-1) {
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
if (len == 0) {
|
||||
return JS_ThrowReferenceError(js, "No data to send");
|
||||
}
|
||||
sent = sendto(sockfd, (const char *)data, len, flags, to_addr, to_len);
|
||||
}
|
||||
|
||||
if (sent < 0) {
|
||||
return JS_ThrowReferenceError(js, "sendto failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
return JS_NewInt64(js, sent);
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_recvfrom,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
size_t len = 4096;
|
||||
if (argc > 1) {
|
||||
len = js2number(js, argv[1]);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (argc > 2) {
|
||||
flags = js2number(js, argv[2]);
|
||||
}
|
||||
|
||||
void *buf = malloc(len);
|
||||
if (!buf) {
|
||||
return JS_ThrowReferenceError(js, "malloc failed");
|
||||
}
|
||||
|
||||
struct sockaddr_storage from_addr;
|
||||
socklen_t from_len = sizeof from_addr;
|
||||
|
||||
ssize_t received = recvfrom(sockfd, buf, len, flags,
|
||||
(struct sockaddr *)&from_addr, &from_len);
|
||||
if (received < 0) {
|
||||
free(buf);
|
||||
return JS_ThrowReferenceError(js, "recvfrom failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
ret = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, ret, "data", js_new_blob_stoned_copy(js, buf, received));
|
||||
free(buf);
|
||||
|
||||
// Get source address info
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
int port;
|
||||
if (from_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&from_addr;
|
||||
inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
|
||||
port = ntohs(s->sin_port);
|
||||
} else {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&from_addr;
|
||||
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
|
||||
port = ntohs(s->sin6_port);
|
||||
}
|
||||
|
||||
JSValue addr_info = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, addr_info, "address", JS_NewString(js, ipstr));
|
||||
JS_SetPropertyStr(js, addr_info, "port", JS_NewInt32(js, port));
|
||||
JS_SetPropertyStr(js, ret, "address", addr_info);
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_shutdown,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
int how = SHUT_RDWR;
|
||||
if (argc > 1) {
|
||||
how = js2number(js, argv[1]);
|
||||
}
|
||||
|
||||
if (shutdown(sockfd, how) < 0) {
|
||||
return JS_ThrowReferenceError(js, "shutdown failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_getpeername,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof addr;
|
||||
|
||||
if (getpeername(sockfd, (struct sockaddr *)&addr, &len) < 0) {
|
||||
return JS_ThrowReferenceError(js, "getpeername failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
int port;
|
||||
if (addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
|
||||
inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
|
||||
port = ntohs(s->sin_port);
|
||||
} else {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
|
||||
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
|
||||
port = ntohs(s->sin6_port);
|
||||
}
|
||||
|
||||
ret = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, ret, "address", JS_NewString(js, ipstr));
|
||||
JS_SetPropertyStr(js, ret, "port", JS_NewInt32(js, port));
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_gethostname,
|
||||
char hostname[256];
|
||||
if (gethostname(hostname, sizeof(hostname)) < 0) {
|
||||
return JS_ThrowReferenceError(js, "gethostname failed: %s", strerror(errno));
|
||||
}
|
||||
return JS_NewString(js, hostname);
|
||||
)
|
||||
|
||||
|
||||
JSC_CCALL(socket_gai_strerror,
|
||||
int errcode = js2number(js, argv[0]);
|
||||
return JS_NewString(js, gai_strerror(errcode));
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_setsockopt,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
int level = SOL_SOCKET;
|
||||
int optname = 0;
|
||||
|
||||
// Parse level
|
||||
if (JS_IsString(argv[1])) {
|
||||
const char *level_str = JS_ToCString(js, argv[1]);
|
||||
if (strcmp(level_str, "SOL_SOCKET") == 0) level = SOL_SOCKET;
|
||||
else if (strcmp(level_str, "IPPROTO_TCP") == 0) level = IPPROTO_TCP;
|
||||
else if (strcmp(level_str, "IPPROTO_IP") == 0) level = IPPROTO_IP;
|
||||
else if (strcmp(level_str, "IPPROTO_IPV6") == 0) level = IPPROTO_IPV6;
|
||||
JS_FreeCString(js, level_str);
|
||||
} else {
|
||||
level = js2number(js, argv[1]);
|
||||
}
|
||||
|
||||
// Parse option name
|
||||
if (JS_IsString(argv[2])) {
|
||||
const char *opt_str = JS_ToCString(js, argv[2]);
|
||||
if (strcmp(opt_str, "SO_REUSEADDR") == 0) optname = SO_REUSEADDR;
|
||||
else if (strcmp(opt_str, "SO_KEEPALIVE") == 0) optname = SO_KEEPALIVE;
|
||||
else if (strcmp(opt_str, "SO_BROADCAST") == 0) optname = SO_BROADCAST;
|
||||
JS_FreeCString(js, opt_str);
|
||||
} else {
|
||||
optname = js2number(js, argv[2]);
|
||||
}
|
||||
|
||||
// Parse option value
|
||||
if (JS_IsBool(argv[3])) {
|
||||
int optval = JS_ToBool(js, argv[3]);
|
||||
if (setsockopt(sockfd, level, optname, &optval, sizeof(optval)) < 0) {
|
||||
return JS_ThrowReferenceError(js, "setsockopt failed: %s", strerror(errno));
|
||||
}
|
||||
} else if (JS_IsNumber(argv[3])) {
|
||||
int optval = js2number(js, argv[3]);
|
||||
if (setsockopt(sockfd, level, optname, &optval, sizeof(optval)) < 0) {
|
||||
return JS_ThrowReferenceError(js, "setsockopt failed: %s", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
return JS_ThrowTypeError(js, "Invalid option value");
|
||||
}
|
||||
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_close,
|
||||
int sockfd = js2fd(js, argv[0]);
|
||||
if (sockfd < 0) return JS_EXCEPTION;
|
||||
|
||||
if (close(sockfd) != 0)
|
||||
return JS_ThrowReferenceError(js, "close failed: %s", strerror(errno));
|
||||
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_socket_funcs[] = {
|
||||
MIST_FUNC_DEF(socket, getaddrinfo, 3),
|
||||
MIST_FUNC_DEF(socket, socket, 3),
|
||||
MIST_FUNC_DEF(socket, bind, 2),
|
||||
MIST_FUNC_DEF(socket, connect, 2),
|
||||
MIST_FUNC_DEF(socket, listen, 2),
|
||||
MIST_FUNC_DEF(socket, accept, 1),
|
||||
MIST_FUNC_DEF(socket, send, 3),
|
||||
MIST_FUNC_DEF(socket, recv, 3),
|
||||
MIST_FUNC_DEF(socket, sendto, 4),
|
||||
MIST_FUNC_DEF(socket, recvfrom, 3),
|
||||
MIST_FUNC_DEF(socket, shutdown, 2),
|
||||
MIST_FUNC_DEF(socket, getpeername, 1),
|
||||
MIST_FUNC_DEF(socket, gethostname, 0),
|
||||
MIST_FUNC_DEF(socket, gai_strerror, 1),
|
||||
MIST_FUNC_DEF(socket, setsockopt, 4),
|
||||
MIST_FUNC_DEF(socket, close, 1),
|
||||
};
|
||||
|
||||
JSValue js_socket_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs));
|
||||
|
||||
// Add constants
|
||||
JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC));
|
||||
JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, AF_INET));
|
||||
JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, AF_INET6));
|
||||
JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, AF_UNIX));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM));
|
||||
JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, SHUT_RD));
|
||||
JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, SHUT_WR));
|
||||
JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
|
||||
JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
|
||||
|
||||
return mod;
|
||||
}
|
||||
115
net/socket_playdate.c
Normal file
115
net/socket_playdate.c
Normal file
@@ -0,0 +1,115 @@
|
||||
// socket_playdate.c - Socket stub for Playdate
|
||||
// Raw sockets are not directly supported on Playdate.
|
||||
// Use the http module for HTTP requests or enet for game networking.
|
||||
|
||||
#include "cell.h"
|
||||
|
||||
// All socket functions throw "not supported" errors
|
||||
|
||||
JSC_CCALL(socket_getaddrinfo,
|
||||
return JS_ThrowInternalError(js, "socket.getaddrinfo: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_socket,
|
||||
return JS_ThrowInternalError(js, "socket.socket: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_bind,
|
||||
return JS_ThrowInternalError(js, "socket.bind: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_connect,
|
||||
return JS_ThrowInternalError(js, "socket.connect: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_listen,
|
||||
return JS_ThrowInternalError(js, "socket.listen: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_accept,
|
||||
return JS_ThrowInternalError(js, "socket.accept: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_send,
|
||||
return JS_ThrowInternalError(js, "socket.send: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_recv,
|
||||
return JS_ThrowInternalError(js, "socket.recv: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_sendto,
|
||||
return JS_ThrowInternalError(js, "socket.sendto: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_recvfrom,
|
||||
return JS_ThrowInternalError(js, "socket.recvfrom: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_shutdown,
|
||||
return JS_ThrowInternalError(js, "socket.shutdown: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_getpeername,
|
||||
return JS_ThrowInternalError(js, "socket.getpeername: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_gethostname,
|
||||
return JS_NewString(js, "playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_gai_strerror,
|
||||
return JS_NewString(js, "not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_setsockopt,
|
||||
return JS_ThrowInternalError(js, "socket.setsockopt: not supported on Playdate");
|
||||
)
|
||||
|
||||
JSC_CCALL(socket_close,
|
||||
return JS_ThrowInternalError(js, "socket.close: not supported on Playdate");
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_socket_funcs[] = {
|
||||
MIST_FUNC_DEF(socket, getaddrinfo, 3),
|
||||
MIST_FUNC_DEF(socket, socket, 3),
|
||||
MIST_FUNC_DEF(socket, bind, 2),
|
||||
MIST_FUNC_DEF(socket, connect, 2),
|
||||
MIST_FUNC_DEF(socket, listen, 2),
|
||||
MIST_FUNC_DEF(socket, accept, 1),
|
||||
MIST_FUNC_DEF(socket, send, 3),
|
||||
MIST_FUNC_DEF(socket, recv, 3),
|
||||
MIST_FUNC_DEF(socket, sendto, 4),
|
||||
MIST_FUNC_DEF(socket, recvfrom, 3),
|
||||
MIST_FUNC_DEF(socket, shutdown, 2),
|
||||
MIST_FUNC_DEF(socket, getpeername, 1),
|
||||
MIST_FUNC_DEF(socket, gethostname, 0),
|
||||
MIST_FUNC_DEF(socket, gai_strerror, 1),
|
||||
MIST_FUNC_DEF(socket, setsockopt, 4),
|
||||
MIST_FUNC_DEF(socket, close, 1),
|
||||
};
|
||||
|
||||
JSValue js_socket_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs));
|
||||
|
||||
// Add constants (even though they won't be useful)
|
||||
JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, 2));
|
||||
JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, 10));
|
||||
JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, 1));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, 1));
|
||||
JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, 2));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, 1));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, 1));
|
||||
JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, 2));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, 1));
|
||||
JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, 2));
|
||||
|
||||
return mod;
|
||||
}
|
||||
Reference in New Issue
Block a user