add return to callback send
This commit is contained in:
@@ -233,10 +233,10 @@ tests = [
|
||||
'spawn_actor',
|
||||
'empty',
|
||||
'nota',
|
||||
'enet',
|
||||
'wota',
|
||||
'portalspawner',
|
||||
'overling'
|
||||
'overling',
|
||||
'send'
|
||||
]
|
||||
|
||||
foreach file : tests
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
(function engine() {
|
||||
prosperon.DOC = Symbol('+documentation+') // Symbol for documentation references
|
||||
|
||||
prosperon.id = 'newguy';
|
||||
|
||||
var listeners = new Map()
|
||||
|
||||
prosperon.on = function(type, callback) {
|
||||
@@ -127,7 +129,7 @@ function bare_load(file) {
|
||||
var res_cache = {}
|
||||
|
||||
function console_rec(category, priority, line, file, msg) {
|
||||
return `${file}:${line}: [${category} ${priority}]: ${msg}\n`
|
||||
return `[${prosperon.id.substring(0,6)}] ${file}:${line}: [${category} ${priority}]: ${msg}\n`
|
||||
}
|
||||
|
||||
io.mkdir('.prosperon')
|
||||
@@ -522,11 +524,33 @@ js.eval(DOCPATH, script)()
|
||||
var enet = use('enet')
|
||||
var util = use('util')
|
||||
var math = use('math')
|
||||
var crypto = use('crypto')
|
||||
|
||||
var REPLY = Symbol()
|
||||
var REPLYCC = Symbol()
|
||||
var ACTORID = Symbol()
|
||||
var PEER = Symbol()
|
||||
|
||||
var ar = 5 // seconds before reclamation
|
||||
|
||||
/*
|
||||
When an actor object like $_ is passed to another machine or function, it appears like this
|
||||
id: local to the machine. Currently, the local port it is listening on
|
||||
address: the IP address of the portal that connects it, if set
|
||||
port: the public port of the portal that connects it, if set
|
||||
|
||||
There is a map of actor.id -> peer, so messages can be sent.
|
||||
*/
|
||||
|
||||
/*
|
||||
Each actor has an id. When an actor object is received, that is essentially just an id. The method to reach the id is in routing tables local to the actor that has obtained the object. If actor.id === prosperon.id, it's localhost.
|
||||
|
||||
Currently, each actor has an enet peer, so we're focused on that. Eventually, there might also be a thread local mailbox.
|
||||
*/
|
||||
|
||||
var $_ = {}
|
||||
|
||||
$_.random = math.rand
|
||||
|
||||
$_.random = crypto.random;
|
||||
$_.clock = function(fn)
|
||||
{
|
||||
return os.now()
|
||||
@@ -536,7 +560,7 @@ var underlings = new Set()
|
||||
var overling = undefined
|
||||
|
||||
var host = enet.create_host({
|
||||
address:"127.0.0.1", // or any address like "x.x.x.x", or "broadcast" for 255.255.255.255 and "any" for o0
|
||||
address:"127.0.0.1",
|
||||
port:0,
|
||||
channels:0,
|
||||
incoming_bandwidth:0,
|
||||
@@ -545,12 +569,16 @@ var host = enet.create_host({
|
||||
|
||||
globalThis.$_ = $_
|
||||
|
||||
var portal = undefined
|
||||
|
||||
var receive_fn
|
||||
|
||||
var peers = {} // mapping of guids to peers
|
||||
var greeters = {} // mapping of underling guids to their system callback functions
|
||||
var peer2id = new WeakMap() // local bound peers to relevant id
|
||||
|
||||
$_.connection = function(callback, actor, config) {
|
||||
var peer = actor.peer;
|
||||
var peer = peers[actor.id]
|
||||
if (!peer) throw new Error(`Cannot get information from actor ${json.encode(actor)}`)
|
||||
|
||||
callback({
|
||||
latency: peer.rtt,
|
||||
bandwidth: {
|
||||
@@ -574,28 +602,51 @@ $_.connection = function(callback, actor, config) {
|
||||
};
|
||||
|
||||
var portal = undefined
|
||||
var pppfn
|
||||
$_.portal = function(fn, port)
|
||||
{
|
||||
if (portal)
|
||||
throw new Error(`Already started a portal listening on ${portal.port}`)
|
||||
|
||||
console.log(`starting a portal on port ${port}`)
|
||||
if (!port)
|
||||
throw new Error("Requires a valid port.");
|
||||
|
||||
$_.start(e => {
|
||||
switch(e.type) {
|
||||
case "actor_started":
|
||||
portal = e.actor
|
||||
break
|
||||
}
|
||||
portal = e
|
||||
}, undefined, {
|
||||
port
|
||||
});
|
||||
portal = enet.create_host({
|
||||
address: "any", // any can connect
|
||||
port,
|
||||
})
|
||||
|
||||
pppfn = fn
|
||||
}
|
||||
|
||||
function portal_fn(e)
|
||||
{
|
||||
switch (e.type) {
|
||||
case "connect":
|
||||
console.log('portal got connect')
|
||||
break
|
||||
|
||||
case "disconnect":
|
||||
console.log('portal got disconnect')
|
||||
break
|
||||
|
||||
case "receive":
|
||||
pppfn(e.data)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var peer2contact = new WeakMap()
|
||||
$_.contact = function(callback, record)
|
||||
{
|
||||
if (!callback) throw new Error('Contact requires a callback function')
|
||||
console.log(`connecting to ${json.encode(record)}`)
|
||||
host.connect(record.address, record.port)
|
||||
var peer = host.connect(record.address, record.port)
|
||||
peer2contact.set(peer, {
|
||||
callback,
|
||||
record
|
||||
})
|
||||
}
|
||||
|
||||
$_.receiver = function(fn)
|
||||
@@ -603,17 +654,15 @@ $_.receiver = function(fn)
|
||||
receive_fn = fn;
|
||||
}
|
||||
|
||||
var greeters = {}
|
||||
|
||||
$_.start = function(cb, prg, arg)
|
||||
{
|
||||
var guid = util.guid()
|
||||
greeters[guid] = cb
|
||||
var id = util.guid()
|
||||
greeters[id] = cb
|
||||
var argv = [
|
||||
"./prosperon",
|
||||
"spawn",
|
||||
"--overling", host.port,
|
||||
"--guid", guid,
|
||||
"--id", id,
|
||||
]
|
||||
|
||||
if (prg)
|
||||
@@ -623,7 +672,6 @@ $_.start = function(cb, prg, arg)
|
||||
argv = argv.concat(cmd.encode(arg))
|
||||
|
||||
os.createprocess(argv)
|
||||
guid2actor.set(guid, {peer:undefined, guid:guid})
|
||||
}
|
||||
|
||||
$_.stop = function(actor)
|
||||
@@ -631,7 +679,7 @@ $_.stop = function(actor)
|
||||
if (!actor)
|
||||
destroyself()
|
||||
|
||||
send_system(actor, {type:"stop"})
|
||||
actor_send(actor, {type:"stop"})
|
||||
}
|
||||
|
||||
var unneeded_fn = $_.stop
|
||||
@@ -650,96 +698,168 @@ $_.delay = function(fn, seconds)
|
||||
return function() { os.removetimer(id); }
|
||||
}
|
||||
|
||||
var guid2actor = new Map()
|
||||
// Set of actor guids
|
||||
var couplings = new Set()
|
||||
|
||||
$_.couple = function(actor)
|
||||
{
|
||||
couplings.add(actor)
|
||||
couplings.add(actor.id)
|
||||
}
|
||||
|
||||
function send_system(actor, message)
|
||||
// Shuffles the message to the actor with whatever means available
|
||||
function actor_send(actor, message)
|
||||
{
|
||||
actor.peer.send({
|
||||
type:"system",
|
||||
data:message
|
||||
})
|
||||
|
||||
if (typeof message !== 'object')
|
||||
throw new Error('Must send an object record.')
|
||||
|
||||
console.log(`Getting peer for ${actor.id}`)
|
||||
var peer = peers[actor.id]
|
||||
if (!peer) throw new Error(`Could not send message to actor.`)
|
||||
console.log(`sending message ${json.encode(message)} to ${json.encode(actor)}`)
|
||||
peer.send(message)
|
||||
}
|
||||
|
||||
$_.send = function(actor, message, receive)
|
||||
// Map of reply IDs to functions
|
||||
var replies = {}
|
||||
|
||||
// Map of a message object to a peer for replying directly
|
||||
var reply_cc = new WeakMap()
|
||||
|
||||
$_.send = function(actor, message, reply)
|
||||
{
|
||||
if (typeof message !== 'object')
|
||||
throw new Error('Must send an object record.')
|
||||
|
||||
actor.peer.send({ // right now only peers so this works
|
||||
|
||||
console.log(`sending to ${json.encode(actor)} ...`)
|
||||
|
||||
var send = {
|
||||
type:"user",
|
||||
data: message
|
||||
})
|
||||
}
|
||||
|
||||
if (actor[REPLYCC]) {
|
||||
console.log(`replying to a message: ${json.encode(actor)}`)
|
||||
console.log(`replycc and reply are ${actor[REPLYCC]} and ${actor[REPLY]}`)
|
||||
actor.id = actor[REPLYCC]
|
||||
send.return = actor[REPLY]
|
||||
} else if (peers[actor.id]) {
|
||||
} else
|
||||
throw new Error(`Could not send message to actor.`)
|
||||
|
||||
if (reply) {
|
||||
var id = util.guid()
|
||||
replies[id] = reply
|
||||
send.reply = id
|
||||
}
|
||||
|
||||
actor_send(actor, send)
|
||||
}
|
||||
|
||||
var cmd = use('cmd')
|
||||
cmd.process(prosperon.argv)
|
||||
|
||||
if (!prosperon.args.id)
|
||||
prosperon.id = util.guid()
|
||||
else
|
||||
prosperon.id = prosperon.args.id;
|
||||
|
||||
if (prosperon.args.overling)
|
||||
host.connect("localhost", prosperon.args.overling)
|
||||
|
||||
if (prosperon.args.program)
|
||||
actor.spawn(prosperon.args.program)
|
||||
|
||||
if (!prosperon.args.guid)
|
||||
prosperon.guid = util.guid()
|
||||
else
|
||||
prosperon.guid = prosperon.args.guid;
|
||||
|
||||
var ar = 5 // seconds before reclamation
|
||||
|
||||
var unneeded_timer = $_.delay($_.stop, ar)
|
||||
|
||||
function destroyself()
|
||||
{
|
||||
host.broadcast({type:"system", data:{
|
||||
type:"disconnect"
|
||||
}})
|
||||
host.flush()
|
||||
console.log('got the message to destroy')
|
||||
os.exit(0)
|
||||
}
|
||||
|
||||
function handle_actor_disconnect(actor)
|
||||
function handle_actor_disconnect(id)
|
||||
{
|
||||
if (couplings.has(actor))
|
||||
if (couplings.has(id))
|
||||
$_.stop()
|
||||
|
||||
guid2actor.delete(actor.guid)
|
||||
guid2actor.delete(actor.peer)
|
||||
delete peers[id]
|
||||
delete greeters[id]
|
||||
}
|
||||
|
||||
function handle_system(e)
|
||||
function handle_message(e)
|
||||
{
|
||||
var msg = e.data.data
|
||||
switch(msg.type) {
|
||||
case "disconnect":
|
||||
handle_actor_disconnect(guid2actor.get(e.peer))
|
||||
console.log(`handling message ${json.encode(e)}`)
|
||||
switch (e.data.type) {
|
||||
case "user":
|
||||
if (e.data.return) {
|
||||
console.log(`Message has a return address.`)
|
||||
var fn = replies[e.data.return]
|
||||
if (!fn)
|
||||
throw new Error(`Could not find return function for message ${e.data.return}`)
|
||||
|
||||
fn(e.data)
|
||||
delete replies[e.data.return]
|
||||
return
|
||||
}
|
||||
|
||||
if (receive_fn) {
|
||||
if (e.data.reply) {
|
||||
// Doing this here because there is no way for an actor to reply to the message if it doesn't get it in the first place
|
||||
e.data.data[REPLYCC] = peer2id.get(e.peer)
|
||||
e.data.data[REPLY] = e.data.reply
|
||||
console.log(`set replycc and reply to ${e.data.data[REPLYCC]} and ${e.data.data[REPLY]}`)
|
||||
}
|
||||
|
||||
receive_fn(e.data.data)
|
||||
}
|
||||
break
|
||||
|
||||
|
||||
case "greet":
|
||||
peers[e.data.id] = e.peer;
|
||||
peer2id.set(e.peer, e.data.id);
|
||||
console.log(`Registered a peer with ${e.data.id}`)
|
||||
var greeter = greeters[e.data.id]
|
||||
if (!greeter) return; //throw new Error(`No greeter registered for greet message id ${e.data.id}`)
|
||||
greeter({
|
||||
type: "actor_started",
|
||||
actor: { id: e.data.id }
|
||||
})
|
||||
break
|
||||
|
||||
case "stop":
|
||||
destroyself()
|
||||
break
|
||||
|
||||
case "greet":
|
||||
if (greeters[msg.guid]) {
|
||||
var actor = guid2actor.get(msg.guid)
|
||||
if (!actor) throw new Error(`No registered actor for guid ${msg.guid}`)
|
||||
actor.peer = e.peer
|
||||
guid2actor.set(e.peer, actor)
|
||||
greeters[msg.guid]({
|
||||
type: "actor_started",
|
||||
actor
|
||||
})
|
||||
}
|
||||
case "contact":
|
||||
if (pppfn) pppfn(e.data)
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var hang = 0.016
|
||||
function handle_connect(e)
|
||||
{
|
||||
console.log(`message arrived at ${host.address}:${host.port} from somebody ... ${e.peer.address}:${e.peer.port}`)
|
||||
var contact = peer2contact.get(e.peer)
|
||||
if (contact) {
|
||||
// We have successfully made contact. now send the request.
|
||||
e.peer.send({
|
||||
type: "contact",
|
||||
data: contact.record
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// For a local greet
|
||||
e.peer.send({
|
||||
type:"greet",
|
||||
id: prosperon.id
|
||||
})
|
||||
}
|
||||
|
||||
var hang = 0.001
|
||||
while (1) {
|
||||
os.waitevent(e => {
|
||||
unneeded_timer()
|
||||
@@ -750,35 +870,26 @@ while (1) {
|
||||
unneeded_timer()
|
||||
switch(e.type) {
|
||||
case "connect":
|
||||
console.log(`message arrived at ${host.address}:${host.port} from somebody ... ${e.peer.address}:${e.peer.port}`)
|
||||
e.peer.send({
|
||||
type:"system",
|
||||
data: {
|
||||
type:"greet",
|
||||
guid: prosperon.guid
|
||||
}
|
||||
})
|
||||
break;
|
||||
handle_connect(e);
|
||||
break;
|
||||
|
||||
case "receive":
|
||||
if (e.data.type === "system")
|
||||
handle_system(e)
|
||||
else if(receive_fn)
|
||||
receive_fn(e.data.data)
|
||||
else
|
||||
console.log(`Got a messge but no receiver is registered.`)
|
||||
|
||||
handle_message(e)
|
||||
break;
|
||||
|
||||
case "disconnect":
|
||||
greeters[guid2actor.get(e.peer).guid]({
|
||||
var id = peer2id(e.peer)
|
||||
greeters[id]({
|
||||
type: "actor_stopped"
|
||||
})
|
||||
handle_actor_disconnect(guid2actor.get(e.peer))
|
||||
break
|
||||
});
|
||||
handle_actor_disconnect(id);
|
||||
break;
|
||||
}
|
||||
unneeded_timer = $_.delay(unneeded_fn, unneeded_time)
|
||||
}, hang);
|
||||
|
||||
if (portal)
|
||||
portal.service(portal_fn, hang)
|
||||
}
|
||||
|
||||
})()
|
||||
|
||||
105
source/jsffi.c
105
source/jsffi.c
@@ -57,6 +57,8 @@ typedef struct rtree rtree;
|
||||
#include <SDL3/SDL_loadso.h>
|
||||
#include <SDL3/SDL_cpuinfo.h>
|
||||
|
||||
int randombytes(void *buf, size_t n);
|
||||
|
||||
static Uint32 timer_cb_event;
|
||||
|
||||
#ifdef __APPLE__
|
||||
@@ -5747,8 +5749,7 @@ static const JSCFunctionListEntry js_input_funcs[] = {
|
||||
|
||||
JSC_CCALL(os_guid,
|
||||
SDL_GUID guid;
|
||||
for (int i = 0; i < 16; i++)
|
||||
guid.data[i] = rand() % 256;
|
||||
randombytes(guid.data, 16);
|
||||
|
||||
char guid_str[33];
|
||||
|
||||
@@ -7657,6 +7658,84 @@ static void exit_handler()
|
||||
|
||||
#include "monocypher.h"
|
||||
|
||||
// randombytes.c - Minimal cross-platform CSPRNG shim (single file)
|
||||
/*
|
||||
Usage:
|
||||
#include "randombytes.c"
|
||||
|
||||
int main() {
|
||||
uint8_t buffer[32];
|
||||
if (randombytes(buffer, sizeof(buffer)) != 0) {
|
||||
// handle error
|
||||
}
|
||||
// buffer now has 32 cryptographically secure random bytes
|
||||
}
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
// ------- Windows: use BCryptGenRandom -------
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
|
||||
int randombytes(void *buf, size_t n) {
|
||||
NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
return (status == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
// ------- Linux: try getrandom, fall back to /dev/urandom -------
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
// If we have a new enough libc and kernel, getrandom is available.
|
||||
// Otherwise, we’ll do a /dev/urandom fallback.
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int randombytes_fallback(void *buf, size_t n) {
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
ssize_t r = read(fd, buf, n);
|
||||
close(fd);
|
||||
return (r == (ssize_t)n) ? 0 : -1;
|
||||
}
|
||||
|
||||
int randombytes(void *buf, size_t n) {
|
||||
#ifdef SYS_getrandom
|
||||
// Try getrandom(2) if available
|
||||
ssize_t ret = syscall(SYS_getrandom, buf, n, 0);
|
||||
if (ret < 0) {
|
||||
// If getrandom is not supported or fails, fall back
|
||||
if (errno == ENOSYS) {
|
||||
return randombytes_fallback(buf, n);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return (ret == (ssize_t)n) ? 0 : -1;
|
||||
#else
|
||||
// getrandom not available, just fallback
|
||||
return randombytes_fallback(buf, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
// ------- Other Unix: read from /dev/urandom -------
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int randombytes(void *buf, size_t n) {
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
ssize_t r = read(fd, buf, n);
|
||||
close(fd);
|
||||
return (r == (ssize_t)n) ? 0 : -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void to_hex(const uint8_t *in, size_t in_len, char *out)
|
||||
{
|
||||
static const char hexchars[] = "0123456789abcdef";
|
||||
@@ -7721,8 +7800,8 @@ JSC_CCALL(crypto_keypair,
|
||||
|
||||
uint8_t public[32];
|
||||
uint8_t private[32];
|
||||
for (int i = 0; i < 32; i++)
|
||||
private[i] = (uint8_t)rand();
|
||||
|
||||
randombytes(private,32);
|
||||
|
||||
private[0] &= 248;
|
||||
private[31] &= 127;
|
||||
@@ -7768,9 +7847,27 @@ JSC_CCALL(crypto_shared,
|
||||
ret = crypto2js(js, shared);
|
||||
})
|
||||
|
||||
JSC_CCALL(crypto_random,
|
||||
{
|
||||
// 1) Pull 64 bits of cryptographically secure randomness
|
||||
uint64_t r;
|
||||
if (randombytes(&r, sizeof(r)) != 0) {
|
||||
// If something fails (extremely rare), throw an error
|
||||
return JS_ThrowInternalError(js, "crypto.random: unable to get random bytes");
|
||||
}
|
||||
|
||||
// 2) Convert r to a double in the range [0,1).
|
||||
// We divide by (UINT64_MAX + 1.0) to ensure we never produce exactly 1.0.
|
||||
double val = (double)r / ((double)UINT64_MAX + 1.0);
|
||||
|
||||
// 3) Return that as a JavaScript number
|
||||
ret = JS_NewFloat64(js, val);
|
||||
})
|
||||
|
||||
static const JSCFunctionListEntry js_crypto_funcs[] = {
|
||||
MIST_FUNC_DEF(crypto, keypair, 0),
|
||||
MIST_FUNC_DEF(crypto, shared, 1),
|
||||
MIST_FUNC_DEF(crypto, random, 0),
|
||||
};
|
||||
|
||||
MISTUSE(io)
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
#include <enet/enet.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "script.h"
|
||||
|
||||
#define countof(a) (sizeof(a)/sizeof(*(a)))
|
||||
|
||||
static JSClassID enet_host_id;
|
||||
@@ -53,7 +54,7 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val,
|
||||
JSValue obj;
|
||||
|
||||
// Default parameters (if not provided by user)
|
||||
size_t peer_count = 32;
|
||||
size_t peer_count = 1000;
|
||||
size_t channel_limit = 0;
|
||||
enet_uint32 incoming_bandwidth = 0;
|
||||
enet_uint32 outgoing_bandwidth = 0;
|
||||
@@ -217,18 +218,17 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
|
||||
printf("INVALID JSON!\n");
|
||||
enet_packet_destroy(event.packet);
|
||||
JS_FreeValue(ctx, event_obj);
|
||||
JS_FreeValue(ctx, callback);
|
||||
return JS_ThrowTypeError(ctx, "Received invalid JSON (parse error).");
|
||||
uncaught_exception(ctx, packet_data);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!JS_IsObject(packet_data)) {
|
||||
// It might be a string/number/array/... -> we want only a plain object
|
||||
JS_FreeValue(ctx, event_obj);
|
||||
JS_FreeValue(ctx, callback);
|
||||
JS_FreeValue(ctx, packet_data);
|
||||
enet_packet_destroy(event.packet);
|
||||
return JS_ThrowTypeError(ctx,
|
||||
"Received data is not an object (must send a plain object).");
|
||||
uncaught_exception(ctx, JS_ThrowTypeError(ctx, "Received data is not an object (must send a plain object)."));
|
||||
continue;
|
||||
}
|
||||
|
||||
JS_SetPropertyStr(ctx, event_obj, "data", packet_data);
|
||||
@@ -244,7 +244,7 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
|
||||
}
|
||||
|
||||
// Invoke callback
|
||||
JS_Call(ctx, callback, JS_UNDEFINED, 1, &event_obj);
|
||||
uncaught_exception(ctx, JS_Call(ctx, callback, JS_UNDEFINED, 1, &event_obj));
|
||||
JS_FreeValue(ctx, event_obj);
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val,
|
||||
|
||||
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
|
||||
if (!peer) {
|
||||
return JS_ThrowInternalError(ctx, "Failed to initiate connection.");
|
||||
return JS_ThrowInternalError(ctx, "No available peers for initiating an ENet connection.");
|
||||
}
|
||||
|
||||
return peer_get_value(ctx, peer);
|
||||
@@ -346,9 +346,22 @@ static JSValue js_enet_host_get_port(JSContext *js, JSValueConst self, int argc,
|
||||
|
||||
static JSValue js_enet_host_get_address(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.host);
|
||||
ENetHost *me = JS_GetOpaque(self, enet_host_id);
|
||||
if (!me) return JS_EXCEPTION;
|
||||
|
||||
uint32_t host = ntohl(me->address.host);
|
||||
|
||||
if (host == 0x7F000001)
|
||||
return JS_NewString(js, "localhost");
|
||||
|
||||
char ip_str[16]; // Enough space for "255.255.255.255" + null terminator
|
||||
snprintf(ip_str, sizeof(ip_str), "%u.%u.%u.%u",
|
||||
(host >> 24) & 0xFF,
|
||||
(host >> 16) & 0xFF,
|
||||
(host >> 8) & 0xFF,
|
||||
host & 0xFF);
|
||||
|
||||
return JS_NewString(js, ip_str);
|
||||
}
|
||||
|
||||
/* Peer-level operations */
|
||||
@@ -399,7 +412,7 @@ static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val,
|
||||
}
|
||||
|
||||
if (enet_peer_send(peer, 0, packet) < 0) {
|
||||
return JS_ThrowInternalError(ctx, "enet_peer_send returned error.");
|
||||
return JS_ThrowInternalError(ctx, "unable to send packet. send returned error.");
|
||||
}
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Test to connect to a portal
|
||||
|
||||
$_.contact((actor, reason) => {
|
||||
|
||||
}, {
|
||||
address: "localhost",
|
||||
port: 5678,
|
||||
|
||||
191
tests/enet.js
191
tests/enet.js
@@ -1,191 +0,0 @@
|
||||
var os = use('os');
|
||||
var enet = use('enet');
|
||||
var json = use('json'); // Some QuickJS environments need this import for JSON
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 1. Initialization
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Make sure ENet is initialized before we do anything else
|
||||
enet.initialize();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 2. "Framework": test runner & polling helper
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let results = [];
|
||||
|
||||
/**
|
||||
* Simple test runner. Each test is given a name and a function.
|
||||
* If the function throws an Error, the test is marked failed.
|
||||
*/
|
||||
function runTest(testName, testFunc) {
|
||||
console.log(`=== Running Test: ${testName} ===`);
|
||||
try {
|
||||
testFunc();
|
||||
results.push({ testName, passed: true });
|
||||
} catch (err) {
|
||||
console.log(`Test "${testName}" failed: ${err}`);
|
||||
results.push({ testName, passed: false, error: err });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* runSteps polls both client and server for `steps` iterations,
|
||||
* each iteration calling service() with a small timeout. Any events
|
||||
* are captured and returned in arrays for further inspection.
|
||||
*
|
||||
* @param {Object} client - the client ENet host
|
||||
* @param {Object} server - the server ENet host
|
||||
* @param {number} steps - how many iterations to process
|
||||
* @param {boolean} printEvents - whether to log events to console
|
||||
* @param {number} timeout - milliseconds to pass to service()
|
||||
* @returns { clientEvents, serverEvents } arrays of all events captured
|
||||
*/
|
||||
function runSteps(client, server, steps = 5, printEvents = false, timeout = 10) {
|
||||
let clientEvents = [];
|
||||
let serverEvents = [];
|
||||
|
||||
for (let i = 0; i < steps; i++) {
|
||||
// Poll the client
|
||||
client.service((evt) => {
|
||||
if (evt) {
|
||||
clientEvents.push(evt);
|
||||
if (printEvents) {
|
||||
console.log("client:" + json.encode(evt));
|
||||
}
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
// Poll the server
|
||||
server.service((evt) => {
|
||||
if (evt) {
|
||||
serverEvents.push(evt);
|
||||
if (printEvents) {
|
||||
console.log("server:" + json.encode(evt));
|
||||
}
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
return { clientEvents, serverEvents };
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 3. Actual Tests
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let serverHost = null;
|
||||
let clientHost = null;
|
||||
let clientPeer = null;
|
||||
|
||||
runTest("Create Server", () => {
|
||||
// Bind on 127.0.0.1:12345
|
||||
serverHost = enet.create_host("127.0.0.1:12345");
|
||||
if (!serverHost) {
|
||||
throw new Error("Failed to create server host");
|
||||
}
|
||||
});
|
||||
|
||||
runTest("Create Client", () => {
|
||||
clientHost = enet.create_host();
|
||||
if (!clientHost) {
|
||||
throw new Error("Failed to create client host");
|
||||
}
|
||||
});
|
||||
|
||||
runTest("Connect Client to Server", () => {
|
||||
clientPeer = clientHost.connect("127.0.0.1", 12345);
|
||||
if (!clientPeer) {
|
||||
throw new Error("Failed to create client->server peer");
|
||||
}
|
||||
|
||||
// Poll both sides for a few steps so the connection can succeed
|
||||
const { clientEvents, serverEvents } = runSteps(clientHost, serverHost, 5, true);
|
||||
|
||||
// Verify the server got a 'connect' event
|
||||
let connectEvent = serverEvents.find(evt => evt.type === "connect");
|
||||
if (!connectEvent) {
|
||||
throw new Error("Server did not receive a connect event");
|
||||
}
|
||||
});
|
||||
|
||||
runTest("Send Data from Client to Server", () => {
|
||||
// Send some JSON object from client -> server
|
||||
clientPeer.send({ hello: "HELLO" });
|
||||
|
||||
// Process for a few steps so the data actually arrives
|
||||
const { clientEvents, serverEvents } = runSteps(clientHost, serverHost, 5, true);
|
||||
|
||||
// The server should get a 'receive' event
|
||||
let receiveEvent = serverEvents.find(evt => evt.type === "receive");
|
||||
if (!receiveEvent) {
|
||||
throw new Error("Server did not receive data from the client");
|
||||
}
|
||||
|
||||
// Check the payload
|
||||
if (!receiveEvent.data || receiveEvent.data.hello !== "HELLO") {
|
||||
throw new Error(`Server got unexpected data: ${JSON.stringify(receiveEvent.data)}`);
|
||||
}
|
||||
});
|
||||
|
||||
runTest("Broadcast from Server to Client", () => {
|
||||
// The server broadcasts a JSON string
|
||||
serverHost.broadcast({ broadcast: "HelloAll" });
|
||||
|
||||
// Let data flow
|
||||
const { clientEvents, serverEvents } = runSteps(clientHost, serverHost, 5, true);
|
||||
|
||||
// Client should get a 'receive' event with broadcast data
|
||||
let broadcastEvent = clientEvents.find(evt => evt.type === "receive");
|
||||
if (!broadcastEvent) {
|
||||
throw new Error("Client did not receive broadcast data");
|
||||
}
|
||||
|
||||
// The broadcastEvent.data should be an object with broadcast="HelloAll"
|
||||
if (!broadcastEvent.data || broadcastEvent.data.broadcast !== "HelloAll") {
|
||||
throw new Error(`Client received unexpected broadcast: ${JSON.stringify(broadcastEvent.data)}`);
|
||||
}
|
||||
});
|
||||
|
||||
runTest("Disconnect Client", () => {
|
||||
// Disconnect from the client side
|
||||
clientPeer.disconnect();
|
||||
|
||||
// Let both sides see the disconnect
|
||||
const { clientEvents, serverEvents } = runSteps(clientHost, serverHost, 5, true);
|
||||
|
||||
// The server should eventually get a "disconnect"
|
||||
let disconnectEvent = serverEvents.find(evt => evt.type === "disconnect");
|
||||
if (!disconnectEvent) {
|
||||
throw new Error("Server never received a disconnect event");
|
||||
}
|
||||
});
|
||||
|
||||
runTest("Deinitialize ENet", () => {
|
||||
enet.deinitialize();
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 4. Print Summary & Exit
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
console.log("\n=== Test Summary ===");
|
||||
let passedCount = 0;
|
||||
results.forEach(({ testName, passed, error }, idx) => {
|
||||
console.log(`Test ${idx + 1}: ${testName} - ${passed ? "PASSED" : "FAILED"}`);
|
||||
if (!passed && error) {
|
||||
console.log(" Reason:", error);
|
||||
}
|
||||
if (passed) passedCount++;
|
||||
});
|
||||
|
||||
console.log(`\nResult: ${passedCount}/${results.length} tests passed`);
|
||||
|
||||
if (passedCount < results.length) {
|
||||
console.log("Overall: FAILED");
|
||||
os.exit(1);
|
||||
} else {
|
||||
console.log("Overall: PASSED");
|
||||
os.exit(0);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ $_.start(e => {
|
||||
switch(e.type) {
|
||||
case "actor_started":
|
||||
console.log(`parent got system level actor_started msg`)
|
||||
console.log(json.encode(e))
|
||||
$_.connection(e => console.log(json.encode(e)), e.actor) // get connection info
|
||||
|
||||
$_.send(e.actor, {message: "Hello!"})
|
||||
@@ -16,5 +17,3 @@ $_.start(e => {
|
||||
$_.couple(e.actor)
|
||||
}
|
||||
}, "tests/underling.js");
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
var password = "abc123"
|
||||
|
||||
$_.portal(e => {
|
||||
console.log(`received a message for contact: ${json.encode(e)}`)
|
||||
console.log(`got message with password ${e.password}`)
|
||||
if (e.password !== password)
|
||||
throw new Error("Password does not match.");
|
||||
|
||||
console.log(`passwords matched.`)
|
||||
|
||||
$_.send(e, {
|
||||
actor: $_
|
||||
});
|
||||
}, 5678);
|
||||
|
||||
9
tests/reply.js
Normal file
9
tests/reply.js
Normal file
@@ -0,0 +1,9 @@
|
||||
var os = use('os')
|
||||
|
||||
$_.receiver(e => {
|
||||
console.log(`Got a message: ${json.encode(e)}`)
|
||||
|
||||
$_.send(e, {
|
||||
message: "Good to go."
|
||||
})
|
||||
})
|
||||
9
tests/send.js
Normal file
9
tests/send.js
Normal file
@@ -0,0 +1,9 @@
|
||||
var os = use('os')
|
||||
|
||||
$_.start(e => {
|
||||
console.log(json.encode(e.actor))
|
||||
$_.send(e.actor, { message: "Hello! Good to go?" }, msg => {
|
||||
console.log(`Original sender got message back: ${json.encode(msg)}. Stopping!`)
|
||||
$_.stop()
|
||||
})
|
||||
}, "tests/reply.js")
|
||||
Reference in New Issue
Block a user