Files
cc-haxe/src/kernel/net/Net.hx
2023-07-30 15:55:22 +02:00

290 lines
7.0 KiB
Haxe

package kernel.net;
import kernel.net.Package.GenericPackage;
import kernel.gps.GPS;
import haxe.ds.ReadOnlyArray;
import kernel.net.Package.NetworkID;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.log.Log;
import kernel.Timer;
import cc.OS;
using tink.CoreApi;
using Lambda;
/**
Class responsible for everything network related.
Used to send and recceive packages.
**/
class Net {
/**
Depends on: KernelEvents
**/
public static inline final BRODCAST_PORT:Int = 65533;
public static inline final MESSAGE_TIMEOUT:Int = 3;
public static inline final DEFAULT_TTL:Int = 10;
public static final networkID:NetworkID = OS.getComputerID();
private static final responseBus:Map<Int, Callback<Outcome<GenericPackage, Error>>> = new Map();
private static final protoHandlers:Map<String, Callback<GenericPackage>> = new Map();
private static var interfaces:Array<INetworkInterface>;
@:allow(kernel.Init)
private static function init() {
interfaces = [for (e in Peripheral.getAllModems()) e]; // TODO: is this the way to do it?
interfaces.push(Loopback.instance);
for (interf in interfaces) {
setupInterf(interf);
}
setupPingHandle();
Routing.setup();
}
private static function setupPingHandle() {
registerProto("icmp", pack -> {
switch pack.data.type {
case "ping":
respondTo(pack, "pong");
case "died":
// If we get a "died" message from a node when one of our packages ttl hits 0
// the `data.msgId` prop is the message id
var msgID:Int = pack.data.msgID;
if (responseBus.exists(msgID)) {
responseBus[msgID].invoke(Outcome.Failure(new Error("TTL reached 0")));
}
default:
Log.silly('Unknown icmp message: ${pack.data}');
}
});
}
private static function setupInterf(interf:INetworkInterface) {
interf.onMessage.handle(e -> handle(e.pack, interf, e.dist));
interf.listen(networkID);
interf.listen(BRODCAST_PORT);
}
/**
Called when a new package comes in.
**/
private static function handle(pack:GenericPackage, interf:INetworkInterface, ?dist:Float) {
if (pack.toID == networkID || pack.toID == Net.BRODCAST_PORT) {
switch pack.type {
case Data(_) | DataNoResponse(_):
// Let a local proccess handle it
routeToProto(pack);
case Response:
// Got a response to a send message. Invoke the callback
if (responseBus.exists(pack.msgID)) {
responseBus[pack.msgID].invoke(Outcome.Success(pack));
}
case RouteDiscover(_) | RouteDiscoverResponse(_) | RouteDiscoverUpdate(_):
// Delegate to Routing
Routing.handleRoutePackage(cast pack, interf);
case GPSRequest | GPSResponse(_):
if (dist == null) {
Log.silly("Got a GPS package but no distance was provided");
return;
}
// Delegate to GPS
GPS.handlePackage(cast pack, dist, interf);
}
} else {
// New message received but its not ment for us. Forward if possible.
forwardPackage(pack);
}
}
private static function generateMessageID():Int {
return Std.random(2147483647); // TODO: better uniqe number
}
/**
Send a message. Dont care if its reaches its destination nor it has a response.
**/
public static function sendAndForget(dest:Int, proto:String, data:Dynamic) {
var pack:GenericPackage = {
toID: dest,
fromID: networkID,
msgID: generateMessageID(),
type: DataNoResponse(proto),
data: data,
ttl: Net.DEFAULT_TTL,
}
sendRaw(pack);
}
private static function forwardPackage(pack:GenericPackage) {
if (pack.ttl == 0) {
if (pack.type.match(Data(_))) {
// If the package is a data package and the ttl hits 0
// we send a "died" message to the sender
sendAndForget(pack.fromID, "icmp", {type: "died", msgID: pack.msgID});
}
// Drop package
return;
}
pack.ttl--;
if (!sendRaw(pack)) {
// Cant forward
}
}
public static function respondTo(pack:GenericPackage, data:Dynamic) {
if (pack.type.match(DataNoResponse(_))) {
Log.warn("Responed to a no response package. Ignoring");
return;
}
var response = pack.createResponse(data);
sendRaw(response);
}
/**
Send to package to the localy register handler based on the proto
**/
private static function routeToProto(pack:GenericPackage) {
var proto = switch pack.type {
case Data(proto):
proto;
case DataNoResponse(proto):
proto;
default:
return;
}
if (!protoHandlers.exists(proto)) {
Log.warn('Trying to route package to proto: $proto but nothing was register');
return;
}
protoHandlers[proto].invoke(pack);
}
/**
Just send the package to the right modem.
Returns true if message was send
**/
private static function sendRaw(pack:GenericPackage):Bool {
var route = Routing.getRouteToID(pack.toID);
if (route == null) {
return false;
}
route.interf.send(route.rep, networkID, pack);
return true;
}
/**
Send a message and wait for a response.
**/
public static function sendAndAwait<T>(dest:NetworkID, proto:String, data:T):Promise<GenericPackage> {
return new Promise<GenericPackage>((resolve, reject) -> {
var pack:GenericPackage = {
toID: dest,
fromID: networkID,
msgID: generateMessageID(),
type: Data(proto),
data: data,
ttl: Net.DEFAULT_TTL,
}
var timeout:Timer = null;
responseBus[pack.msgID] = ((reponse:Outcome<GenericPackage, Error>) -> {
switch reponse {
case Success(pack):
resolve(pack);
case Failure(err):
reject(err);
}
// Always remove the timeout
if (timeout != null) {
timeout.cancle();
}
});
timeout = new Timer(MESSAGE_TIMEOUT, () -> {
responseBus.remove(pack.msgID);
reject(new Error(InternalError, "Message timeout"));
});
if (!sendRaw(pack)) {
reject(new Error("ID unreachable"));
}
// No callback link for you
return null;
});
}
public static function registerProto(proto:String, callback:Callback<GenericPackage>) {
if (protoHandlers.exists(proto)) {
// Failed. Handler already exist.
// TODO: return error
return;
}
protoHandlers[proto] = callback;
}
public static function removeProto(proto:String) {
protoHandlers.remove(proto);
}
/**
Sends a ping package to the given id. Returns true if there was a response.
**/
public static function ping(toID:NetworkID):Promise<Noise> {
return new Promise<Noise>((resolve, reject) -> {
sendAndAwait(toID, "icmp", {type: "ping"}).handle(pack -> {
switch pack {
case Success(_):
resolve(Noise);
case Failure(err):
reject(err);
}
});
return null;
});
}
public static function getActiveProtocols():ReadOnlyArray<String> {
var arr = new Array<String>();
for (proto in protoHandlers.keys()) {
arr.push(proto);
}
return arr;
}
@:allow(kernel.gps.GPS)
private static function brodcastGPSRequest() {
var pack:Package<Noise> = {
fromID: networkID,
toID: Net.BRODCAST_PORT,
ttl: 0, // Prevent forwarding
msgID: generateMessageID(),
type: GPSRequest,
data: null,
};
for (modem in Peripheral.getAllModems()) {
if (!modem.isWireless())
continue;
modem.send(Net.BRODCAST_PORT, networkID, pack);
}
}
}