diff --git a/Esiur/Core/AsyncAwaiterGeneric.cs b/Esiur/Core/AsyncAwaiterGeneric.cs index 394faa8..ca77ec0 100644 --- a/Esiur/Core/AsyncAwaiterGeneric.cs +++ b/Esiur/Core/AsyncAwaiterGeneric.cs @@ -47,5 +47,4 @@ public class AsyncAwaiter : INotifyCompletion callback = continuation; } - } diff --git a/Esiur/Core/AsyncReply.cs b/Esiur/Core/AsyncReply.cs index 76a7812..1d61129 100644 --- a/Esiur/Core/AsyncReply.cs +++ b/Esiur/Core/AsyncReply.cs @@ -90,6 +90,16 @@ public class AsyncReply return result; } + public AsyncReply Timeout(int milliseconds, Action callback) + { + Task.Delay(milliseconds).ContinueWith(x => + { + if (!resultReady && exception == null) + callback(); + }); + + return this; + } public object Wait(int millisecondsTimeout) { diff --git a/Esiur/Core/ExceptionCode.cs b/Esiur/Core/ExceptionCode.cs index 79f81c2..6ee93a7 100644 --- a/Esiur/Core/ExceptionCode.cs +++ b/Esiur/Core/ExceptionCode.cs @@ -40,5 +40,6 @@ public enum ExceptionCode : ushort NotAttached, AlreadyListened, AlreadyUnlistened, - NotListenable + NotListenable, + ParseError } diff --git a/Esiur/Data/VarInfo.cs b/Esiur/Data/VarInfo.cs new file mode 100644 index 0000000..b1764c1 --- /dev/null +++ b/Esiur/Data/VarInfo.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Esiur.Data +{ + struct VarInfo + { + public string Pre; + public string Post; + public string VarName; + + public string Build() + { + return Regex.Escape(Pre) + @"(?<" + VarName + @">[^\{]*)" + Regex.Escape(Post); + } + } + +} diff --git a/Esiur/Esiur - Backup.csproj b/Esiur/Esiur - Backup.csproj new file mode 100644 index 0000000..3d1b94a --- /dev/null +++ b/Esiur/Esiur - Backup.csproj @@ -0,0 +1,111 @@ + + + + netstandard2.0 + Distributed Resources Platform + Ahmed Kh. Zamil + http://www.esiur.com + true + 2.2.6.1 + https://github.com/esiur/esiur-dotnet + Ahmed Kh. Zamil + + Esiur Foundation + + Esiur + Esiur + Esiur + Esiur + latest + LICENSE + + + + True + TRACE;DEBUG;NETSTANDARD + + + + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Esiur/Esiur.csproj b/Esiur/Esiur.csproj index ba0e3cf..5e20dd8 100644 --- a/Esiur/Esiur.csproj +++ b/Esiur/Esiur.csproj @@ -6,7 +6,7 @@ Ahmed Kh. Zamil http://www.esiur.com true - 2.2.5 + 2.2.6.2 https://github.com/esiur/esiur-dotnet Ahmed Kh. Zamil diff --git a/Esiur/Misc/Global.cs b/Esiur/Misc/Global.cs index ba3cd54..f3c35f3 100644 --- a/Esiur/Misc/Global.cs +++ b/Esiur/Misc/Global.cs @@ -482,6 +482,36 @@ public static Hashtable Cached else return Expression; } + + + + public static Regex GetRouteRegex(string url) + { + var sc = Regex.Match(url, @"([^\{]*)\{([^\}]*)\}([^\{]*)"); + + List vars = new List(); + + while (sc.Success) + { + vars.Add(new VarInfo() + { + Pre = sc.Groups[1].Value, + VarName = sc.Groups[2].Value, + Post = sc.Groups[3].Value + }); + sc = sc.NextMatch(); + } + + if (vars.Count > 0) + { + return new Regex("^" + String.Join("", vars.Select(x => x.Build()).ToArray()) + "$"); + } + else + { + return new Regex("^" + Regex.Escape(url) + "$"); + } + } + //public void Replace(string Expression, string Find, string Replacement, int Start, int Count) //{ // Expression.IndexOf( diff --git a/Esiur/Net/HTTP/HTTPServer.cs b/Esiur/Net/HTTP/HTTPServer.cs index fd40f69..1fded44 100644 --- a/Esiur/Net/HTTP/HTTPServer.cs +++ b/Esiur/Net/HTTP/HTTPServer.cs @@ -128,46 +128,9 @@ public class HTTPServer : NetworkServer, IResource } } - struct VarInfo - { - public string Pre; - public string Post; - public string VarName; - - public string Build() - { - return Regex.Escape(Pre) + @"(?<" + VarName + @">[^\{]*)" + Regex.Escape(Post); - } - } - static Regex getRouteRegex(string url) - { - var sc = Regex.Match(url, @"([^\{]*)\{([^\}]*)\}([^\{]*)"); - - List vars = new List(); - - while (sc.Success) - { - vars.Add(new VarInfo() - { - Pre = sc.Groups[1].Value, - VarName = sc.Groups[2].Value, - Post = sc.Groups[3].Value - }); - sc = sc.NextMatch(); - } - - if (vars.Count > 0) - { - return new Regex("^" + String.Join("", vars.Select(x => x.Build()).ToArray()) + "$"); - } - else - { - return new Regex("^" + Regex.Escape(url) + "$"); - } - } public Instance Instance @@ -276,18 +239,17 @@ public class HTTPServer : NetworkServer, IResource return false; } - //public delegate void HTTPGetHandler(HTTPConnection connection, object[] params values); - + public void MapGet(string pattern, Delegate handler) { - var regex = getRouteRegex(pattern); + var regex = Global.GetRouteRegex(pattern); var list = routes[HTTPRequestPacket.HTTPMethod.GET]; list.Add(new RouteInfo(handler, regex)); } public void MapPost(string pattern, Delegate handler) { - var regex = getRouteRegex(pattern); + var regex = Global.GetRouteRegex(pattern); var list = routes[HTTPRequestPacket.HTTPMethod.POST]; list.Add(new RouteInfo(handler, regex)); } diff --git a/Esiur/Net/IIP/DistributedConnection.cs b/Esiur/Net/IIP/DistributedConnection.cs index 5cde61e..deede51 100644 --- a/Esiur/Net/IIP/DistributedConnection.cs +++ b/Esiur/Net/IIP/DistributedConnection.cs @@ -40,6 +40,7 @@ using System.Linq; using System.Diagnostics; using static Esiur.Net.Packets.IIPPacket; using Esiur.Net.HTTP; +using System.Timers; namespace Esiur.Net.IIP; public partial class DistributedConnection : NetworkConnection, IStore @@ -47,6 +48,9 @@ public partial class DistributedConnection : NetworkConnection, IStore public delegate void ReadyEvent(DistributedConnection sender); public delegate void ErrorEvent(DistributedConnection sender, byte errorCode, string errorMessage); + + Timer keepAliveTimer; + /// /// Ready event is raised when the connection is fully established. /// @@ -330,13 +334,64 @@ public partial class DistributedConnection : NetworkConnection, IStore else x.Resource._UpdatePropertyByIndex(x.Index, x.Value); }); - //q.timeout?.Dispose(); + var r = new Random(); localNonce = new byte[32]; r.NextBytes(localNonce); + + keepAliveTimer = new Timer(KeepAliveInterval * 1000); + keepAliveTimer.Elapsed += KeepAliveTimer_Elapsed; } + [Public] public virtual uint Jitter { get; set; } + + public uint KeepAliveTime { get; set; } = 10; + + DateTime? lastKeepAliveSent; + + private void KeepAliveTimer_Elapsed(object sender, ElapsedEventArgs e) + { + if (!IsConnected) + return; + + + keepAliveTimer.Stop(); + + var now = DateTime.UtcNow; + + uint interval = lastKeepAliveSent == null ? 0 : + (uint)(now - (DateTime)lastKeepAliveSent).TotalMilliseconds; + + lastKeepAliveSent = now; + + SendRequest(IIPPacketAction.KeepAlive) + .AddDateTime(now) + .AddUInt32(interval) + .Done() + .Then(x => + { + + Jitter = (uint)x[1]; + keepAliveTimer.Start(); + //Console.WriteLine($"Keep Alive Received {Jitter}"); + }).Error(ex => + { + keepAliveTimer.Stop(); + Close(); + }).Timeout((int)(KeepAliveTime * 1000), () => + { + keepAliveTimer.Stop(); + Close(); + }); + + //Console.WriteLine("Keep Alive sent"); + + + } + + public uint KeepAliveInterval { get; set; } = 30; + public override void Destroy() { UnsubscribeAll(); @@ -535,6 +590,19 @@ public partial class DistributedConnection : NetworkConnection, IStore // @TODO : fix this //IIPRequestClearAttributes(packet.CallbackId, packet.ResourceId, packet.Content, false); break; + + case IIPPacketAction.KeepAlive: + IIPRequestKeepAlive(packet.CallbackId, packet.CurrentTime, packet.Interval); + break; + + case IIPPacketAction.ProcedureCall: + IIPRequestProcedureCall(packet.CallbackId, packet.Procedure, (TransmissionType)packet.DataType, msg); + break; + + case IIPPacketAction.StaticCall: + IIPRequestStaticCall(packet.CallbackId, packet.ClassId, packet.MethodIndex, (TransmissionType)packet.DataType, msg); + break; + } } else if (packet.Command == IIPPacket.IIPPacketCommand.Reply) @@ -584,7 +652,9 @@ public partial class DistributedConnection : NetworkConnection, IStore break; // Invoke - case IIPPacket.IIPPacketAction.InvokeFunction: + case IIPPacketAction.InvokeFunction: + case IIPPacketAction.StaticCall: + case IIPPacketAction.ProcedureCall: IIPReplyInvoke(packet.CallbackId, (TransmissionType)packet.DataType, msg);// packet.Content); break; @@ -615,6 +685,9 @@ public partial class DistributedConnection : NetworkConnection, IStore IIPReply(packet.CallbackId); break; + case IIPPacketAction.KeepAlive: + IIPReply(packet.CallbackId, packet.CurrentTime, packet.Jitter); + break; } } @@ -781,28 +854,28 @@ public partial class DistributedConnection : NetworkConnection, IStore { Server.Membership.TokenExists(authPacket.RemoteTokenIndex, authPacket.Domain).Then(x => { - if (x != null) - { - session.RemoteAuthentication.Username = x; - session.RemoteAuthentication.TokenIndex = authPacket.RemoteTokenIndex; - remoteNonce = authPacket.RemoteNonce; - session.RemoteAuthentication.Domain = authPacket.Domain; - SendParams() - .AddUInt8(0xa0) - .AddUInt8Array(localNonce) - .Done(); - } - else - { - //Console.WriteLine("User not found"); - SendParams() - .AddUInt8(0xc0) - .AddUInt8((byte)ExceptionCode.UserOrTokenNotFound) - .AddUInt16(15) - .AddString("Token not found") - .Done(); - } - }); + if (x != null) + { + session.RemoteAuthentication.Username = x; + session.RemoteAuthentication.TokenIndex = authPacket.RemoteTokenIndex; + remoteNonce = authPacket.RemoteNonce; + session.RemoteAuthentication.Domain = authPacket.Domain; + SendParams() + .AddUInt8(0xa0) + .AddUInt8Array(localNonce) + .Done(); + } + else + { + //Console.WriteLine("User not found"); + SendParams() + .AddUInt8(0xc0) + .AddUInt8((byte)ExceptionCode.UserOrTokenNotFound) + .AddUInt16(15) + .AddString("Token not found") + .Done(); + } + }); } } catch (Exception ex) @@ -937,7 +1010,6 @@ public partial class DistributedConnection : NetworkConnection, IStore { Warehouse.Put(this.RemoteUsername, this, null, Server).Then(x => { - ready = true; openReply?.Trigger(true); OnReady?.Invoke(this); @@ -1061,6 +1133,9 @@ public partial class DistributedConnection : NetworkConnection, IStore openReply?.Trigger(true); OnReady?.Invoke(this); } + + // start perodic keep alive timer + keepAliveTimer.Start(); } } else if (authPacket.Command == IIPAuthPacket.IIPAuthPacketCommand.Error) @@ -1326,28 +1401,61 @@ public partial class DistributedConnection : NetworkConnection, IStore // clean up readyToEstablish = false; + keepAliveTimer.Stop(); + + foreach (var x in requests.Values) - x.TriggerError(new AsyncException(ErrorType.Management, 0, "Connection closed")); + { + try + { + x.TriggerError(new AsyncException(ErrorType.Management, 0, "Connection closed")); + } + catch (Exception ex) + { + Global.Log(ex); + } + } foreach (var x in resourceRequests.Values) - x.TriggerError(new AsyncException(ErrorType.Management, 0, "Connection closed")); + { + try + { + x.TriggerError(new AsyncException(ErrorType.Management, 0, "Connection closed")); + } + catch (Exception ex) + { + Global.Log(ex); + } + } foreach (var x in templateRequests.Values) - x.TriggerError(new AsyncException(ErrorType.Management, 0, "Connection closed")); + { + try + { + x.TriggerError(new AsyncException(ErrorType.Management, 0, "Connection closed")); + } + catch (Exception ex) + { + Global.Log(ex); + } + } requests.Clear(); resourceRequests.Clear(); templateRequests.Clear(); - foreach (var x in resources.Values) - x.Suspend(); - UnsubscribeAll(); - Warehouse.Remove(this); + if (Server != null) { + foreach (var x in resources.Values) + x.Suspend(); + UnsubscribeAll(); + Warehouse.Remove(this); + + if (ready) + Server.Membership?.Logout(session); + }; - if (ready) - Server?.Membership?.Logout(session); ready = false; } diff --git a/Esiur/Net/IIP/DistributedConnectionProtocol.cs b/Esiur/Net/IIP/DistributedConnectionProtocol.cs index ab64d57..6919e54 100644 --- a/Esiur/Net/IIP/DistributedConnectionProtocol.cs +++ b/Esiur/Net/IIP/DistributedConnectionProtocol.cs @@ -67,6 +67,9 @@ partial class DistributedConnection AsyncQueue queue = new AsyncQueue(); + + DateTime? lastKeepAliveReceived; + /// /// Send IIP request. /// @@ -99,18 +102,6 @@ partial class DistributedConnection internal SendList SendReply(IIPPacket.IIPPacketAction action, uint callbackId) { - /* - if (callbackId > maxcallerid) - { - maxcallerid = callbackId; - } - else - { - Console.Beep(); - - } - */ - return (SendList)SendParams().AddUInt8((byte)(0x80 | (byte)action)).AddUInt32(callbackId); } @@ -150,6 +141,53 @@ partial class DistributedConnection } + public AsyncReply StaticCall(Guid classId, byte index, Map parameters) + { + var pb = Codec.Compose(parameters, this);// Codec.ComposeVarArray(parameters, this, true); + + var reply = new AsyncReply(); + var c = callbackCounter++; + requests.Add(c, reply); + + + SendParams().AddUInt8((byte)(0x40 | (byte)IIPPacket.IIPPacketAction.StaticCall)) + .AddUInt32(c) + .AddGuid(classId) + .AddUInt8(index) + .AddUInt8Array(pb) + .Done(); + + return reply; + } + + public AsyncReply Call(string procedureCall, params object[] parameters) + { + var args = new Map(); + for (byte i = 0; i < parameters.Length; i++) + args.Add(i, parameters[i]); + return Call(procedureCall, args); + } + + public AsyncReply Call(string procedureCall, Map parameters) + { + var pb = Codec.Compose(parameters, this); + + var reply = new AsyncReply(); + var c = callbackCounter++; + requests.Add(c, reply); + + var callName = DC.ToBytes(procedureCall); + + SendParams().AddUInt8((byte)(0x40 | (byte)IIPPacket.IIPPacketAction.ProcedureCall)) + .AddUInt32(c) + .AddUInt16((ushort)callName.Length) + .AddUInt8Array(callName) + .AddUInt8Array(pb) + .Done(); + + return reply; + } + internal AsyncReply SendInvoke(uint instanceId, byte index, Map parameters) { var pb = Codec.Compose(parameters, this);// Codec.ComposeVarArray(parameters, this, true); @@ -158,17 +196,12 @@ partial class DistributedConnection var c = callbackCounter++; requests.Add(c, reply); - SendParams().AddUInt8((byte)(0x40 | (byte)Packets.IIPPacket.IIPPacketAction.InvokeFunction)) + SendParams().AddUInt8((byte)(0x40 | (byte)IIPPacket.IIPPacketAction.InvokeFunction)) .AddUInt32(c) .AddUInt32(instanceId) .AddUInt8(index) .AddUInt8Array(pb) .Done(); - - //var bl = new BinaryList((byte)(0x40 | (byte)Packets.IIPPacket.IIPPacketAction.InvokeFunctionArrayArguments), - // callbackCounter, instanceId, index, pb); - //Send(bl.ToArray()); - return reply; } @@ -1212,6 +1245,102 @@ partial class DistributedConnection return new Tuple((ushort)code, $"{source}: {msg}\n{trace}"); } + + void IIPRequestProcedureCall(uint callback, string procedureCall, TransmissionType transmissionType, byte[] content) + { + + if (Server == null) + { + SendError(ErrorType.Management, callback, (ushort)ExceptionCode.GeneralFailure); + return; + } + + var call = Server.Calls[procedureCall]; + + if (call == null) + { + SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound); + return; + } + + var (_, parsed) = Codec.Parse(content, 0, this, null, transmissionType); + + parsed.Then(results => + { + var arguments = (Map)results;// (object[])results; + + // un hold the socket to send data immediately + this.Socket.Unhold(); + + // @TODO: Make managers for procedure calls + //if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied) + //{ + // SendError(ErrorType.Management, callback, + // (ushort)ExceptionCode.InvokeDenied); + // return; + //} + + InvokeFunction(call.Method, callback, arguments, IIPPacket.IIPPacketAction.ProcedureCall, call.Target); + + }).Error(x => + { + SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ParseError); + }); + } + + void IIPRequestStaticCall(uint callback, Guid classId, byte index, TransmissionType transmissionType, byte[] content) + { + var template = Warehouse.GetTemplateByClassId(classId); + + if (template == null) + { + SendError(ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound); + return; + } + + var ft = template.GetFunctionTemplateByIndex(index); + + if (ft == null) + { + // no function at this index + SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound); + return; + } + + var (_, parsed) = Codec.Parse(content, 0, this, null, transmissionType); + + parsed.Then(results => + { + var arguments = (Map)results;// (object[])results; + + // un hold the socket to send data immediately + this.Socket.Unhold(); + + var fi = ft.MethodInfo; + + if (fi == null) + { + // ft found, fi not found, this should never happen + SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound); + return; + } + + // @TODO: Make managers for static calls + //if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied) + //{ + // SendError(ErrorType.Management, callback, + // (ushort)ExceptionCode.InvokeDenied); + // return; + //} + + InvokeFunction(fi, callback, arguments, IIPPacket.IIPPacketAction.StaticCall, null); + + }).Error(x => + { + SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ParseError); + }); + } + void IIPRequestInvokeFunction(uint callback, uint resourceId, byte index, TransmissionType transmissionType, byte[] content) { //Console.WriteLine("IIPRequestInvokeFunction " + callback + " " + resourceId + " " + index); @@ -1263,128 +1392,135 @@ partial class DistributedConnection } else { + var fi = r.GetType().GetMethod(ft.Name); - if (fi != null) - { - if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied) - { - SendError(ErrorType.Management, callback, - (ushort)ExceptionCode.InvokeDenied); - return; - } - - // cast arguments - ParameterInfo[] pis = fi.GetParameters(); - - object[] args = new object[pis.Length]; - - if (pis.Length > 0) - { - if (pis.Last().ParameterType == typeof(DistributedConnection)) - { - for (byte i = 0; i < pis.Length - 1; i++) - args[i] = arguments.ContainsKey(i) ? - DC.CastConvert(arguments[i], pis[i].ParameterType) : Type.Missing; - args[args.Length - 1] = this; - } - else - { - for (byte i = 0; i < pis.Length; i++) - args[i] = arguments.ContainsKey(i) ? - DC.CastConvert(arguments[i], pis[i].ParameterType) : Type.Missing; - } - } - - object rt; - - try - { - rt = fi.Invoke(r, args); - } - catch (Exception ex) - { - var (code, msg) = SummerizeException(ex); - SendError(ErrorType.Exception, callback, code, msg); - return; - } - - if (rt is System.Collections.IEnumerable && !(rt is Array || rt is Map || rt is string)) - { - var enu = rt as System.Collections.IEnumerable; - - try - { - foreach (var v in enu) - SendChunk(callback, v); - SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback) - .AddUInt8((byte)TransmissionTypeIdentifier.Null) - .Done(); - } - catch (Exception ex) - { - var (code, msg) = SummerizeException(ex); - SendError(ErrorType.Exception, callback, code, msg); - } - - } - else if (rt is Task) - { - (rt as Task).ContinueWith(t => - { -#if NETSTANDARD - var res = t.GetType().GetTypeInfo().GetProperty("Result").GetValue(t); -#else - var res = t.GetType().GetProperty("Result").GetValue(t); -#endif - SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback) - .AddUInt8Array(Codec.Compose(res, this)) - .Done(); - }); - - //await t; - //SendParams((byte)0x90, callback, Codec.Compose(res, this)); - } - else if (rt is AsyncReply)// Codec.ImplementsInterface(rt.GetType(), typeof(IAsyncReply<>)))// rt.GetType().GetTypeInfo().IsGenericType - //&& rt.GetType().GetGenericTypeDefinition() == typeof(IAsyncReply<>)) - { - (rt as AsyncReply).Then(res => - { - SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback) - .AddUInt8Array(Codec.Compose(res, this)) - .Done(); - }).Error(ex => - { - var (code, msg) = SummerizeException(ex); - SendError(ErrorType.Exception, callback, code, msg); - }).Progress((pt, pv, pm) => - { - SendProgress(callback, pv, pm); - }).Chunk(v => - { - SendChunk(callback, v); - }); - } - else - { - SendReply(IIPPacket.IIPPacketAction.InvokeFunction, callback) - .AddUInt8Array(Codec.Compose(rt, this)) - .Done(); - } - } - else + if (fi == null) { // ft found, fi not found, this should never happen SendError(ErrorType.Management, callback, (ushort)ExceptionCode.MethodNotFound); + return; } + + + if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied) + { + SendError(ErrorType.Management, callback, + (ushort)ExceptionCode.InvokeDenied); + return; + } + + InvokeFunction(fi, callback, arguments, IIPPacket.IIPPacketAction.InvokeFunction, r); } }); - }); } + void InvokeFunction(MethodInfo fi, uint callback, Map arguments, IIPPacket.IIPPacketAction actionType, object target = null) + { + + // cast arguments + ParameterInfo[] pis = fi.GetParameters(); + + object[] args = new object[pis.Length]; + + if (pis.Length > 0) + { + if (pis.Last().ParameterType == typeof(DistributedConnection)) + { + for (byte i = 0; i < pis.Length - 1; i++) + args[i] = arguments.ContainsKey(i) ? + DC.CastConvert(arguments[i], pis[i].ParameterType) : Type.Missing; + args[args.Length - 1] = this; + } + else + { + for (byte i = 0; i < pis.Length; i++) + args[i] = arguments.ContainsKey(i) ? + DC.CastConvert(arguments[i], pis[i].ParameterType) : Type.Missing; + } + } + + object rt; + + try + { + rt = fi.Invoke(target, args); + } + catch (Exception ex) + { + var (code, msg) = SummerizeException(ex); + msg = "Arguments: " + string.Join(", ", args.Select(x => x?.ToString() ?? "[Null]").ToArray()) + "\r\n" + msg; + + SendError(ErrorType.Exception, callback, code, msg); + return; + } + + if (rt is System.Collections.IEnumerable && !(rt is Array || rt is Map || rt is string)) + { + var enu = rt as System.Collections.IEnumerable; + + try + { + foreach (var v in enu) + SendChunk(callback, v); + SendReply(actionType, callback) + .AddUInt8((byte)TransmissionTypeIdentifier.Null) + .Done(); + } + catch (Exception ex) + { + var (code, msg) = SummerizeException(ex); + SendError(ErrorType.Exception, callback, code, msg); + } + + } + else if (rt is Task) + { + (rt as Task).ContinueWith(t => + { +#if NETSTANDARD + var res = t.GetType().GetTypeInfo().GetProperty("Result").GetValue(t); +#else + var res = t.GetType().GetProperty("Result").GetValue(t); +#endif + SendReply(actionType, callback) + .AddUInt8Array(Codec.Compose(res, this)) + .Done(); + }); + + //await t; + //SendParams((byte)0x90, callback, Codec.Compose(res, this)); + } + else if (rt is AsyncReply)// Codec.ImplementsInterface(rt.GetType(), typeof(IAsyncReply<>)))// rt.GetType().GetTypeInfo().IsGenericType + //&& rt.GetType().GetGenericTypeDefinition() == typeof(IAsyncReply<>)) + { + (rt as AsyncReply).Then(res => + { + SendReply(actionType, callback) + .AddUInt8Array(Codec.Compose(res, this)) + .Done(); + }).Error(ex => + { + var (code, msg) = SummerizeException(ex); + SendError(ErrorType.Exception, callback, code, msg); + }).Progress((pt, pv, pm) => + { + SendProgress(callback, pv, pm); + }).Chunk(v => + { + SendChunk(callback, v); + }); + } + else + { + SendReply(actionType, callback) + .AddUInt8Array(Codec.Compose(rt, this)) + .Done(); + } + } void IIPRequestListen(uint callback, uint resourceId, byte index) { @@ -1651,11 +1787,11 @@ partial class DistributedConnection { /* -#if NETSTANDARD + #if NETSTANDARD var pi = r.GetType().GetTypeInfo().GetProperty(pt.Name); -#else + #else var pi = r.GetType().GetProperty(pt.Name); -#endif*/ + #endif*/ var pi = pt.PropertyInfo; @@ -2531,4 +2667,31 @@ partial class DistributedConnection .AddUInt8Array(Codec.Compose(info.Value, this)) .Done(); } + + + + void IIPRequestKeepAlive(uint callbackId, DateTime peerTime, uint interval) + { + + uint jitter = 0; + + var now = DateTime.UtcNow; + + if (lastKeepAliveReceived != null) + { + var diff = (uint)(now - (DateTime)lastKeepAliveReceived).TotalMilliseconds; + //Console.WriteLine("Diff " + diff + " " + interval); + + jitter = (uint)Math.Abs((int)diff - (int)interval); + } + + SendParams() + .AddUInt8((byte)(0x80 | (byte)IIPPacket.IIPPacketAction.KeepAlive)) + .AddUInt32(callbackId) + .AddDateTime(now) + .AddUInt32(jitter) + .Done(); + + lastKeepAliveReceived = now; + } } diff --git a/Esiur/Net/IIP/DistributedResource.cs b/Esiur/Net/IIP/DistributedResource.cs index 219dda6..af2521a 100644 --- a/Esiur/Net/IIP/DistributedResource.cs +++ b/Esiur/Net/IIP/DistributedResource.cs @@ -222,9 +222,8 @@ public class DistributedResource : DynamicObject, IResource public AsyncReply _Invoke(byte index, Map args) { - if (destroyed) - throw new Exception("Trying to access destroyed object"); + throw new Exception("Trying to access a destroyed object"); if (suspended) throw new Exception("Trying to access suspended object"); @@ -232,11 +231,17 @@ public class DistributedResource : DynamicObject, IResource if (index >= Instance.Template.Functions.Length) throw new Exception("Function index is incorrect"); + var ft = Instance.Template.GetFunctionTemplateByIndex(index); - return connection.SendInvoke(instanceId, index, args); + if (ft == null) + throw new Exception("Function template not found."); + + if (ft.IsStatic) + return connection.StaticCall(Instance.Template.ClassId, index, args); + else + return connection.SendInvoke(instanceId, index, args); } - public AsyncReply Listen(EventTemplate et) { if (et == null) diff --git a/Esiur/Net/IIP/DistributedServer.cs b/Esiur/Net/IIP/DistributedServer.cs index 2c00590..94d6457 100644 --- a/Esiur/Net/IIP/DistributedServer.cs +++ b/Esiur/Net/IIP/DistributedServer.cs @@ -176,5 +176,11 @@ public class DistributedServer : NetworkServer, IResource } + public KeyList Calls { get; } = new KeyList(); + + public void MapCall(string call, Delegate handler) + { + Calls.Add(call, handler); + } } diff --git a/Esiur/Net/Packets/IIPPacket.cs b/Esiur/Net/Packets/IIPPacket.cs index 32860ce..40a08b9 100644 --- a/Esiur/Net/Packets/IIPPacket.cs +++ b/Esiur/Net/Packets/IIPPacket.cs @@ -121,7 +121,13 @@ class IIPPacket : Packet ClearAllAttributes, GetAttributes, UpdateAttributes, - ClearAttributes + ClearAttributes, + + + // Static calling + KeepAlive = 0x20, + ProcedureCall, + StaticCall } public enum IIPPacketReport : byte @@ -200,6 +206,11 @@ class IIPPacket : Packet public ulong FromAge { get; set; } public ulong ToAge { get; set; } + public DateTime CurrentTime { get; set; } + public uint Interval { get; set; } + public uint Jitter { get; set; } + public string Procedure { get; set; } + private uint dataLengthNeeded; private uint originalOffset; @@ -313,7 +324,7 @@ class IIPPacket : Packet MethodIndex = data[offset++]; - (var size, DataType) = TransmissionType.Parse(data, offset, ends); + (var size, DataType) = TransmissionType.Parse(data, offset, ends); //var dt = (DataType)data[offset++]; @@ -583,7 +594,7 @@ class IIPPacket : Packet MethodIndex = data[offset++]; - (var size, DataType) = TransmissionType.Parse(data, offset, ends); + (var size, DataType) = TransmissionType.Parse(data, offset, ends); if (DataType == null) return -(int)size; @@ -615,6 +626,60 @@ class IIPPacket : Packet //Content = data.Clip(offset, cl); offset += cl; } + + else if (Action == IIPPacketAction.KeepAlive) + { + if (NotEnough(offset, ends, 12)) + return -dataLengthNeeded; + + CurrentTime = data.GetDateTime(offset, Endian.Little); + offset += 8; + Interval = data.GetUInt32(offset, Endian.Little); + offset += 4; + + } + else if (Action == IIPPacketAction.ProcedureCall) + { + if (NotEnough(offset, ends, 2)) + return -dataLengthNeeded; + + var cl = data.GetUInt16(offset, Endian.Little); + offset += 2; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + Procedure = data.GetString(offset, cl); + offset += cl; + + if (NotEnough(offset, ends, 1)) + return -dataLengthNeeded; + + (var size, DataType) = TransmissionType.Parse(data, offset, ends); + + if (DataType == null) + return -(int)size; + + offset += (uint)size; + + } else if (Action == IIPPacketAction.StaticCall) + { + if (NotEnough(offset, ends, 18)) + return -dataLengthNeeded; + + ClassId = data.GetGuid(offset);//, Endian.Little); + offset += 16; + + MethodIndex = data[offset++]; + + + (var size, DataType) = TransmissionType.Parse(data, offset, ends); + + if (DataType == null) + return -(int)size; + + offset += (uint)size; + } } else if (Command == IIPPacketCommand.Reply) { @@ -641,10 +706,10 @@ class IIPPacket : Packet offset += cl; //if (NotEnough(offset, ends, 4)) - // return -dataLengthNeeded; + // return -dataLengthNeeded; - (var size, DataType) = TransmissionType.Parse(data, offset, ends); + (var size, DataType) = TransmissionType.Parse(data, offset, ends); if (DataType == null) return -(int)size; @@ -690,7 +755,7 @@ class IIPPacket : Packet if (NotEnough(offset, ends, 1)) return -dataLengthNeeded; - (var size, DataType) = TransmissionType.Parse(data, offset, ends ); + (var size, DataType) = TransmissionType.Parse(data, offset, ends); if (DataType == null) return -(int)size; @@ -706,14 +771,14 @@ class IIPPacket : Packet //Content = data.Clip(offset, cl); //offset += cl; } - else if (Action == IIPPacketAction.InvokeFunction) - //|| Action == IIPPacketAction.GetProperty - //|| Action == IIPPacketAction.GetPropertyIfModified) + else if (Action == IIPPacketAction.InvokeFunction + || Action == IIPPacketAction.ProcedureCall + || Action == IIPPacketAction.StaticCall) { if (NotEnough(offset, ends, 1)) return -dataLengthNeeded; - (var size, DataType) = TransmissionType.Parse(data, offset, ends); + (var size, DataType) = TransmissionType.Parse(data, offset, ends); if (DataType == null) return -(int)size; @@ -728,6 +793,16 @@ class IIPPacket : Packet { // nothing to do } + else if (Action == IIPPacketAction.KeepAlive) + { + if (NotEnough(offset, ends, 12)) + return -dataLengthNeeded; + + CurrentTime = data.GetDateTime(offset, Endian.Little); + offset += 8; + Jitter = data.GetUInt32(offset, Endian.Little); + offset += 4; + } } else if (Command == IIPPacketCommand.Report) { @@ -775,7 +850,7 @@ class IIPPacket : Packet return -dataLengthNeeded; - (var size, DataType) = TransmissionType.Parse(Data, offset, ends ); + (var size, DataType) = TransmissionType.Parse(Data, offset, ends); if (DataType == null) return -(int)size; diff --git a/Esiur/Proxy/TemplateGenerator.cs b/Esiur/Proxy/TemplateGenerator.cs index ccd277e..1c6960a 100644 --- a/Esiur/Proxy/TemplateGenerator.cs +++ b/Esiur/Proxy/TemplateGenerator.cs @@ -69,7 +69,7 @@ public static class TemplateGenerator var rt = new StringBuilder(); rt.AppendLine("using System;\r\nusing Esiur.Resource;\r\nusing Esiur.Core;\r\nusing Esiur.Data;\r\nusing Esiur.Net.IIP;"); - rt.AppendLine($"namespace { nameSpace} {{"); + rt.AppendLine($"namespace {nameSpace} {{"); if (template.Annotation != null) rt.AppendLine($"[Annotation({ToLiteral(template.Annotation)})]"); @@ -99,7 +99,7 @@ public static class TemplateGenerator var rt = new StringBuilder(); rt.AppendLine("using System;\r\nusing Esiur.Resource;\r\nusing Esiur.Core;\r\nusing Esiur.Data;\r\nusing Esiur.Net.IIP;"); - rt.AppendLine($"namespace { nameSpace} {{"); + rt.AppendLine($"namespace {nameSpace} {{"); if (template.Annotation != null) rt.AppendLine($"[Annotation({ToLiteral(template.Annotation)})]"); @@ -136,7 +136,7 @@ public static class TemplateGenerator representationType.Identifier == RepresentationTypeIdentifier.Tuple5 || representationType.Identifier == RepresentationTypeIdentifier.Tuple6 || representationType.Identifier == RepresentationTypeIdentifier.Tuple7) - name = "(" + String.Join(",", representationType.SubTypes.Select(x=> GetTypeName(x, templates))) + name = "(" + String.Join(",", representationType.SubTypes.Select(x => GetTypeName(x, templates))) + ")"; else { @@ -263,7 +263,7 @@ public static class TemplateGenerator var rt = new StringBuilder(); rt.AppendLine("using System;\r\nusing Esiur.Resource;\r\nusing Esiur.Core;\r\nusing Esiur.Data;\r\nusing Esiur.Net.IIP;"); - rt.AppendLine($"namespace { nameSpace} {{"); + rt.AppendLine($"namespace {nameSpace} {{"); if (template.Annotation != null) rt.AppendLine($"[Annotation({ToLiteral(template.Annotation)})]"); @@ -289,37 +289,55 @@ public static class TemplateGenerator var optionalArgs = f.Arguments.Where((x) => x.Optional).ToArray(); - rt.Append($"[Public] public AsyncReply<{rtTypeName}> {f.Name}("); - - - if (positionalArgs.Length > 0) - rt.Append( - String.Join(", ", positionalArgs.Select((a) => GetTypeName(a.Type, templates) + " " + a.Name))); - - if (optionalArgs.Length > 0) + if (f.IsStatic) { - if (positionalArgs.Length > 0) rt.Append(","); + rt.Append($"[Public] public static AsyncReply<{rtTypeName}> {f.Name}(DistributedConnection connection"); + + if (positionalArgs.Length > 0) + rt.Append(", " + + String.Join(", ", positionalArgs.Select((a) => GetTypeName(a.Type, templates) + " " + a.Name))); + + if (optionalArgs.Length > 0) + rt.Append(", " + + String.Join(", ", optionalArgs.Select((a) => GetTypeName(a.Type.ToNullable(), templates) + " " + a.Name + " = null"))); - rt.Append( - String.Join(", ", optionalArgs.Select((a) => GetTypeName(a.Type.ToNullable(), templates) + " " + a.Name + " = null"))); } + else + { + rt.Append($"[Public] public AsyncReply<{rtTypeName}> {f.Name}("); - //rt.Append(string.Join(",", f.Arguments.Select(x => GetTypeName(x.Type, templates) + " " + x.Name))); + if (positionalArgs.Length > 0) + rt.Append( + String.Join(", ", positionalArgs.Select((a) => GetTypeName(a.Type, templates) + " " + a.Name))); + + if (optionalArgs.Length > 0) + { + if (positionalArgs.Length > 0) rt.Append(","); + + rt.Append( + String.Join(", ", optionalArgs.Select((a) => GetTypeName(a.Type.ToNullable(), templates) + " " + a.Name + " = null"))); + } + } rt.AppendLine(") {"); rt.AppendLine( - $"var args = new Map(){{{ String.Join(", ", positionalArgs.Select((e) => "[" + e.Index + "] = " + e.Name))}}};"); + $"var args = new Map(){{{String.Join(", ", positionalArgs.Select((e) => "[" + e.Index + "] = " + e.Name))}}};"); - foreach(var a in optionalArgs) { + foreach (var a in optionalArgs) + { rt.AppendLine( $"if ({a.Name} != null) args[{a.Index}] = {a.Name};"); } - + rt.AppendLine($"var rt = new AsyncReply<{rtTypeName}>();"); - //rt.AppendLine($"_Invoke({f.Index}, new Map[] {{ { string.Join(", ", f.Arguments.Select(x => x.Name)) } }})"); - rt.AppendLine($"_Invoke({f.Index}, args)"); + + if (f.IsStatic) + rt.AppendLine($"connection.StaticCall(Guid.Parse(\"{template.ClassId.ToString()}\"), {f.Index}, args)"); + else + rt.AppendLine($"_Invoke({f.Index}, args)"); + rt.AppendLine($".Then(x => rt.Trigger(({rtTypeName})x))"); rt.AppendLine($".Error(x => rt.TriggerError(x))"); rt.AppendLine($".Chunk(x => rt.TriggerChunk(x));"); @@ -350,7 +368,7 @@ public static class TemplateGenerator if (template.Events.Length > 0) { - + rt.AppendLine("protected override void _EmitEventByIndex(byte index, object args) {"); rt.AppendLine("switch (index) {"); diff --git a/Esiur/Resource/Template/FunctionTemplate.cs b/Esiur/Resource/Template/FunctionTemplate.cs index 435f321..0b6f9aa 100644 --- a/Esiur/Resource/Template/FunctionTemplate.cs +++ b/Esiur/Resource/Template/FunctionTemplate.cs @@ -24,6 +24,8 @@ public class FunctionTemplate : MemberTemplate public RepresentationType ReturnType { get; set; } + public bool IsStatic { get; set; } + public ArgumentTemplate[] Arguments { get; set; } public MethodInfo MethodInfo @@ -53,20 +55,20 @@ public class FunctionTemplate : MemberTemplate var exp = DC.ToBytes(Annotation); bl.AddInt32(exp.Length) .AddUInt8Array(exp); - bl.InsertUInt8(0, Inherited ? (byte)0x90 : (byte)0x10); + bl.InsertUInt8(0, (byte)((Inherited ? (byte)0x90 : (byte)0x10) | (IsStatic ? 0x4 : 0))); } else - bl.InsertUInt8(0, Inherited ? (byte)0x80 : (byte)0x0); + bl.InsertUInt8(0, (byte)((Inherited ? (byte)0x80 : (byte)0x0) | (IsStatic ? 0x4 : 0))); return bl.ToArray(); } - public FunctionTemplate(TypeTemplate template, byte index, string name, bool inherited, ArgumentTemplate[] arguments, RepresentationType returnType, string annotation = null) + public FunctionTemplate(TypeTemplate template, byte index, string name, bool inherited, bool isStatic, ArgumentTemplate[] arguments, RepresentationType returnType, string annotation = null) : base(template, index, name, inherited) { - //this.IsVoid = isVoid; this.Arguments = arguments; this.ReturnType = returnType; this.Annotation = annotation; + this.IsStatic = isStatic; } } diff --git a/Esiur/Resource/Template/TypeTemplate.cs b/Esiur/Resource/Template/TypeTemplate.cs index 974257b..506270e 100644 --- a/Esiur/Resource/Template/TypeTemplate.cs +++ b/Esiur/Resource/Template/TypeTemplate.cs @@ -409,9 +409,9 @@ public class TypeTemplate - PropertyInfo[] propsInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);// | BindingFlags.DeclaredOnly); - EventInfo[] eventsInfo = type.GetEvents(BindingFlags.Public | BindingFlags.Instance);// | BindingFlags.DeclaredOnly); - MethodInfo[] methodsInfo = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); // | BindingFlags.DeclaredOnly); + PropertyInfo[] propsInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + EventInfo[] eventsInfo = type.GetEvents(BindingFlags.Public | BindingFlags.Instance); + MethodInfo[] methodsInfo = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); FieldInfo[] constantsInfo = type.GetFields(BindingFlags.Public | BindingFlags.Static); @@ -423,7 +423,7 @@ public class TypeTemplate var annotationAttr = ci.GetCustomAttribute(true); var nullableAttr = ci.GetCustomAttribute(true); - var valueType = RepresentationType.FromType(ci.FieldType);//, nullable != null && nullable.NullableFlags[0] == 2); + var valueType = RepresentationType.FromType(ci.FieldType); if (valueType == null) throw new Exception($"Unsupported type `{ci.FieldType}` in constant `{type.Name}.{ci.Name}`"); @@ -447,8 +447,6 @@ public class TypeTemplate RepresentationType.FromType(pi.PropertyType.GetGenericArguments()[0]) : RepresentationType.FromType(pi.PropertyType); - //var propType = RepresentationType.FromType(pi.PropertyType);//, nullableAttr != null && nullableAttr.Flag == 2); - if (propType == null) throw new Exception($"Unsupported type `{pi.PropertyType}` in property `{type.Name}.{pi.Name}`"); @@ -497,7 +495,7 @@ public class TypeTemplate var addEvent = (EventInfo ei, PublicAttribute publicAttr) => { var argType = ei.EventHandlerType.GenericTypeArguments[0]; - var evtType = RepresentationType.FromType(argType);//, argIsNull); + var evtType = RepresentationType.FromType(argType); if (evtType == null) throw new Exception($"Unsupported type `{argType}` in event `{type.Name}.{ei.Name}`"); @@ -641,7 +639,9 @@ public class TypeTemplate var fn = publicAttr.Name ?? mi.Name; - var ft = new FunctionTemplate(this, (byte)functions.Count, fn, mi.DeclaringType != type, arguments, rtType); + var ft = new FunctionTemplate(this, (byte)functions.Count, fn, mi.DeclaringType != type, + mi.IsStatic, + arguments, rtType); if (annotationAttr != null) ft.Annotation = annotationAttr.Annotation; @@ -918,6 +918,9 @@ public class TypeTemplate if (type == 0) // function { string annotation = null; + var isStatic = ((data[offset] & 0x4) == 0x4); + + var hasAnnotation = ((data[offset++] & 0x10) == 0x10); var name = data.GetString(offset + 1, data[offset]); @@ -947,7 +950,7 @@ public class TypeTemplate offset += cs; } - var ft = new FunctionTemplate(od, functionIndex++, name, inherited, arguments.ToArray(), returnType, annotation); + var ft = new FunctionTemplate(od, functionIndex++, name, inherited, isStatic, arguments.ToArray(), returnType, annotation); od.functions.Add(ft); } diff --git a/Test/MyService.cs b/Test/MyService.cs index c258976..0971fd9 100644 --- a/Test/MyService.cs +++ b/Test/MyService.cs @@ -37,6 +37,8 @@ public partial class MyService return new MyGenericRecord() { Needed = 3, Start = 10, Results = new MyResource[0], Total = 102 }; } + [Public] public static string staticFunction(string name) => $"Hello {name}"; + [Public] byte uInt8Test = 8; [Public] byte? uInt8Null = null; [Public] byte[] uInt8Array = new byte[] { 0, 1, 2, 3, 4, 5 }; diff --git a/Test/Program.cs b/Test/Program.cs index 9bc4724..fbc42b9 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -57,7 +57,14 @@ namespace Test // Create stores to keep objects. var system = await Warehouse.Put("sys", new MemoryStore()); var server = await Warehouse.Put("sys/server", new DistributedServer()); - var web = await Warehouse.Put("sys/web", new HTTPServer() { Port = 8888}); + + server.MapCall("Hello", (string msg, DateTime time, DistributedConnection sender) => + { + Console.WriteLine(msg); + return "Hi " + DateTime.UtcNow; + }); + + var web = await Warehouse.Put("sys/web", new HTTPServer() { Port = 8088}); var service = await Warehouse.Put("sys/service", new MyService()); var res1 = await Warehouse.Put("sys/service/r1", new MyResource() { Description = "Testing 1", CategoryId = 10 }); @@ -95,6 +102,9 @@ namespace Test dynamic remote = await Warehouse.Get("iip://localhost/sys/service"); var con = remote.Connection as DistributedConnection; + + var pcall = await con.Call("Hello", "whats up ?", DateTime.UtcNow); + var template = await con.GetTemplateByClassName("Test.MyResource");