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>> = new Map(); private static final protoHandlers:Map> = new Map(); private static var interfaces:Array; @: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(dest:NetworkID, proto:String, data:T):Promise { return new Promise((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) -> { 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) { 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 { return new Promise((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 { var arr = new Array(); for (proto in protoHandlers.keys()) { arr.push(proto); } return arr; } @:allow(kernel.gps.GPS) private static function brodcastGPSRequest() { var pack:Package = { 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); } } }