mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-04-04 12:28:21 +00:00
Layout
This commit is contained in:
52
Libraries/Esiur/Core/AsyncAwaiter.cs
Normal file
52
Libraries/Esiur/Core/AsyncAwaiter.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public class AsyncAwaiter : INotifyCompletion
|
||||
{
|
||||
Action callback = null;
|
||||
|
||||
AsyncException exception = null;
|
||||
|
||||
object result;
|
||||
|
||||
|
||||
public AsyncAwaiter(AsyncReply reply)
|
||||
{
|
||||
reply.Then(x =>
|
||||
{
|
||||
this.IsCompleted = true;
|
||||
this.result = x;
|
||||
this.callback?.Invoke();
|
||||
}).Error(x =>
|
||||
{
|
||||
exception = x;
|
||||
this.IsCompleted = true;
|
||||
this.callback?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public object GetResult()
|
||||
{
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
if (IsCompleted)
|
||||
continuation?.Invoke();
|
||||
else
|
||||
// Continue....
|
||||
callback = continuation;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
50
Libraries/Esiur/Core/AsyncAwaiterGeneric.cs
Normal file
50
Libraries/Esiur/Core/AsyncAwaiterGeneric.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public class AsyncAwaiter<T> : INotifyCompletion
|
||||
{
|
||||
Action callback = null;
|
||||
|
||||
AsyncException exception = null;
|
||||
|
||||
T result;
|
||||
|
||||
public AsyncAwaiter(AsyncReply<T> reply)
|
||||
{
|
||||
reply.Then(x =>
|
||||
{
|
||||
this.IsCompleted = true;
|
||||
this.result = (T)x;
|
||||
this.callback?.Invoke();
|
||||
}).Error(x =>
|
||||
{
|
||||
exception = x;
|
||||
this.IsCompleted = true;
|
||||
this.callback?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public T GetResult()
|
||||
{
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
if (IsCompleted)
|
||||
continuation?.Invoke();
|
||||
else
|
||||
// Continue....
|
||||
callback = continuation;
|
||||
}
|
||||
|
||||
}
|
||||
159
Libraries/Esiur/Core/AsyncBag.cs
Normal file
159
Libraries/Esiur/Core/AsyncBag.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
interface IAsyncBag
|
||||
{
|
||||
public void Add(object replyOrValue);
|
||||
}
|
||||
|
||||
public class AsyncBag<T> : AsyncReply, IAsyncBag
|
||||
{
|
||||
|
||||
protected List<object> replies = new List<object>();
|
||||
//List<T> results = new();
|
||||
|
||||
int count = 0;
|
||||
bool sealedBag = false;
|
||||
|
||||
|
||||
public virtual Type ArrayType { get; set; } = typeof(T);
|
||||
|
||||
public AsyncBag<T> Then(Action<T[]> callback)
|
||||
{
|
||||
//if (!sealedBag && !resultReady)
|
||||
// throw new Exception("Not sealed");
|
||||
|
||||
//Timeout(6000, () =>
|
||||
//{
|
||||
//Console.WriteLine("Timeout " + count + this.Result);
|
||||
//});
|
||||
|
||||
base.Then(new Action<object>(o => callback((T[])o)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public new AsyncBagAwaiter<T> GetAwaiter()
|
||||
{
|
||||
return new AsyncBagAwaiter<T>(this);
|
||||
}
|
||||
|
||||
public new T[] Wait()
|
||||
{
|
||||
return (T[])base.Wait();
|
||||
}
|
||||
|
||||
public new T[] Wait(int timeout)
|
||||
{
|
||||
return (T[])base.Wait(timeout);
|
||||
}
|
||||
|
||||
public void Seal()
|
||||
{
|
||||
if (sealedBag)
|
||||
return;
|
||||
|
||||
sealedBag = true;
|
||||
|
||||
var results = ArrayType == null ? new T[replies.Count]
|
||||
: Array.CreateInstance(ArrayType, replies.Count);
|
||||
|
||||
if (replies.Count == 0)
|
||||
{
|
||||
Trigger(results);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < replies.Count; i++)
|
||||
{
|
||||
|
||||
var k = replies[i];
|
||||
var index = i;
|
||||
|
||||
if (k is AsyncReply reply)
|
||||
{
|
||||
reply.Then((r) =>
|
||||
{
|
||||
results.SetValue(r, index);
|
||||
count++;
|
||||
if (count == replies.Count)
|
||||
Trigger(results);
|
||||
}).Error(e => TriggerError(e));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ArrayType != null)
|
||||
replies[i] = RuntimeCaster.Cast(replies[i], ArrayType);
|
||||
|
||||
results.SetValue(replies[i], index);
|
||||
count++;
|
||||
if (count == replies.Count)
|
||||
Trigger(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Add(object valueOrReply)
|
||||
{
|
||||
if (!sealedBag)
|
||||
{
|
||||
//if (valueOrReply is AsyncReply)
|
||||
//{
|
||||
// results.Add(default(T));
|
||||
replies.Add(valueOrReply);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void AddBag(AsyncBag<T> bag)
|
||||
{
|
||||
foreach (var r in bag.replies)
|
||||
Add(r);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AsyncBag()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AsyncBag(T[] results)
|
||||
: base(results)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
51
Libraries/Esiur/Core/AsyncBagAwaiter.cs
Normal file
51
Libraries/Esiur/Core/AsyncBagAwaiter.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public class AsyncBagAwaiter<T> : INotifyCompletion
|
||||
{
|
||||
Action callback = null;
|
||||
|
||||
AsyncException exception = null;
|
||||
|
||||
T[] result;
|
||||
|
||||
public AsyncBagAwaiter(AsyncBag<T> reply)
|
||||
{
|
||||
reply.Then(x =>
|
||||
{
|
||||
this.IsCompleted = true;
|
||||
this.result = x;
|
||||
this.callback?.Invoke();
|
||||
}).Error(x =>
|
||||
{
|
||||
exception = x;
|
||||
this.IsCompleted = true;
|
||||
this.callback?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public T[] GetResult()
|
||||
{
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
if (IsCompleted)
|
||||
continuation?.Invoke();
|
||||
else
|
||||
// Continue....
|
||||
callback = continuation;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
56
Libraries/Esiur/Core/AsyncException.cs
Normal file
56
Libraries/Esiur/Core/AsyncException.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public class AsyncException : Exception
|
||||
{
|
||||
public readonly ErrorType Type;
|
||||
public readonly ExceptionCode Code;
|
||||
|
||||
public AsyncException(Exception exception) : base(exception.Message, exception)
|
||||
{
|
||||
Type = ErrorType.Exception;
|
||||
Code = 0;
|
||||
}
|
||||
|
||||
public override string StackTrace => InnerException != null && Type == ErrorType.Exception ? InnerException.StackTrace : base.StackTrace;
|
||||
|
||||
public AsyncException(ErrorType type, ushort code, string message)
|
||||
: base(type == ErrorType.Management ? ((ExceptionCode)code).ToString() : message)
|
||||
{
|
||||
this.Type = type;
|
||||
this.Code = (ExceptionCode)code;
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Code.ToString() + ": " + Message;
|
||||
}
|
||||
}
|
||||
147
Libraries/Esiur/Core/AsyncQueue.cs
Normal file
147
Libraries/Esiur/Core/AsyncQueue.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public struct AsyncQueueItem<T>
|
||||
{
|
||||
public AsyncReply<T> Reply;
|
||||
public int Sequence;
|
||||
public DateTime Arrival;
|
||||
public DateTime Delivered;
|
||||
public DateTime Ready;
|
||||
public int BatchSize;
|
||||
public int FlushId;
|
||||
public int NotificationsCountWaitingInTheQueueAtEnqueueing;
|
||||
public bool HasResource;
|
||||
}
|
||||
|
||||
|
||||
public class AsyncQueue<T> : AsyncReply<T>
|
||||
{
|
||||
|
||||
int currentId = 0;
|
||||
int currentFlushId;
|
||||
|
||||
public List<AsyncQueueItem<T>> Processed = new();
|
||||
|
||||
List<AsyncQueueItem<T>> list = new List<AsyncQueueItem<T>>();
|
||||
//Action<T> callback;
|
||||
object queueLock = new object();
|
||||
|
||||
//public AsyncQueue<T> Then(Action<T> callback)
|
||||
//{
|
||||
// base.Then(new Action<object>(o => callback((T)o)));
|
||||
|
||||
//return this;
|
||||
//}
|
||||
|
||||
public void Add(AsyncReply<T> reply)
|
||||
{
|
||||
lock (queueLock)
|
||||
{
|
||||
currentId++;
|
||||
list.Add(new AsyncQueueItem<T>()
|
||||
{
|
||||
Sequence = currentId,
|
||||
NotificationsCountWaitingInTheQueueAtEnqueueing = list.Count,
|
||||
Reply = reply,
|
||||
Arrival = DateTime.Now,
|
||||
HasResource = !reply.Ready
|
||||
});
|
||||
}
|
||||
|
||||
resultReady = false;
|
||||
if (reply.Ready)
|
||||
processQueue(default(T));
|
||||
else
|
||||
reply.Then(processQueue);
|
||||
}
|
||||
|
||||
public void Remove(AsyncReply<T> reply)
|
||||
{
|
||||
lock (queueLock)
|
||||
{
|
||||
var item = list.FirstOrDefault(i => i.Reply == reply);
|
||||
list.Remove(item);
|
||||
}
|
||||
|
||||
processQueue(default(T));
|
||||
}
|
||||
|
||||
void processQueue(T o)
|
||||
{
|
||||
lock (queueLock)
|
||||
{
|
||||
var batchSize = 0;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].Reply.Ready)
|
||||
{
|
||||
batchSize++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var flushId = currentFlushId++;
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
if (list[i].Reply.Ready)
|
||||
{
|
||||
Trigger(list[i].Reply.Result);
|
||||
resultReady = false;
|
||||
|
||||
var p = list[i];
|
||||
p.Delivered = DateTime.Now;
|
||||
p.Ready = p.Reply.ReadyTime;
|
||||
p.BatchSize = batchSize;
|
||||
p.FlushId = flushId;
|
||||
//p.HasResource = p.Reply. (p.Ready - p.Arrival).TotalMilliseconds > 5;
|
||||
Processed.Add(p);
|
||||
|
||||
list.RemoveAt(i);
|
||||
|
||||
i--;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
resultReady = (list.Count == 0);
|
||||
}
|
||||
|
||||
public AsyncQueue()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
432
Libraries/Esiur/Core/AsyncReply.cs
Normal file
432
Libraries/Esiur/Core/AsyncReply.cs
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Esiur.Resource;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
[AsyncMethodBuilder(typeof(AsyncReplyBuilder))]
|
||||
public class AsyncReply
|
||||
{
|
||||
|
||||
public DateTime ReadyTime;
|
||||
|
||||
protected List<Action<object>> callbacks = new List<Action<object>>();
|
||||
protected object result;
|
||||
|
||||
protected List<Action<AsyncException>> errorCallbacks = null;
|
||||
|
||||
protected List<Action<ProgressType, uint, uint>> progressCallbacks = null;
|
||||
|
||||
protected List<Action<object>> chunkCallbacks = null;
|
||||
|
||||
protected List<Action<object>> propagationCallbacks = null;
|
||||
protected List<Action<byte, string>> warningCallbacks = null;
|
||||
|
||||
|
||||
object asyncLock = new object();
|
||||
|
||||
//public Timer timeout;// = new Timer()
|
||||
protected bool resultReady = false;
|
||||
AsyncException exception;
|
||||
// StackTrace trace;
|
||||
AutoResetEvent mutex = new AutoResetEvent(false);
|
||||
|
||||
public static int MaxId;
|
||||
|
||||
public int Id;
|
||||
|
||||
public bool Ready
|
||||
{
|
||||
get { return resultReady; }
|
||||
}
|
||||
|
||||
public bool Failed => exception != null;
|
||||
|
||||
public Exception Exception => exception;
|
||||
|
||||
public static AsyncReply<T> FromResult<T>(T result) => new AsyncReply<T>(result);
|
||||
|
||||
public object Wait()
|
||||
{
|
||||
if (resultReady)
|
||||
return result;
|
||||
|
||||
mutex.WaitOne();
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//int timeoutMilliseconds = 0;
|
||||
public AsyncReply Timeout(int milliseconds, Action callback = null)
|
||||
{
|
||||
|
||||
//timeoutMilliseconds = milliseconds;
|
||||
|
||||
Task.Delay(milliseconds).ContinueWith(x =>
|
||||
{
|
||||
if (!resultReady && exception == null)
|
||||
{
|
||||
TriggerError(new AsyncException(ErrorType.Management,
|
||||
(ushort)ExceptionCode.Timeout, "Execution timeout expired."));
|
||||
|
||||
callback?.Invoke();
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Wait(int millisecondsTimeout)
|
||||
{
|
||||
if (resultReady)
|
||||
return result;
|
||||
|
||||
//if (Debug)
|
||||
// Console.WriteLine($"AsyncReply: {Id} Wait");
|
||||
|
||||
if (!mutex.WaitOne(millisecondsTimeout))
|
||||
{
|
||||
var e = new Exception("AsyncReply timeout");
|
||||
TriggerError(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
//if (Debug)
|
||||
// Console.WriteLine($"AsyncReply: {Id} Wait ended");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public object Result
|
||||
{
|
||||
get { return result; }
|
||||
}
|
||||
|
||||
|
||||
protected string codePath, codeMethod;
|
||||
protected int codeLine;
|
||||
|
||||
public AsyncReply Then(Action<object> callback, [CallerMemberName] string methodName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
if (codeLine == 0)
|
||||
{
|
||||
codeLine = lineNumber; codeMethod = methodName; codePath = filePath;
|
||||
}
|
||||
//lock (callbacksLock)
|
||||
//{
|
||||
lock (asyncLock)
|
||||
{
|
||||
// trace = new StackTrace();
|
||||
|
||||
if (resultReady)
|
||||
{
|
||||
//if (Debug)
|
||||
// Console.WriteLine($"AsyncReply: {Id} Then ready");
|
||||
|
||||
callback(result);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
//timeout = new Timer(x =>
|
||||
//{
|
||||
// // Get calling method name
|
||||
// Console.WriteLine(trace.GetFrame(1).GetMethod().Name);
|
||||
|
||||
// var tr = String.Join("\r\n", trace.GetFrames().Select(f => f.GetMethod().Name));
|
||||
// timeout.Dispose();
|
||||
|
||||
// tr = trace.ToString();
|
||||
// throw new Exception("Request timeout " + Id);
|
||||
//}, null, 15000, 0);
|
||||
|
||||
|
||||
//if (Debug)
|
||||
// Console.WriteLine($"AsyncReply: {Id} Then pending");
|
||||
|
||||
callbacks.Add(callback);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AsyncReply Error(Action<AsyncException> callback)
|
||||
{
|
||||
|
||||
if (errorCallbacks == null)
|
||||
errorCallbacks = new List<Action<AsyncException>>();
|
||||
|
||||
errorCallbacks.Add(callback);
|
||||
|
||||
if (exception != null)
|
||||
callback(exception);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply Progress(Action<ProgressType, uint, uint> callback)
|
||||
{
|
||||
if (progressCallbacks == null)
|
||||
progressCallbacks = new List<Action<ProgressType, uint, uint>>();
|
||||
|
||||
progressCallbacks.Add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply Warning(Action<byte, string> callback)
|
||||
{
|
||||
if (warningCallbacks == null)
|
||||
warningCallbacks = new List<Action<byte, string>>();
|
||||
|
||||
warningCallbacks.Add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply Chunk(Action<object> callback)
|
||||
{
|
||||
if (chunkCallbacks == null)
|
||||
chunkCallbacks = new List<Action<object>>();
|
||||
|
||||
chunkCallbacks.Add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply Propagation(Action<object> callback)
|
||||
{
|
||||
if (propagationCallbacks == null)
|
||||
propagationCallbacks = new List<Action<object>>();
|
||||
|
||||
propagationCallbacks.Add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply Trigger(object result)
|
||||
{
|
||||
lock (asyncLock)
|
||||
{
|
||||
ReadyTime = DateTime.Now;
|
||||
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (exception != null)
|
||||
return this;
|
||||
|
||||
//if (Debug)
|
||||
// Console.WriteLine($"AsyncReply: {Id} Trigger");
|
||||
|
||||
if (resultReady)
|
||||
return this;
|
||||
|
||||
this.result = result;
|
||||
|
||||
resultReady = true;
|
||||
|
||||
//if (mutex != null)
|
||||
mutex.Set();
|
||||
|
||||
foreach (var cb in callbacks)
|
||||
cb(result);
|
||||
|
||||
|
||||
//if (Debug)
|
||||
// Console.WriteLine($"AsyncReply: {Id} Trigger ended");
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply TriggerError(Exception exception)
|
||||
{
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (resultReady)
|
||||
return this;
|
||||
|
||||
if (exception is AsyncException)
|
||||
this.exception = exception as AsyncException;
|
||||
else
|
||||
this.exception = new AsyncException(exception);
|
||||
|
||||
if (errorCallbacks != null)
|
||||
{
|
||||
foreach (var cb in errorCallbacks)
|
||||
cb(this.exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no error handlers found
|
||||
throw exception;
|
||||
}
|
||||
|
||||
mutex?.Set();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply TriggerProgress(ProgressType type, uint value, uint max)
|
||||
{
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (progressCallbacks != null)
|
||||
foreach (var cb in progressCallbacks)
|
||||
cb(type, value, max);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply TriggerWarning(byte level, string message)
|
||||
{
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (warningCallbacks != null)
|
||||
foreach (var cb in warningCallbacks)
|
||||
cb(level, message);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public AsyncReply TriggerPropagation(object value)
|
||||
{
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (propagationCallbacks != null)
|
||||
foreach (var cb in propagationCallbacks)
|
||||
cb(value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AsyncReply TriggerChunk(object value)
|
||||
{
|
||||
|
||||
//timeout?.Dispose();
|
||||
|
||||
|
||||
if (chunkCallbacks != null)
|
||||
foreach (var cb in chunkCallbacks)
|
||||
cb(value);
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncAwaiter GetAwaiter()
|
||||
{
|
||||
return new AsyncAwaiter(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AsyncReply()
|
||||
{
|
||||
// this.Debug = true;
|
||||
Id = MaxId++;
|
||||
}
|
||||
|
||||
public AsyncReply(object result)
|
||||
{
|
||||
// this.Debug = true;
|
||||
|
||||
ReadyTime = DateTime.Now;
|
||||
|
||||
resultReady = true;
|
||||
this.result = result;
|
||||
|
||||
Id = MaxId++;
|
||||
}
|
||||
|
||||
/*
|
||||
public AsyncReply<T> Then(Action<T> callback)
|
||||
{
|
||||
base.Then(new Action<object>(o => callback((T)o)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Trigger(T result)
|
||||
{
|
||||
Trigger((object)result);
|
||||
}
|
||||
|
||||
public Task<bool> MoveNext(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncReply()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public new Task<T> Task
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Task.ContinueWith<T>((t) =>
|
||||
{
|
||||
|
||||
#if NETSTANDARD
|
||||
return (T)t.GetType().GetTypeInfo().GetProperty("Result").GetValue(t);
|
||||
#else
|
||||
return (T)t.GetType().GetProperty("Result").GetValue(t);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public T Current => throw new NotImplementedException();
|
||||
|
||||
public AsyncReply(T result)
|
||||
: base(result)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
}
|
||||
68
Libraries/Esiur/Core/AsyncReplyBuilder.cs
Normal file
68
Libraries/Esiur/Core/AsyncReplyBuilder.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Esiur.Misc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public class AsyncReplyBuilder
|
||||
{
|
||||
AsyncReply reply;
|
||||
|
||||
AsyncReplyBuilder(AsyncReply reply)
|
||||
{
|
||||
this.reply = reply;
|
||||
}
|
||||
|
||||
public static AsyncReplyBuilder Create()
|
||||
{
|
||||
return new AsyncReplyBuilder(new AsyncReply());
|
||||
}
|
||||
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
Global.Log("AsyncReplyBuilder", LogType.Debug, "SetStateMachine");
|
||||
}
|
||||
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
reply.TriggerError(exception);
|
||||
}
|
||||
|
||||
public void SetResult()
|
||||
{
|
||||
reply.Trigger(null);
|
||||
}
|
||||
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.OnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
|
||||
public AsyncReply Task
|
||||
{
|
||||
get
|
||||
{
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
68
Libraries/Esiur/Core/AsyncReplyBuilderGeneric.cs
Normal file
68
Libraries/Esiur/Core/AsyncReplyBuilderGeneric.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Esiur.Misc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public class AsyncReplyBuilder<T>
|
||||
{
|
||||
AsyncReply<T> reply;
|
||||
|
||||
AsyncReplyBuilder(AsyncReply<T> reply)
|
||||
{
|
||||
this.reply = reply;
|
||||
}
|
||||
|
||||
public static AsyncReplyBuilder<T> Create()
|
||||
{
|
||||
return new AsyncReplyBuilder<T>(new AsyncReply<T>());
|
||||
}
|
||||
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
Global.Log("AsyncReplyBuilderGeneric", LogType.Debug, "SetStateMachine");
|
||||
}
|
||||
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
reply.TriggerError(exception);
|
||||
}
|
||||
|
||||
public void SetResult(T result)
|
||||
{
|
||||
reply.Trigger(result);
|
||||
}
|
||||
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.OnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
|
||||
public AsyncReply<T> Task
|
||||
{
|
||||
get
|
||||
{
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
384
Libraries/Esiur/Core/AsyncReplyGeneric.cs
Normal file
384
Libraries/Esiur/Core/AsyncReplyGeneric.cs
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Esiur.Resource;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
[AsyncMethodBuilder(typeof(AsyncReplyBuilder<>))]
|
||||
public class AsyncReply<T> : AsyncReply
|
||||
{
|
||||
|
||||
public AsyncReply<T> Then(Action<T> callback, [CallerMemberName] string methodName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
if (base.codeLine == 0)
|
||||
{
|
||||
base.codeLine = lineNumber; base.codeMethod = methodName; base.codePath = filePath;
|
||||
}
|
||||
|
||||
base.Then((x) => callback((T)x));
|
||||
return this;
|
||||
}
|
||||
|
||||
public new AsyncReply<T> Progress(Action<ProgressType, uint, uint> callback)
|
||||
{
|
||||
base.Progress(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public AsyncReply<T> Chunk(Action<T> callback)
|
||||
{
|
||||
chunkCallbacks.Add((x) => callback((T)x));
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncReply(T result)
|
||||
: base(result)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public AsyncReply()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public new AsyncAwaiter<T> GetAwaiter()
|
||||
{
|
||||
return new AsyncAwaiter<T>(this);
|
||||
}
|
||||
|
||||
public new T Wait()
|
||||
{
|
||||
return (T)base.Wait();
|
||||
}
|
||||
|
||||
public new T Wait(int millisecondsTimeout)
|
||||
{
|
||||
return (T)base.Wait(millisecondsTimeout);
|
||||
}
|
||||
|
||||
/*
|
||||
protected new List<Action> callbacks = new List<Action>();
|
||||
protected new object result;
|
||||
|
||||
protected new List<Action<AsyncException>> errorCallbacks = new List<Action<AsyncException>>();
|
||||
|
||||
protected new List<Action<ProgressType, int, int>> progressCallbacks = new List<Action<ProgressType, int, int>>();
|
||||
|
||||
protected new List<Action> chunkCallbacks = new List<Action>();
|
||||
|
||||
//List<AsyncAwaiter> awaiters = new List<AsyncAwaiter>();
|
||||
|
||||
object asyncLock = new object();
|
||||
|
||||
//public Timer timeout;// = new Timer()
|
||||
|
||||
AsyncException exception;
|
||||
// StackTrace trace;
|
||||
AutoResetEvent mutex = new AutoResetEvent(false);
|
||||
|
||||
public static int MaxId;
|
||||
|
||||
public int Id;
|
||||
|
||||
public bool Ready
|
||||
{
|
||||
get { return resultReady; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
public T Wait()
|
||||
{
|
||||
|
||||
if (resultReady)
|
||||
return result;
|
||||
|
||||
if (Debug)
|
||||
Console.WriteLine($"AsyncReply: {Id} Wait");
|
||||
|
||||
//mutex = new AutoResetEvent(false);
|
||||
mutex.WaitOne();
|
||||
|
||||
if (Debug)
|
||||
Console.WriteLine($"AsyncReply: {Id} Wait ended");
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public object Result
|
||||
{
|
||||
get { return result; }
|
||||
}
|
||||
|
||||
|
||||
public IAsyncReply<T> Then(Action<T> callback)
|
||||
{
|
||||
//lock (callbacksLock)
|
||||
//{
|
||||
lock (asyncLock)
|
||||
{
|
||||
// trace = new StackTrace();
|
||||
|
||||
if (resultReady)
|
||||
{
|
||||
if (Debug)
|
||||
Console.WriteLine($"AsyncReply: {Id} Then ready");
|
||||
|
||||
callback(result);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
//timeout = new Timer(x =>
|
||||
//{
|
||||
// // Get calling method name
|
||||
// Console.WriteLine(trace.GetFrame(1).GetMethod().Name);
|
||||
|
||||
// var tr = String.Join("\r\n", trace.GetFrames().Select(f => f.GetMethod().Name));
|
||||
// timeout.Dispose();
|
||||
|
||||
// tr = trace.ToString();
|
||||
// throw new Exception("Request timeout " + Id);
|
||||
//}, null, 15000, 0);
|
||||
|
||||
|
||||
if (Debug)
|
||||
Console.WriteLine($"AsyncReply: {Id} Then pending");
|
||||
|
||||
|
||||
|
||||
callbacks.Add(callback);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public IAsyncReply<T> Error(Action<AsyncException> callback)
|
||||
{
|
||||
// lock (callbacksLock)
|
||||
// {
|
||||
errorCallbacks.Add(callback);
|
||||
|
||||
if (exception != null)
|
||||
callback(exception);
|
||||
|
||||
return this;
|
||||
//}
|
||||
}
|
||||
|
||||
public IAsyncReply<T> Progress(Action<ProgressType, int, int> callback)
|
||||
{
|
||||
//lock (callbacksLock)
|
||||
//{
|
||||
progressCallbacks.Add(callback);
|
||||
return this;
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
public IAsyncReply<T> Chunk(Action<T> callback)
|
||||
{
|
||||
// lock (callbacksLock)
|
||||
// {
|
||||
chunkCallbacks.Add(callback);
|
||||
return this;
|
||||
// }
|
||||
}
|
||||
|
||||
public void Trigger(object result)
|
||||
{
|
||||
lock (asyncLock)
|
||||
{
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (Debug)
|
||||
Console.WriteLine($"AsyncReply: {Id} Trigger");
|
||||
|
||||
if (resultReady)
|
||||
return;
|
||||
|
||||
this.result = (T)result;
|
||||
|
||||
resultReady = true;
|
||||
|
||||
//if (mutex != null)
|
||||
mutex.Set();
|
||||
|
||||
foreach (var cb in callbacks)
|
||||
cb((T)result);
|
||||
|
||||
|
||||
if (Debug)
|
||||
Console.WriteLine($"AsyncReply: {Id} Trigger ended");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerError(Exception exception)
|
||||
{
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (resultReady)
|
||||
return;
|
||||
|
||||
if (exception is AsyncException)
|
||||
this.exception = exception as AsyncException;
|
||||
else
|
||||
this.exception = new AsyncException(ErrorType.Management, 0, exception.Message);
|
||||
|
||||
|
||||
// lock (callbacksLock)
|
||||
// {
|
||||
foreach (var cb in errorCallbacks)
|
||||
cb(this.exception);
|
||||
// }
|
||||
|
||||
mutex?.Set();
|
||||
|
||||
}
|
||||
|
||||
public void TriggerProgress(ProgressType type, int value, int max)
|
||||
{
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (resultReady)
|
||||
return;
|
||||
|
||||
//lock (callbacksLock)
|
||||
//{
|
||||
foreach (var cb in progressCallbacks)
|
||||
cb(type, value, max);
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
public void TriggerChunk(object value)
|
||||
{
|
||||
|
||||
//timeout?.Dispose();
|
||||
|
||||
if (resultReady)
|
||||
return;
|
||||
|
||||
//lock (callbacksLock)
|
||||
//{
|
||||
foreach (var cb in chunkCallbacks)
|
||||
cb((T)value);
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
public AsyncAwaiter<T> GetAwaiter()
|
||||
{
|
||||
return new AsyncAwaiter<T>(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AsyncReply()
|
||||
{
|
||||
// this.Debug = true;
|
||||
Id = MaxId++;
|
||||
}
|
||||
|
||||
public AsyncReply(T result)
|
||||
{
|
||||
// this.Debug = true;
|
||||
resultReady = true;
|
||||
this.result = result;
|
||||
|
||||
Id = MaxId++;
|
||||
}
|
||||
|
||||
/*
|
||||
public AsyncReply<T> Then(Action<T> callback)
|
||||
{
|
||||
base.Then(new Action<object>(o => callback((T)o)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Trigger(T result)
|
||||
{
|
||||
Trigger((object)result);
|
||||
}
|
||||
|
||||
public Task<bool> MoveNext(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncReply()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public new Task<T> Task
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Task.ContinueWith<T>((t) =>
|
||||
{
|
||||
|
||||
#if NETSTANDARD
|
||||
return (T)t.GetType().GetTypeInfo().GetProperty("Result").GetValue(t);
|
||||
#else
|
||||
return (T)t.GetType().GetProperty("Result").GetValue(t);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public T Current => throw new NotImplementedException();
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
12
Libraries/Esiur/Core/ErrorType.cs
Normal file
12
Libraries/Esiur/Core/ErrorType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public enum ErrorType
|
||||
{
|
||||
Management,
|
||||
Exception,
|
||||
Warning
|
||||
}
|
||||
50
Libraries/Esiur/Core/ExceptionCode.cs
Normal file
50
Libraries/Esiur/Core/ExceptionCode.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public enum ExceptionCode : ushort
|
||||
{
|
||||
RuntimeException,
|
||||
HostNotReachable,
|
||||
AccessDenied,
|
||||
UserOrTokenNotFound,
|
||||
ChallengeFailed,
|
||||
ResourceNotFound,
|
||||
AttachDenied,
|
||||
InvalidMethod,
|
||||
InvokeDenied,
|
||||
CreateDenied,
|
||||
AddParentDenied,
|
||||
AddChildDenied,
|
||||
ViewAttributeDenied,
|
||||
UpdateAttributeDenied,
|
||||
StoreNotFound,
|
||||
ParentNotFound,
|
||||
ChildNotFound,
|
||||
ResourceIsNotStore,
|
||||
DeleteDenied,
|
||||
DeleteFailed,
|
||||
UpdateAttributeFailed,
|
||||
GetAttributesFailed,
|
||||
ClearAttributesFailed,
|
||||
TypeDefNotFound,
|
||||
RenameDenied,
|
||||
ClassNotFound,
|
||||
MethodNotFound,
|
||||
PropertyNotFound,
|
||||
SetPropertyDenied,
|
||||
ReadOnlyProperty,
|
||||
GeneralFailure,
|
||||
AddToStoreFailed,
|
||||
NotAttached,
|
||||
AlreadyListened,
|
||||
AlreadyUnsubscribed,
|
||||
NotSubscribable,
|
||||
ParseError,
|
||||
Timeout,
|
||||
NotSupported,
|
||||
NotImplemented,
|
||||
NotAllowed
|
||||
}
|
||||
13
Libraries/Esiur/Core/ExceptionLevel.cs
Normal file
13
Libraries/Esiur/Core/ExceptionLevel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public enum ExceptionLevel
|
||||
{
|
||||
Code = 0x1,
|
||||
Message = 0x2,
|
||||
Source = 0x4,
|
||||
Trace = 0x8
|
||||
}
|
||||
40
Libraries/Esiur/Core/IDestructible.cs
Normal file
40
Libraries/Esiur/Core/IDestructible.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public delegate void DestroyedEvent(object sender);
|
||||
|
||||
public interface IDestructible
|
||||
{
|
||||
event DestroyedEvent? OnDestroy;
|
||||
void Destroy();
|
||||
}
|
||||
49
Libraries/Esiur/Core/InvocationContext.cs
Normal file
49
Libraries/Esiur/Core/InvocationContext.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Esiur.Protocol;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Core
|
||||
{
|
||||
public class InvocationContext
|
||||
{
|
||||
private uint CallbackId;
|
||||
|
||||
internal bool Ended;
|
||||
|
||||
public void Chunk(object value)
|
||||
{
|
||||
if (Ended)
|
||||
throw new Exception("Execution has ended.");
|
||||
|
||||
Connection.SendChunk(CallbackId, value);
|
||||
}
|
||||
|
||||
public void Progress(uint value, uint max) {
|
||||
|
||||
if (Ended)
|
||||
throw new Exception("Execution has ended.");
|
||||
|
||||
Connection.SendProgress(CallbackId, value, max);
|
||||
}
|
||||
|
||||
public void Warning(byte level, string message)
|
||||
{
|
||||
|
||||
if (Ended)
|
||||
throw new Exception("Execution has ended.");
|
||||
|
||||
Connection.SendWarning(CallbackId, level, message);
|
||||
}
|
||||
|
||||
public EpConnection Connection { get; internal set; }
|
||||
|
||||
|
||||
internal InvocationContext(EpConnection connection, uint callbackId)
|
||||
{
|
||||
Connection = connection;
|
||||
CallbackId = callbackId;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Libraries/Esiur/Core/LogType.cs
Normal file
39
Libraries/Esiur/Core/LogType.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public enum LogType
|
||||
{
|
||||
Debug,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
11
Libraries/Esiur/Core/ProgressType.cs
Normal file
11
Libraries/Esiur/Core/ProgressType.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Core;
|
||||
|
||||
public enum ProgressType
|
||||
{
|
||||
Execution,
|
||||
Network,
|
||||
}
|
||||
313
Libraries/Esiur/Data/AutoList.cs
Normal file
313
Libraries/Esiur/Data/AutoList.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using Esiur.Core;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
public class AutoList<T, ST> : IEnumerable<T>, ICollection, ICollection<T>
|
||||
{
|
||||
|
||||
private readonly object syncRoot = new object();
|
||||
private List<T> list = new List<T>();
|
||||
|
||||
public delegate void Modified(ST sender, int index, T oldValue, T newValue);
|
||||
public delegate void Added(ST sender, T value);
|
||||
public delegate void Removed(ST sender, T value);
|
||||
public delegate void Cleared(ST sender);
|
||||
|
||||
|
||||
public event Modified OnModified;
|
||||
public event Removed OnRemoved;
|
||||
public event Cleared OnCleared;
|
||||
public event Added OnAdd;
|
||||
|
||||
bool removableList;
|
||||
|
||||
|
||||
public ST State { get; set; }
|
||||
/*
|
||||
IOrderedEnumerable<T> OrderBy<T, TK>(Func<T, TK> keySelector)
|
||||
{
|
||||
return list.OrderBy<T,TK>(keySelector);
|
||||
}
|
||||
*/
|
||||
|
||||
public void Sort()
|
||||
{
|
||||
lock (syncRoot)
|
||||
list.Sort();
|
||||
}
|
||||
|
||||
public void Sort(IComparer<T> comparer)
|
||||
{
|
||||
lock (syncRoot)
|
||||
list.Sort(comparer);
|
||||
}
|
||||
|
||||
public void Sort(Comparison<T> comparison)
|
||||
{
|
||||
lock (syncRoot)
|
||||
list.Sort(comparison);
|
||||
}
|
||||
|
||||
public IEnumerable<T> Where(Func<T, bool> predicate)
|
||||
{
|
||||
return list.Where(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert AutoList to array
|
||||
/// </summary>
|
||||
/// <returns>Array</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
// list.OrderBy()
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of AutoList
|
||||
/// </summary>
|
||||
/// <param name="state">State object to be included when an event is raised.</param>
|
||||
public AutoList(ST state = default(ST))
|
||||
{
|
||||
State = state;
|
||||
#if NETSTANDARD
|
||||
removableList = (typeof(IDestructible).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()));
|
||||
#else
|
||||
removableList = (typeof(IDestructible).IsAssignableFrom(typeof(T)));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of AutoList
|
||||
/// </summary>
|
||||
/// <param name="values">Populate the list with items</param>
|
||||
/// <returns></returns>
|
||||
public AutoList(ST state, T[] values)
|
||||
{
|
||||
State = state;
|
||||
#if NETSTANDARD
|
||||
removableList = (typeof(IDestructible).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()));
|
||||
#else
|
||||
removableList = (typeof(IDestructible).IsAssignableFrom(typeof(T)));
|
||||
#endif
|
||||
|
||||
AddRange(values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronization lock of the list
|
||||
/// </summary>
|
||||
public object SyncRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
return syncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First item in the list
|
||||
/// </summary>
|
||||
public T First()
|
||||
{
|
||||
return list.First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an item at a specified index
|
||||
/// </summary>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return list[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
var oldValue = list[index];
|
||||
|
||||
if (removableList)
|
||||
{
|
||||
if (oldValue != null)
|
||||
((IDestructible)oldValue).OnDestroy -= ItemDestroyed;
|
||||
if (value != null)
|
||||
((IDestructible)value).OnDestroy += ItemDestroyed;
|
||||
}
|
||||
|
||||
lock (syncRoot)
|
||||
list[index] = value;
|
||||
|
||||
OnModified?.Invoke(State, index, oldValue, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add item to the list
|
||||
/// </summary>
|
||||
public void Add(T value)
|
||||
{
|
||||
if (removableList)
|
||||
if (value != null)
|
||||
((IDestructible)value).OnDestroy += ItemDestroyed;
|
||||
|
||||
lock (syncRoot)
|
||||
list.Add(value);
|
||||
|
||||
OnAdd?.Invoke(State, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an array of items to the list
|
||||
/// </summary>
|
||||
public void AddRange(T[] values)
|
||||
{
|
||||
foreach (var v in values)
|
||||
Add(v);
|
||||
}
|
||||
|
||||
private void ItemDestroyed(object sender)
|
||||
{
|
||||
Remove((T)sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the list
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
if (removableList)
|
||||
foreach (IDestructible v in list)
|
||||
if (v != null)
|
||||
v.OnDestroy -= ItemDestroyed;
|
||||
|
||||
lock (syncRoot)
|
||||
list.Clear();
|
||||
|
||||
OnCleared?.Invoke(State);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove an item from the list
|
||||
/// <param name="value">Item to remove</param>
|
||||
/// </summary>
|
||||
public bool Remove(T value)
|
||||
{
|
||||
if (!list.Contains(value))
|
||||
return false;
|
||||
|
||||
if (removableList)
|
||||
if (value != null)
|
||||
((IDestructible)value).OnDestroy -= ItemDestroyed;
|
||||
|
||||
lock (syncRoot)
|
||||
list.Remove(value);
|
||||
|
||||
OnRemoved?.Invoke(State, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of items in the list
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get { return list.Count; }
|
||||
}
|
||||
|
||||
public bool IsSynchronized => (list as ICollection).IsSynchronized;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if an item exists in the list
|
||||
/// </summary>
|
||||
/// <param name="value">Item to check if exists</param>
|
||||
public bool Contains(T value)
|
||||
{
|
||||
return list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any item of the given array is in the list
|
||||
/// </summary>
|
||||
/// <param name="values">Array of items</param>
|
||||
public bool ContainsAny(T[] values)
|
||||
{
|
||||
foreach (var v in values)
|
||||
if (list.Contains(v))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any item of the given list is in the list
|
||||
/// </summary>
|
||||
/// <param name="values">List of items</param>
|
||||
public bool ContainsAny(AutoList<T, ST> values)
|
||||
{
|
||||
foreach (var v in values)
|
||||
if (list.Contains((T)v))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)list).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)list).GetEnumerator();
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
lock (syncRoot)
|
||||
(list as ICollection).CopyTo(array, index);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
lock (syncRoot)
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
//bool ICollection<T>.Remove(T item)
|
||||
//{
|
||||
// lock(syncRoot)
|
||||
// return Remove(item);
|
||||
//}
|
||||
}
|
||||
290
Libraries/Esiur/Data/BinaryList.cs
Normal file
290
Libraries/Esiur/Data/BinaryList.cs
Normal file
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Esiur.Misc;
|
||||
using System.Reflection;
|
||||
using Esiur.Core;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
/// <summary>
|
||||
/// BinaryList holds a list of items to be converted to binary for storage and transmission
|
||||
/// </summary>
|
||||
public class BinaryList
|
||||
{
|
||||
private List<byte> list = new List<byte>();
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of BinaryList
|
||||
/// </summary>
|
||||
public BinaryList()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Endian Endian { get; set; } = Endian.Little;
|
||||
|
||||
public int Length => list.Count;
|
||||
|
||||
public BinaryList AddDateTime(DateTime value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertDateTime(int position, DateTime value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BinaryList AddUUID(Uuid value)
|
||||
{
|
||||
list.AddRange(value.Data);
|
||||
return this;
|
||||
|
||||
}
|
||||
//public BinaryList AddGuid(Guid value)
|
||||
//{
|
||||
// list.AddRange(DC.ToBytes(value));
|
||||
// return this;
|
||||
//}
|
||||
|
||||
public BinaryList InsertGuid(int position, Guid value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public BinaryList AddUInt8Array(byte[] value)
|
||||
{
|
||||
list.AddRange(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BinaryList InsertUInt8Array(int position, byte[] value)
|
||||
{
|
||||
list.InsertRange(position, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BinaryList AddHex(string value)
|
||||
{
|
||||
return this.AddUInt8Array(DC.FromHex(value, null));
|
||||
}
|
||||
|
||||
public BinaryList InsertHex(int position, string value)
|
||||
{
|
||||
return this.InsertUInt8Array(position, DC.FromHex(value, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public BinaryList AddString(string value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertString(int position, string value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BinaryList InsertUInt8(int position, byte value)
|
||||
{
|
||||
list.Insert(position, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList AddUInt8(byte value)
|
||||
{
|
||||
list.Add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList AddInt8(sbyte value)
|
||||
{
|
||||
list.Add((byte)value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertInt8(int position, sbyte value)
|
||||
{
|
||||
list.Insert(position, (byte)value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public BinaryList AddChar(char value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertChar(int position, char value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList AddBoolean(bool value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertBoolean(int position, bool value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList AddUInt16(ushort value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
public BinaryList InsertUInt16(int position, ushort value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BinaryList AddInt16(short value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertInt16(int position, short value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public BinaryList AddUInt32(uint value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
public BinaryList InsertUInt32(int position, uint value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BinaryList AddInt32(int value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
public BinaryList InsertInt32(int position, int value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList AddUInt64(ulong value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
public BinaryList InsertUInt64(int position, ulong value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public BinaryList AddInt64(long value)
|
||||
{
|
||||
list.AddRange(DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertInt64(int position, long value)
|
||||
{
|
||||
list.InsertRange(position, DC.ToBytes(value, Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BinaryList AddFloat32(float value)
|
||||
{
|
||||
list.AddRange(value.ToBytes(Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertFloat32(int position, float value)
|
||||
{
|
||||
list.InsertRange(position, value.ToBytes(Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BinaryList AddFloat64(double value)
|
||||
{
|
||||
list.AddRange(value.ToBytes(Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinaryList InsertFloat64(int position, double value)
|
||||
{
|
||||
list.InsertRange(position, value.ToBytes(Endian));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the list to an array of bytes
|
||||
/// </summary>
|
||||
/// <returns>Bytes array</returns>
|
||||
public byte[] ToArray()
|
||||
{
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public virtual AsyncReply<object[]> Done()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
586
Libraries/Esiur/Data/Codec.cs
Normal file
586
Libraries/Esiur/Data/Codec.cs
Normal file
@@ -0,0 +1,586 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Core;
|
||||
using Esiur.Resource;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections;
|
||||
using Esiur.Protocol;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public static class Codec
|
||||
{
|
||||
|
||||
//delegate AsyncReply AsyncParser(byte[] data, uint offset, uint length, EpConnection connection, uint[] requestSequence);
|
||||
|
||||
delegate object AsyncParser(ParsedTdu tdu, EpConnection connection, uint[] requestSequence);
|
||||
delegate object SyncParser(ParsedTdu tdu, Warehouse warehouse);
|
||||
|
||||
static AsyncParser[][] FixedAsyncParsers = new AsyncParser[][]
|
||||
{
|
||||
new AsyncParser[]{
|
||||
DataDeserializer.NullParserAsync,
|
||||
DataDeserializer.BooleanFalseParserAsync,
|
||||
DataDeserializer.BooleanTrueParserAsync,
|
||||
DataDeserializer.NotModifiedParserAsync,
|
||||
},
|
||||
new AsyncParser[]{
|
||||
DataDeserializer.UInt8ParserAsync,
|
||||
DataDeserializer.Int8ParserAsync,
|
||||
DataDeserializer.Char8ParserAsync,
|
||||
DataDeserializer.LocalResourceParser8Async,
|
||||
DataDeserializer.ResourceParser8Async,
|
||||
},
|
||||
new AsyncParser[]{
|
||||
DataDeserializer.UInt16ParserAsync,
|
||||
DataDeserializer.Int16ParserAsync,
|
||||
DataDeserializer.Char16ParserAsync,
|
||||
DataDeserializer.LocalResourceParser16Async,
|
||||
DataDeserializer.ResourceParser16Async,
|
||||
},
|
||||
new AsyncParser[]{
|
||||
DataDeserializer.UInt32ParserAsync,
|
||||
DataDeserializer.Int32ParserAsync,
|
||||
DataDeserializer.Float32ParserAsync,
|
||||
DataDeserializer.LocalResourceParser32Async,
|
||||
DataDeserializer.ResourceParser32Async,
|
||||
},
|
||||
new AsyncParser[]{
|
||||
DataDeserializer.UInt64ParserAsync,
|
||||
DataDeserializer.Int64ParserAsync,
|
||||
DataDeserializer.Float64ParserAsync,
|
||||
DataDeserializer.DateTimeParserAsync,
|
||||
},
|
||||
new AsyncParser[]
|
||||
{
|
||||
DataDeserializer.UInt128ParserAsync, // uint 128
|
||||
DataDeserializer.Int128ParserAsync, // int 128
|
||||
DataDeserializer.Decimal128ParserAsync,
|
||||
DataDeserializer.UUIDParserAsync
|
||||
}
|
||||
};
|
||||
|
||||
static AsyncParser[] DynamicAsyncParsers = new AsyncParser[]
|
||||
{
|
||||
DataDeserializer.RawDataParserAsync,
|
||||
DataDeserializer.StringParserAsync,
|
||||
DataDeserializer.ListParserAsync,
|
||||
DataDeserializer.ResourceListParserAsync,
|
||||
DataDeserializer.RecordListParserAsync,
|
||||
DataDeserializer.ResourceLinkParserAsync,
|
||||
};
|
||||
|
||||
static AsyncParser[] TypedAsyncParsers = new AsyncParser[]
|
||||
{
|
||||
DataDeserializer.RecordParserAsync,
|
||||
DataDeserializer.TypedListParserAsync,
|
||||
DataDeserializer.TypedMapParserAsync,
|
||||
DataDeserializer.TupleParserAsync,
|
||||
DataDeserializer.EnumParserAsync,
|
||||
DataDeserializer.ConstantParserAsync,
|
||||
};
|
||||
|
||||
static AsyncParser[] ExtendedAsyncParsers = new AsyncParser[]
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
static SyncParser[][] FixedParsers = new SyncParser[][]
|
||||
{
|
||||
new SyncParser[]{
|
||||
DataDeserializer.NullParser,
|
||||
DataDeserializer.BooleanFalseParser,
|
||||
DataDeserializer.BooleanTrueParser,
|
||||
DataDeserializer.NotModifiedParser,
|
||||
},
|
||||
new SyncParser[]{
|
||||
DataDeserializer.UInt8Parser,
|
||||
DataDeserializer.Int8Parser,
|
||||
DataDeserializer.Char8Parser,
|
||||
DataDeserializer.LocalResourceParser8,
|
||||
DataDeserializer.ResourceParser8,
|
||||
},
|
||||
new SyncParser[]{
|
||||
DataDeserializer.UInt16Parser,
|
||||
DataDeserializer.Int16Parser,
|
||||
DataDeserializer.Char16Parser,
|
||||
DataDeserializer.LocalResourceParser16,
|
||||
DataDeserializer.ResourceParser16,
|
||||
},
|
||||
new SyncParser[]{
|
||||
DataDeserializer.UInt32Parser,
|
||||
DataDeserializer.Int32Parser,
|
||||
DataDeserializer.Float32Parser,
|
||||
DataDeserializer.LocalResourceParser32,
|
||||
DataDeserializer.ResourceParser32,
|
||||
},
|
||||
new SyncParser[]{
|
||||
DataDeserializer.UInt64Parser,
|
||||
DataDeserializer.Int64Parser,
|
||||
DataDeserializer.Float64Parser,
|
||||
DataDeserializer.DateTimeParser,
|
||||
},
|
||||
new SyncParser[]
|
||||
{
|
||||
DataDeserializer.UInt128Parser, // uint 128
|
||||
DataDeserializer.Int128Parser, // int 128
|
||||
DataDeserializer.Decimal128Parser,
|
||||
DataDeserializer.UUIDParser
|
||||
}
|
||||
};
|
||||
|
||||
static SyncParser[] DynamicParsers = new SyncParser[]
|
||||
{
|
||||
DataDeserializer.RawDataParser,
|
||||
DataDeserializer.StringParser,
|
||||
DataDeserializer.ListParser,
|
||||
DataDeserializer.ResourceListParser,
|
||||
DataDeserializer.RecordListParser,
|
||||
DataDeserializer.ResourceLinkParser,
|
||||
// @TODO: Map and MapList parsers to be added
|
||||
};
|
||||
|
||||
static SyncParser[] TypedParsers = new SyncParser[]
|
||||
{
|
||||
DataDeserializer.RecordParser,
|
||||
DataDeserializer.TypedListParser,
|
||||
DataDeserializer.TypedMapParser,
|
||||
DataDeserializer.TupleParser,
|
||||
DataDeserializer.EnumParser,
|
||||
DataDeserializer.ConstantParser,
|
||||
};
|
||||
|
||||
static SyncParser[] ExtendedParsers = new SyncParser[]
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Parse a value
|
||||
/// </summary>
|
||||
/// <param name="data">Bytes array</param>
|
||||
/// <param name="offset">Zero-indexed offset.</param>
|
||||
/// <param name="size">Output the number of bytes parsed</param>
|
||||
/// <param name="connection">EpConnection is required in case a structure in the array holds items at the other end.</param>
|
||||
/// <param name="dataType">DataType, in case the data is not prepended with DataType</param>
|
||||
/// <returns>Value</returns>
|
||||
public static (uint, object) ParseAsync(byte[] data, uint offset, EpConnection connection, uint[] requestSequence)
|
||||
{
|
||||
|
||||
var tdu = ParsedTdu.Parse(data, offset, (uint)data.Length);
|
||||
|
||||
if (tdu.Class == TduClass.Invalid)
|
||||
throw new NullReferenceException("DataType can't be parsed.");
|
||||
|
||||
if (tdu.Class == TduClass.Fixed)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, FixedAsyncParsers[tdu.Exponent][tdu.Index](tdu, connection, requestSequence));
|
||||
}
|
||||
else if (tdu.Class == TduClass.Dynamic)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, DynamicAsyncParsers[tdu.Index](tdu, connection, requestSequence));
|
||||
}
|
||||
else if (tdu.Class == TduClass.Typed)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, TypedAsyncParsers[tdu.Index](tdu, connection, requestSequence));
|
||||
}
|
||||
else // if (tt.Class == TDUClass.Extension)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, ExtendedAsyncParsers[tdu.Index](tdu, connection, requestSequence));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static (uint, object) ParseAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
|
||||
{
|
||||
if (tdu.Class == TduClass.Invalid)
|
||||
throw new NullReferenceException("DataType can't be parsed.");
|
||||
|
||||
if (tdu.Class == TduClass.Fixed)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, FixedAsyncParsers[tdu.Exponent][tdu.Index](tdu, connection, requestSequence));
|
||||
}
|
||||
else if (tdu.Class == TduClass.Dynamic)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, DynamicAsyncParsers[tdu.Index](tdu, connection, requestSequence));
|
||||
}
|
||||
else if (tdu.Class == TduClass.Typed)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, TypedAsyncParsers[tdu.Index](tdu, connection, requestSequence));
|
||||
}
|
||||
else // if (tt.Class == TDUClass.Extension)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, ExtendedAsyncParsers[tdu.Index](tdu, connection, requestSequence));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static (uint, object) ParseSync(ParsedTdu tdu, Warehouse warehouse)
|
||||
{
|
||||
if (tdu.Class == TduClass.Fixed)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, FixedParsers[tdu.Exponent][tdu.Index](tdu, warehouse));
|
||||
}
|
||||
else if (tdu.Class == TduClass.Dynamic)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, DynamicParsers[tdu.Index](tdu, warehouse));
|
||||
}
|
||||
else if (tdu.Class == TduClass.Typed)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, TypedParsers[tdu.Index](tdu, warehouse));
|
||||
}
|
||||
else // Extension
|
||||
{
|
||||
return ((uint)tdu.TotalLength, ExtendedParsers[tdu.Index](tdu, warehouse));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static (uint, object) ParseSync(byte[] data, uint offset, Warehouse warehouse)
|
||||
{
|
||||
var tdu = ParsedTdu.Parse(data, offset, (uint)data.Length);
|
||||
|
||||
if (tdu.Class == TduClass.Invalid)
|
||||
throw new NullReferenceException("DataType can't be parsed.");
|
||||
|
||||
|
||||
if (tdu.Class == TduClass.Fixed)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, FixedParsers[tdu.Exponent][tdu.Index](tdu, warehouse));
|
||||
}
|
||||
else if (tdu.Class == TduClass.Dynamic)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, DynamicParsers[tdu.Index](tdu, warehouse));
|
||||
}
|
||||
else if (tdu.Class == TduClass.Typed)
|
||||
{
|
||||
return ((uint)tdu.TotalLength, TypedParsers[tdu.Index](tdu, warehouse));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((uint)tdu.TotalLength, ExtendedParsers[tdu.Index](tdu, warehouse));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Check if a resource is local to a given connection.
|
||||
/// </summary>
|
||||
/// <param name="resource">Resource to check.</param>
|
||||
/// <param name="connection">EpConnection to check if the resource is local to it.</param>
|
||||
/// <returns>True, if the resource owner is the given connection, otherwise False.</returns>
|
||||
public static bool IsLocalResource(IResource resource, EpConnection connection)
|
||||
{
|
||||
if (resource == null)
|
||||
throw new NullReferenceException("Resource is null.");
|
||||
|
||||
if (resource is EpResource)
|
||||
if (((EpResource)(resource)).DistributedResourceConnection == connection)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public delegate Tdu Composer(object value, Warehouse warehouse, EpConnection connection);
|
||||
|
||||
public static Dictionary<Type, Composer> Composers = new Dictionary<Type, Composer>()
|
||||
{
|
||||
// Fixed
|
||||
[typeof(bool)] = DataSerializer.BoolComposer,
|
||||
[typeof(bool?)] = DataSerializer.BoolComposer,
|
||||
[typeof(NotModified)] = DataSerializer.NotModifiedComposer,
|
||||
[typeof(byte)] = DataSerializer.UInt8Composer,
|
||||
[typeof(byte?)] = DataSerializer.UInt8Composer,
|
||||
[typeof(sbyte)] = DataSerializer.Int8Composer,
|
||||
[typeof(sbyte?)] = DataSerializer.Int8Composer,
|
||||
[typeof(char)] = DataSerializer.Char16Composer,
|
||||
[typeof(char?)] = DataSerializer.Char16Composer,
|
||||
[typeof(short)] = DataSerializer.Int16Composer,
|
||||
[typeof(short?)] = DataSerializer.Int16Composer,
|
||||
[typeof(ushort)] = DataSerializer.UInt16Composer,
|
||||
[typeof(ushort?)] = DataSerializer.UInt16Composer,
|
||||
[typeof(int)] = DataSerializer.Int32Composer,
|
||||
[typeof(int?)] = DataSerializer.Int32Composer,
|
||||
[typeof(uint)] = DataSerializer.UInt32Composer,
|
||||
[typeof(uint?)] = DataSerializer.UInt32Composer,
|
||||
[typeof(float)] = DataSerializer.Float32Composer,
|
||||
[typeof(float?)] = DataSerializer.Float32Composer,
|
||||
[typeof(long)] = DataSerializer.Int64Composer,
|
||||
[typeof(long?)] = DataSerializer.Int64Composer,
|
||||
[typeof(ulong)] = DataSerializer.UInt64Composer,
|
||||
[typeof(ulong?)] = DataSerializer.UInt64Composer,
|
||||
[typeof(double)] = DataSerializer.Float64Composer,
|
||||
[typeof(double?)] = DataSerializer.Float64Composer,
|
||||
[typeof(DateTime)] = DataSerializer.DateTimeComposer,
|
||||
[typeof(DateTime?)] = DataSerializer.DateTimeComposer,
|
||||
[typeof(decimal)] = DataSerializer.Decimal128Composer,
|
||||
[typeof(decimal?)] = DataSerializer.Decimal128Composer,
|
||||
[typeof(byte[])] = DataSerializer.RawDataComposerFromArray,
|
||||
//[typeof(byte?[])] = DataSerializer.RawDataComposerFromArray,
|
||||
[typeof(List<byte>)] = DataSerializer.RawDataComposerFromList,
|
||||
//[typeof(List<byte?>)] = DataSerializer.RawDataComposerFromList,
|
||||
[typeof(string)] = DataSerializer.StringComposer,
|
||||
[typeof(ResourceLink)] = DataSerializer.ResourceLinkComposer,
|
||||
[typeof(Uuid)] = DataSerializer.UUIDComposer,
|
||||
// Special
|
||||
[typeof(object[])] = DataSerializer.ListComposer,
|
||||
[typeof(List<object>)] = DataSerializer.ListComposer,
|
||||
[typeof(VarList<object>)] = DataSerializer.ListComposer,
|
||||
[typeof(IResource[])] = DataSerializer.ResourceListComposer,
|
||||
[typeof(IResource?[])] = DataSerializer.ResourceListComposer,
|
||||
[typeof(List<IResource>)] = DataSerializer.ResourceListComposer,
|
||||
[typeof(List<IResource?>)] = DataSerializer.ResourceListComposer,
|
||||
[typeof(VarList<IResource>)] = DataSerializer.ResourceListComposer,
|
||||
[typeof(VarList<IResource?>)] = DataSerializer.ResourceListComposer,
|
||||
[typeof(IRecord[])] = DataSerializer.RecordListComposer,
|
||||
[typeof(IRecord?[])] = DataSerializer.RecordListComposer,
|
||||
[typeof(List<IRecord>)] = DataSerializer.RecordListComposer,
|
||||
[typeof(List<IRecord?>)] = DataSerializer.RecordListComposer,
|
||||
[typeof(VarList<IRecord>)] = DataSerializer.RecordListComposer,
|
||||
[typeof(VarList<IRecord?>)] = DataSerializer.RecordListComposer,
|
||||
[typeof(Map<object, object>)] = DataSerializer.MapComposer,
|
||||
[typeof(Map<object?, object>)] = DataSerializer.MapComposer,
|
||||
[typeof(Map<object, object?>)] = DataSerializer.MapComposer,
|
||||
[typeof(Map<object?, object?>)] = DataSerializer.MapComposer,
|
||||
[typeof(PropertyValue[])] = DataSerializer.PropertyValueArrayComposer
|
||||
// Typed
|
||||
// [typeof(bool[])] = (value, con) => DataSerializer.TypedListComposer((IEnumerable)value, typeof(bool), con),
|
||||
// [typeof(bool?[])] = (value, con) => (TransmissionDataUnitIdentifier.TypedList, new byte[] { (byte)value }),
|
||||
// [typeof(List<bool>)] = (value, con) => (TransmissionDataUnitIdentifier.TypedList, new byte[] { (byte)value }),
|
||||
// [typeof(List<bool?>)] = (value, con) => (TransmissionDataUnitIdentifier.TypedList, new byte[] { (byte)value }),
|
||||
|
||||
// [typeof(byte?[])] = (value, con) => (TransmissionDataUnitIdentifier.TypedList, new byte[] { (byte)value }),
|
||||
// [typeof(List<bool?>)] = (value, con) => (TransmissionDataUnitIdentifier.TypedList, new byte[] { (byte)value }),
|
||||
|
||||
};
|
||||
|
||||
|
||||
internal static Tdu
|
||||
ComposeInternal(object valueOrSource, Warehouse warehouse, EpConnection connection)
|
||||
{
|
||||
if (valueOrSource == null)
|
||||
return new Tdu(TduIdentifier.Null, null, 0);
|
||||
|
||||
var type = valueOrSource.GetType();
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
|
||||
var genericType = type.GetGenericTypeDefinition();
|
||||
if (genericType == typeof(PropertyContext<>))
|
||||
{
|
||||
valueOrSource = ((IPropertyContext)valueOrSource).GetValue(connection);
|
||||
}
|
||||
else if (genericType == typeof(Func<>))
|
||||
{
|
||||
var args = genericType.GetGenericArguments();
|
||||
if (args.Length == 2 && args[0] == typeof(EpConnection))
|
||||
{
|
||||
//Func<EpConnection, EpConnection> a;
|
||||
//a.Invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valueOrSource is IUserType)
|
||||
valueOrSource = ((IUserType)valueOrSource).Get();
|
||||
|
||||
if (valueOrSource == null)
|
||||
return new Tdu(TduIdentifier.Null, null, 0);
|
||||
|
||||
|
||||
type = valueOrSource.GetType();
|
||||
|
||||
|
||||
if (Composers.ContainsKey(type))
|
||||
{
|
||||
return Composers[type](valueOrSource, warehouse, connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Codec.ImplementsInterface(type, typeof(IResource)))
|
||||
{
|
||||
return DataSerializer.ResourceComposer(valueOrSource, warehouse, connection);
|
||||
}
|
||||
else if (Codec.ImplementsInterface(type, typeof(IRecord)))
|
||||
{
|
||||
return DataSerializer.RecordComposer(valueOrSource, warehouse, connection);
|
||||
}
|
||||
else if (type.IsGenericType)
|
||||
{
|
||||
var genericType = type.GetGenericTypeDefinition();
|
||||
if (genericType == typeof(List<>)
|
||||
|| genericType == typeof(VarList<>)
|
||||
|| genericType == typeof(IList<>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
//if (Composers.ContainsKey(args[0]))
|
||||
//{
|
||||
return DataSerializer.TypedListComposer((IEnumerable)valueOrSource, args[0], warehouse, connection);
|
||||
//}
|
||||
}
|
||||
else if (genericType == typeof(Map<,>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
|
||||
return DataSerializer.TypedMapComposer(valueOrSource, args[0], args[1], warehouse, connection);
|
||||
|
||||
}
|
||||
else if (genericType == typeof(Dictionary<,>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
|
||||
return DataSerializer.TypedDictionaryComposer(valueOrSource, args[0], args[1], warehouse, connection);
|
||||
|
||||
}
|
||||
|
||||
else if (genericType == typeof(ValueTuple<,>)
|
||||
|| genericType == typeof(ValueTuple<,,>)
|
||||
|| genericType == typeof(ValueTuple<,,,>)
|
||||
|| genericType == typeof(ValueTuple<,,,,>)
|
||||
|| genericType == typeof(ValueTuple<,,,,,>)
|
||||
|| genericType == typeof(ValueTuple<,,,,,,>)
|
||||
)
|
||||
{
|
||||
return DataSerializer.TupleComposer(valueOrSource, warehouse, connection);
|
||||
}
|
||||
}
|
||||
else if (type.IsArray)
|
||||
{
|
||||
var elementType = type.GetElementType();
|
||||
|
||||
//if (Composers.ContainsKey(elementType))
|
||||
//{
|
||||
return DataSerializer.TypedListComposer((IEnumerable)valueOrSource, elementType, warehouse, connection);
|
||||
|
||||
//}
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
return DataSerializer.EnumComposer(valueOrSource, warehouse, connection);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Tdu(TduIdentifier.Null, null, 0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Compose a variable
|
||||
/// </summary>
|
||||
/// <param name="value">Value to compose.</param>
|
||||
/// <param name="connection">EpConnection is required to check locality.</param>
|
||||
/// <param name="prependType">If True, prepend the DataType at the beginning of the output.</param>
|
||||
/// <returns>Array of bytes in the network byte order.</returns>
|
||||
public static byte[] Compose(object valueOrSource, Warehouse warehouse, EpConnection connection)//, bool prependType = true)
|
||||
{
|
||||
var tdu = ComposeInternal(valueOrSource, warehouse, connection);
|
||||
return tdu.Composed;
|
||||
}
|
||||
|
||||
public static bool IsAnonymous(Type type)
|
||||
{
|
||||
// Detect anonymous types
|
||||
var info = type.GetTypeInfo();
|
||||
var hasCompilerGeneratedAttribute = info.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Count() > 0;
|
||||
var nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
|
||||
return hasCompilerGeneratedAttribute && nameContainsAnonymousType;
|
||||
}
|
||||
|
||||
|
||||
public static Type? GetGenericType(Type type, Type ifaceType, int argument = 0)
|
||||
{
|
||||
if (ifaceType.IsAssignableFrom(type))
|
||||
{
|
||||
var col = type.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == ifaceType)
|
||||
.FirstOrDefault()?
|
||||
.GetGenericArguments()
|
||||
.FirstOrDefault() ?? null;
|
||||
|
||||
return col;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a type implements an interface
|
||||
/// </summary>
|
||||
/// <param name="type">Sub-class type.</param>
|
||||
/// <param name="iface">Super-interface type.</param>
|
||||
/// <returns>True, if <paramref name="type"/> implements <paramref name="iface"/>.</returns>
|
||||
public static bool ImplementsInterface(Type type, Type iface)
|
||||
{
|
||||
while (type != null)
|
||||
{
|
||||
if (type == iface)
|
||||
return true;
|
||||
|
||||
#if NETSTANDARD
|
||||
if (type.GetTypeInfo().GetInterfaces().Contains(iface))
|
||||
return true;
|
||||
|
||||
type = type.GetTypeInfo().BaseType;
|
||||
#else
|
||||
if (type.GetInterfaces().Contains(iface))
|
||||
return true;
|
||||
type = type.BaseType;
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool InheritsClass(Type type, Type parent)
|
||||
=> type.IsSubclassOf(parent);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a type inherits another type.
|
||||
/// </summary>
|
||||
/// <param name="childType">Child type.</param>
|
||||
/// <param name="parentType">Parent type.</param>
|
||||
/// <returns>True, if <paramref name="childType"/> inherits <paramref name="parentType"/>.</returns>
|
||||
private static bool HasParentType(Type childType, Type parentType)
|
||||
{
|
||||
while (childType != null)
|
||||
{
|
||||
if (childType == parentType)
|
||||
return true;
|
||||
#if NETSTANDARD
|
||||
childType = childType.GetTypeInfo().BaseType;
|
||||
#else
|
||||
childType = childType.BaseType;
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
866
Libraries/Esiur/Data/DataConverter.cs
Normal file
866
Libraries/Esiur/Data/DataConverter.cs
Normal file
@@ -0,0 +1,866 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Reflection;
|
||||
using Esiur.Data;
|
||||
using Esiur.Core;
|
||||
using Esiur.Resource;
|
||||
using System.Collections;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
public static class DC // Data Converter
|
||||
{
|
||||
|
||||
|
||||
public static object CastConvertOld(object value, Type destinationType)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
var sourceType = value.GetType();
|
||||
|
||||
if (destinationType == sourceType)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sourceType.IsArray && (destinationType.IsArray || destinationType == typeof(object)))
|
||||
{
|
||||
destinationType = destinationType.GetElementType();
|
||||
|
||||
var v = value as Array;
|
||||
|
||||
var rt = Array.CreateInstance(destinationType, v.Length);
|
||||
|
||||
for (var i = 0; i < rt.Length; i++)
|
||||
{
|
||||
rt.SetValue(CastConvertOld(v.GetValue(i), destinationType), i);
|
||||
}
|
||||
|
||||
return rt;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var underType = Nullable.GetUnderlyingType(destinationType);
|
||||
if (underType != null)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
else
|
||||
destinationType = underType;
|
||||
}
|
||||
|
||||
if (destinationType.IsInstanceOfType(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else if (typeof(IUserType).IsAssignableFrom(destinationType))
|
||||
{
|
||||
var rt = Activator.CreateInstance(destinationType) as IUserType;
|
||||
rt.Set(value);
|
||||
return rt;
|
||||
}
|
||||
//else if (sourceType == typeof(Structure) && sourceType.IsAssignableFrom(destinationType))
|
||||
//{
|
||||
// return Structure.FromStructure((Structure)value, destinationType);
|
||||
//}
|
||||
else if (destinationType.IsEnum)
|
||||
{
|
||||
return Enum.ToObject(destinationType, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Convert.ChangeType(value, destinationType);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw ex;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static byte[] ToBytes(sbyte value)
|
||||
{
|
||||
return new byte[1] { (byte)value };
|
||||
}
|
||||
|
||||
public static byte[] ToBytes(byte value)
|
||||
{
|
||||
return new byte[1] { value };
|
||||
}
|
||||
|
||||
public static byte[] ToBytes(IPAddress ip)
|
||||
{
|
||||
return ip.GetAddressBytes();
|
||||
}
|
||||
|
||||
public static byte[] ToBytes(PhysicalAddress mac)
|
||||
{
|
||||
return mac.GetAddressBytes();
|
||||
}
|
||||
|
||||
public static byte[] ToBytes(bool value)
|
||||
{
|
||||
return new byte[1] { value ? (byte)1 : (byte)0 };
|
||||
}
|
||||
|
||||
public static byte ToByte(bool value)
|
||||
{
|
||||
return value ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
public static byte ToByte(sbyte value)
|
||||
{
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
public static byte[] ToBytes(byte[] value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
public static byte[] ToBytes(bool[] value)
|
||||
{
|
||||
|
||||
byte[] ba = new byte[value.Length];
|
||||
|
||||
for (int i = 0; i < ba.Length; i++)
|
||||
ba[i] = DC.ToByte(value[i]);
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
public static byte[] ToBytes(sbyte[] value)
|
||||
{
|
||||
|
||||
byte[] ba = new byte[value.Length];
|
||||
|
||||
for (int i = 0; i < ba.Length; i++)
|
||||
ba[i] = DC.ToByte(value[i]);
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
public static byte[] ToBytes(char value)
|
||||
{
|
||||
byte[] ret = BitConverter.GetBytes(value);
|
||||
Array.Reverse(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static byte[] ToBytes(Guid value)
|
||||
{
|
||||
return value.ToByteArray();
|
||||
}
|
||||
|
||||
|
||||
public static void Append(ref byte[] dst, byte[] src)
|
||||
{
|
||||
Append(ref dst, src, (uint)0, (uint)src.Length);
|
||||
}
|
||||
|
||||
public static void Append(ref byte[] dst, byte[] src, uint srcOffset, uint length)
|
||||
{
|
||||
var dstOffset = dst.Length;
|
||||
Array.Resize<byte>(ref dst, dstOffset + (int)length);
|
||||
Buffer.BlockCopy(src, (int)srcOffset, dst, dstOffset, (int)length);
|
||||
}
|
||||
|
||||
public static byte[] Combine(byte[] src1, uint src1Offset, uint src1Length, byte[] src2, uint src2Offset, uint src2Length)
|
||||
{
|
||||
var rt = new byte[src1Length + src2Length];
|
||||
Buffer.BlockCopy(src1, (int)src1Offset, rt, 0, (int)src1Length);
|
||||
Buffer.BlockCopy(src2, (int)src2Offset, rt, (int)src1Length, (int)src2Length);
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static byte[] Merge(params byte[][] arrays)
|
||||
{
|
||||
var s = arrays.Sum(x => x.Length);
|
||||
var r = new byte[s];
|
||||
var offset = 0;
|
||||
foreach (var array in arrays)
|
||||
{
|
||||
Buffer.BlockCopy(array, 0, r, offset, array.Length);
|
||||
offset += array.Length;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static byte[] ToBytes(this string[] value)
|
||||
{
|
||||
List<byte> rt = new List<byte>();
|
||||
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
byte[] ba = ToBytes(value[i]);
|
||||
// add string length
|
||||
rt.AddRange(ToBytes(ba.Length, Endian.Little));
|
||||
// add encoded string
|
||||
rt.AddRange(ba);
|
||||
}
|
||||
|
||||
return rt.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public static unsafe byte[] ToBytes(this int value, Endian endian)
|
||||
{
|
||||
var rt = new byte[4];
|
||||
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = rt)
|
||||
*((int*)ptr) = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte* p = (byte*)&value;
|
||||
|
||||
rt[0] = *(p + 3);
|
||||
rt[1] = *(p + 2);
|
||||
rt[2] = *(p + 1);
|
||||
rt[3] = *(p + 0);
|
||||
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static unsafe byte[] ToBytes(this short value, Endian endian)
|
||||
{
|
||||
var rt = new byte[2];
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = rt)
|
||||
*((short*)ptr) = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte* p = (byte*)&value;
|
||||
|
||||
rt[0] = *(p + 1);
|
||||
rt[1] = *(p + 0);
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static unsafe byte[] ToBytes(this float value, Endian endian)
|
||||
|
||||
{
|
||||
var rt = new byte[4];
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = rt)
|
||||
*((float*)ptr) = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte* p = (byte*)&value;
|
||||
rt[0] = *(p + 3);
|
||||
rt[1] = *(p + 2);
|
||||
rt[2] = *(p + 1);
|
||||
rt[3] = *(p);
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static byte[] ToBytes(this string value)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(value);
|
||||
}
|
||||
|
||||
public unsafe static byte[] ToBytes(this double value, Endian endian)
|
||||
{
|
||||
var rt = new byte[8];
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = rt)
|
||||
*((double*)ptr) = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte* p = (byte*)&value;
|
||||
|
||||
rt[0] = *(p + 7);
|
||||
rt[1] = *(p + 6);
|
||||
rt[2] = *(p + 5);
|
||||
rt[3] = *(p + 4);
|
||||
rt[4] = *(p + 3);
|
||||
rt[5] = *(p + 2);
|
||||
rt[6] = *(p + 1);
|
||||
rt[7] = *(p + 0);
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static unsafe byte[] ToBytes(this long value, Endian endian)
|
||||
{
|
||||
var rt = new byte[8];
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = rt)
|
||||
*((long*)ptr) = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte* p = (byte*)&value;
|
||||
|
||||
rt[0] = *(p + 7);
|
||||
rt[1] = *(p + 6);
|
||||
rt[2] = *(p + 5);
|
||||
rt[3] = *(p + 4);
|
||||
rt[4] = *(p + 3);
|
||||
rt[5] = *(p + 2);
|
||||
rt[6] = *(p + 1);
|
||||
rt[7] = *(p + 0);
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
public static unsafe byte[] ToBytes(this DateTime value)
|
||||
{
|
||||
var rt = new byte[8];
|
||||
var v = value.ToUniversalTime().Ticks;
|
||||
|
||||
fixed (byte* ptr = rt)
|
||||
*((long*)ptr) = v;
|
||||
return rt;
|
||||
}
|
||||
|
||||
|
||||
public static unsafe byte[] ToBytes(this ulong value, Endian endia)
|
||||
{
|
||||
var rt = new byte[8];
|
||||
if (endia == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = rt)
|
||||
*((ulong*)ptr) = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
byte* p = (byte*)&value;
|
||||
|
||||
rt[0] = *(p + 7);
|
||||
rt[1] = *(p + 6);
|
||||
rt[2] = *(p + 5);
|
||||
rt[3] = *(p + 4);
|
||||
rt[4] = *(p + 3);
|
||||
rt[5] = *(p + 2);
|
||||
rt[6] = *(p + 1);
|
||||
rt[7] = *(p + 0);
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static unsafe byte[] ToBytes(this uint value, Endian endian)
|
||||
{
|
||||
var rt = new byte[4];
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = rt)
|
||||
*((uint*)ptr) = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
byte* p = (byte*)&value;
|
||||
|
||||
rt[0] = *(p + 3);
|
||||
rt[1] = *(p + 2);
|
||||
rt[2] = *(p + 1);
|
||||
rt[3] = *(p + 0);
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static unsafe byte[] ToBytes(this ushort value, Endian endian)
|
||||
{
|
||||
var rt = new byte[2];
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = rt)
|
||||
*((ushort*)ptr) = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte* p = (byte*)&value;
|
||||
|
||||
rt[0] = *(p + 1);
|
||||
rt[1] = *(p);
|
||||
}
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static unsafe byte[] ToBytes(this decimal value, Endian endian)
|
||||
{
|
||||
var rt = new byte[16];
|
||||
fixed (byte* ptr = rt)
|
||||
*((decimal*)ptr) = value;
|
||||
|
||||
if (endian == Endian.Big)
|
||||
Array.Reverse(rt);
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static string ToHex(this byte[] ba)
|
||||
{
|
||||
if (ba == null)
|
||||
return "";
|
||||
return ToHex(ba, 0, (uint)ba.Length);
|
||||
}
|
||||
|
||||
public static string ToHex(this byte[] ba, uint offset, uint length, string separator = " ")
|
||||
{
|
||||
if (separator == null)
|
||||
separator = "";
|
||||
|
||||
return string.Join(separator, ba.Skip((int)offset).Take((int)length).Select(x => x.ToString("x2")).ToArray());
|
||||
}
|
||||
|
||||
public static byte[] FromHex(string hexString, string separator = " ")
|
||||
{
|
||||
var rt = new List<byte>();
|
||||
|
||||
if (separator == null)
|
||||
{
|
||||
for (var i = 0; i < hexString.Length; i += 2)
|
||||
rt.Add(Convert.ToByte(hexString.Substring(i, 2), 16));
|
||||
}
|
||||
else
|
||||
{
|
||||
var hexes = hexString.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var h in hexes)
|
||||
rt.Add(Convert.ToByte(h, 16));
|
||||
}
|
||||
|
||||
return rt.ToArray();
|
||||
}
|
||||
|
||||
public static string FlagsEnumToString<T>(ulong value)
|
||||
{
|
||||
|
||||
string rt = typeof(T).Name + ":";
|
||||
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
ulong bit = (ulong)(Convert.ToUInt64(Math.Pow(2, i)) & value);
|
||||
if (bit != 0)
|
||||
{
|
||||
rt += " " + Enum.GetName(typeof(T), bit);
|
||||
}
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static bool TryParse<T>(object Input, out T Results)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if NETSTANDARD
|
||||
var tryParse = typeof(T).GetTypeInfo().GetDeclaredMethod("TryParse");
|
||||
if ((bool)tryParse.Invoke(null, new object[] { Input, null }))
|
||||
{
|
||||
var parse = typeof(T).GetTypeInfo().GetDeclaredMethod("Parse");
|
||||
|
||||
Results = (T)parse.Invoke(null, new object[] { Input });
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
if ((bool)typeof(T).InvokeMember("TryParse", BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod, null, null, new object[] { Input, null }))
|
||||
{
|
||||
Results = (T)typeof(T).InvokeMember("Parse", BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod, null, null, new object[] { Input });
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
else
|
||||
{
|
||||
Results = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch //Exception ex)
|
||||
{
|
||||
Results = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static DateTime FromUnixTime(uint seconds)
|
||||
{
|
||||
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds((double)seconds);
|
||||
}
|
||||
|
||||
public static DateTime FromUnixTime(ulong milliseconds)
|
||||
{
|
||||
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((double)milliseconds);
|
||||
}
|
||||
|
||||
|
||||
public static sbyte GetInt8(this byte[] data, uint offset)
|
||||
{
|
||||
return (sbyte)data[offset];
|
||||
}
|
||||
|
||||
|
||||
public static int Mod(this int value, int divisor)
|
||||
{
|
||||
var rt = value % divisor;
|
||||
if (rt < 0) return rt + divisor;
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static ulong RotateLeft(this ulong value, int shift)
|
||||
{
|
||||
return value << shift | value >> (64 - shift);
|
||||
}
|
||||
|
||||
public static ulong RotateRight(this ulong value, int shift)
|
||||
{
|
||||
return value >> shift | value << (64 - shift);
|
||||
}
|
||||
|
||||
public static uint RotateLeft(this uint value, int shift)
|
||||
{
|
||||
return value << shift | value >> (32 - shift);
|
||||
}
|
||||
|
||||
public static uint RotateRight(this uint value, int shift)
|
||||
{
|
||||
return value >> shift | value << (32 - shift);
|
||||
}
|
||||
|
||||
public static ushort RotateLeft(this ushort value, int shift)
|
||||
{
|
||||
return (ushort)(value << shift | value >> (16 - shift));
|
||||
}
|
||||
|
||||
public static ushort RotateRight(this ushort value, int shift)
|
||||
{
|
||||
return (ushort)(value >> shift | value << (16 - shift));
|
||||
}
|
||||
|
||||
public static byte RotateLeft(this byte value, int shift)
|
||||
{
|
||||
return (byte)(value << shift | value >> (8 - shift));
|
||||
}
|
||||
|
||||
public static byte RotateRight(this byte value, int shift)
|
||||
{
|
||||
return (byte)(value >> shift | value << (8 - shift));
|
||||
}
|
||||
|
||||
|
||||
public static byte GetUInt8(this byte[] data, uint offset)
|
||||
{
|
||||
return data[offset];
|
||||
}
|
||||
|
||||
public static unsafe short GetInt16(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = &data[offset])
|
||||
return *(short*)ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (Int16)((data[offset] << 8) | data[offset + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static unsafe ushort GetUInt16(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = &data[offset])
|
||||
return *(ushort*)ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (UInt16)((data[offset] << 8) | data[offset + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe int GetInt32(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = &data[offset])
|
||||
return *(int*)ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (Int32)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]);
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe uint GetUInt32(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = &data[offset])
|
||||
return *(uint*)ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (uint)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static unsafe ulong GetUInt64(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = &data[offset])
|
||||
return *(ulong*)ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt64 rt = 0;
|
||||
byte* p = (byte*)&rt;
|
||||
|
||||
*(p + 7) = data[offset++];
|
||||
*(p + 6) = data[offset++];
|
||||
*(p + 5) = data[offset++];
|
||||
*(p + 4) = data[offset++];
|
||||
*(p + 3) = data[offset++];
|
||||
*(p + 2) = data[offset++];
|
||||
*(p + 1) = data[offset++];
|
||||
*(p) = data[offset++];
|
||||
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static unsafe long GetInt64(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = &data[offset])
|
||||
return *(long*)ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
Int64 rt = 0;
|
||||
byte* p = (byte*)&rt;
|
||||
|
||||
*(p + 7) = data[offset++];
|
||||
*(p + 6) = data[offset++];
|
||||
*(p + 5) = data[offset++];
|
||||
*(p + 4) = data[offset++];
|
||||
*(p + 3) = data[offset++];
|
||||
*(p + 2) = data[offset++];
|
||||
*(p + 1) = data[offset++];
|
||||
*(p) = data[offset++];
|
||||
|
||||
return rt;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe float GetFloat32(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = &data[offset])
|
||||
return *(float*)ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
float rt = 0;
|
||||
byte* p = (byte*)&rt;
|
||||
*p = data[offset + 3];
|
||||
*(p + 1) = data[offset + 2];
|
||||
*(p + 2) = data[offset + 1];
|
||||
*(p + 3) = data[offset];
|
||||
return rt;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static unsafe double GetFloat64(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
if (endian == Endian.Little)
|
||||
{
|
||||
fixed (byte* ptr = &data[offset])
|
||||
return *(double*)ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
double rt = 0;
|
||||
byte* p = (byte*)&rt;
|
||||
|
||||
*(p + 7) = data[offset++];
|
||||
*(p + 6) = data[offset++];
|
||||
*(p + 5) = data[offset++];
|
||||
*(p + 4) = data[offset++];
|
||||
*(p + 3) = data[offset++];
|
||||
*(p + 2) = data[offset++];
|
||||
*(p + 1) = data[offset++];
|
||||
*(p) = data[offset++];
|
||||
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static bool GetBoolean(this byte[] data, uint offset)
|
||||
{
|
||||
return data[offset] > 0;
|
||||
}
|
||||
|
||||
public static char GetChar(this byte[] data, uint offset)
|
||||
{
|
||||
return Convert.ToChar(((data[offset] << 8) | data[offset + 1]));
|
||||
}
|
||||
|
||||
|
||||
public static string GetString(this byte[] data, uint offset, uint length)
|
||||
{
|
||||
return Encoding.UTF8.GetString(data, (int)offset, (int)length);
|
||||
}
|
||||
|
||||
public static string[] GetStringArray(this byte[] data, uint offset, uint length)
|
||||
{
|
||||
List<string> ar = new List<string>();
|
||||
|
||||
uint i = 0;
|
||||
|
||||
while (i < length)
|
||||
{
|
||||
var cl = GetUInt32(data, offset + i, Endian.Little);
|
||||
i += 4;
|
||||
ar.Add(Encoding.UTF8.GetString(data, (int)(offset + i), (int)cl));
|
||||
i += cl;
|
||||
}
|
||||
|
||||
return ar.ToArray();
|
||||
}
|
||||
|
||||
public static Uuid GetUUID(this byte[] data, uint offset)
|
||||
{
|
||||
return new Uuid(data, offset);
|
||||
}
|
||||
|
||||
//public static Guid GetGuid(this byte[] data, uint offset)
|
||||
//{
|
||||
// return new Guid(Clip(data, offset, 16));
|
||||
//}
|
||||
|
||||
public static DateTime GetDateTime(this byte[] data, uint offset, Endian endian)
|
||||
{
|
||||
var ticks = GetInt64(data, offset, endian);
|
||||
return new DateTime(ticks, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
|
||||
public static IPAddress GetIPv4Address(this byte[] data, uint offset)
|
||||
{
|
||||
return new IPAddress((long)GetUInt32(data, offset, Endian.Little));
|
||||
}
|
||||
|
||||
|
||||
public static IPAddress GetIPv6Address(this byte[] data, uint offset)
|
||||
{
|
||||
return new IPAddress(Clip(data, offset, 16));
|
||||
}
|
||||
|
||||
public static byte[] Clip(this byte[] data, uint offset, uint length)
|
||||
{
|
||||
if (data.Length < offset + length)
|
||||
throw new ArgumentException("Length exceeds array boundary.");
|
||||
|
||||
// if (length == data.Length && offset == 0)
|
||||
// return data.ToArray();
|
||||
|
||||
var b = new byte[length];
|
||||
Buffer.BlockCopy(data, (int)offset, b, 0, (int)length);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static string ToISODateTime(this DateTime date)
|
||||
{
|
||||
return date.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
public static uint ToUnixTime(this DateTime date)
|
||||
{
|
||||
return (uint)(date - new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1600
Libraries/Esiur/Data/DataDeserializer.cs
Normal file
1600
Libraries/Esiur/Data/DataDeserializer.cs
Normal file
File diff suppressed because it is too large
Load Diff
1005
Libraries/Esiur/Data/DataSerializer.cs
Normal file
1005
Libraries/Esiur/Data/DataSerializer.cs
Normal file
File diff suppressed because it is too large
Load Diff
12
Libraries/Esiur/Data/Endian.cs
Normal file
12
Libraries/Esiur/Data/Endian.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public enum Endian
|
||||
{
|
||||
Big,
|
||||
Little
|
||||
}
|
||||
}
|
||||
137
Libraries/Esiur/Data/Gvwie/GroupInt16Codec.cs
Normal file
137
Libraries/Esiur/Data/Gvwie/GroupInt16Codec.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Data.Gvwie;
|
||||
|
||||
public static class GroupInt16Codec
|
||||
{
|
||||
// ----------------- Encoder -----------------
|
||||
public static byte[] Encode(IList<short> values)
|
||||
{
|
||||
var dst = new List<byte>(values.Count); // close lower bound
|
||||
int i = 0;
|
||||
|
||||
while (i < values.Count)
|
||||
{
|
||||
ushort zz = ZigZag16(values[i]);
|
||||
|
||||
// Fast path: single byte with 7-bit ZigZag
|
||||
if (zz <= 0x7Fu)
|
||||
{
|
||||
dst.Add((byte)zz); // MSB=0 implicitly
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group path: up to 64 items sharing width (1 or 2 bytes)
|
||||
int start = i;
|
||||
int count = 1;
|
||||
int width = (zz <= 0xFFu) ? 1 : 2;
|
||||
|
||||
while (count < 64 && (i + count) < values.Count)
|
||||
{
|
||||
ushort z2 = ZigZag16(values[i + count]);
|
||||
int w2 = (z2 <= 0xFFu) ? 1 : 2;
|
||||
if (w2 > width) width = w2; // widen as needed
|
||||
count++;
|
||||
}
|
||||
|
||||
// Header: 1 | (count-1)[6 bits] | (width-1)[1 bit]
|
||||
byte header = 0x80;
|
||||
header |= (byte)(((count - 1) & 0x3F) << 1);
|
||||
header |= (byte)((width - 1) & 0x01);
|
||||
dst.Add(header);
|
||||
|
||||
// Payload: count ZigZag magnitudes, LE, 'width' bytes each
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
ushort z = ZigZag16(values[start + k]);
|
||||
WriteLE(dst, z, width);
|
||||
}
|
||||
|
||||
i += count;
|
||||
}
|
||||
|
||||
return dst.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Decoder -----------------
|
||||
public static short[] Decode(ReadOnlySpan<byte> src)
|
||||
{
|
||||
var result = new List<short>();
|
||||
int pos = 0;
|
||||
|
||||
while (pos < src.Length)
|
||||
{
|
||||
byte h = src[pos++];
|
||||
|
||||
if ((h & 0x80) == 0)
|
||||
{
|
||||
// Fast path: 7-bit ZigZag
|
||||
ushort zz7 = (ushort)(h & 0x7F);
|
||||
result.Add(UnZigZag16(zz7));
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = ((h >> 1) & 0x3F) + 1; // 1..64
|
||||
int width = (h & 0x01) + 1; // 1..2
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
uint raw = ReadLE(src, ref pos, width);
|
||||
if (width > 2 && (raw >> 16) != 0)
|
||||
throw new OverflowException("Decoded ZigZag value exceeds 16-bit range.");
|
||||
|
||||
ushort u = (ushort)raw;
|
||||
short val = UnZigZag16(u);
|
||||
result.Add(val);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Helpers -----------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ushort ZigZag16(short v)
|
||||
{
|
||||
// (v << 1) ^ (v >> 15), result as unsigned 16-bit
|
||||
return (ushort)(((uint)(ushort)v << 1) ^ (uint)((int)v >> 15));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static short UnZigZag16(ushort u)
|
||||
{
|
||||
// (u >> 1) ^ -(u & 1), narrowed to 16-bit signed
|
||||
return (short)((u >> 1) ^ (ushort)-(short)(u & 1));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteLE(List<byte> dst, ushort value, int width)
|
||||
{
|
||||
// width is 1 or 2
|
||||
dst.Add((byte)(value & 0xFF));
|
||||
if (width == 2) dst.Add((byte)(value >> 8));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint ReadLE(ReadOnlySpan<byte> src, ref int pos, int width)
|
||||
{
|
||||
if ((uint)(pos + width) > (uint)src.Length)
|
||||
throw new ArgumentException("Buffer underflow while reading group payload.");
|
||||
|
||||
uint v = src[pos++];
|
||||
if (width == 2)
|
||||
{
|
||||
v |= (uint)src[pos++] << 8;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
129
Libraries/Esiur/Data/Gvwie/GroupInt32Codec.cs
Normal file
129
Libraries/Esiur/Data/Gvwie/GroupInt32Codec.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections;
|
||||
|
||||
namespace Esiur.Data.Gvwie;
|
||||
|
||||
public static class GroupInt32Codec
|
||||
{
|
||||
// ----------------- Encoder -----------------
|
||||
public static byte[] Encode(IList<int> values)
|
||||
{
|
||||
//var values = value as int[];
|
||||
|
||||
var dst = new List<byte>(values.Count * 2);
|
||||
int i = 0;
|
||||
|
||||
while (i < values.Count)
|
||||
{
|
||||
uint zz = ZigZag32(values[i]);
|
||||
|
||||
// Fast path: single byte (MSB=0) when zigzag fits in 7 bits
|
||||
if (zz <= 0x7Fu)
|
||||
{
|
||||
dst.Add((byte)zz);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group: up to 32 items sharing a common width (1..4 bytes)
|
||||
int start = i;
|
||||
int count = 1;
|
||||
int width = WidthFromZigZag(zz);
|
||||
|
||||
while (count < 32 && (i + count) < values.Count)
|
||||
{
|
||||
uint z2 = ZigZag32(values[i + count]);
|
||||
int w2 = WidthFromZigZag(z2);
|
||||
width = Math.Max(width, w2); // widen as needed
|
||||
count++;
|
||||
}
|
||||
|
||||
// Header: 1 | (count-1)[5 bits] | (width-1)[2 bits]
|
||||
byte header = 0x80;
|
||||
header |= (byte)(((count - 1) & 0x1F) << 2);
|
||||
header |= (byte)((width - 1) & 0x03);
|
||||
dst.Add(header);
|
||||
|
||||
// Payload: 'count' zigzag values, LE, 'width' bytes each
|
||||
for (int k = 0; k < count; k++)
|
||||
WriteLE(dst, ZigZag32(values[start + k]), width);
|
||||
|
||||
i += count;
|
||||
}
|
||||
|
||||
return dst.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Decoder -----------------
|
||||
public static int[] Decode(ReadOnlySpan<byte> src)
|
||||
{
|
||||
var result = new List<int>();
|
||||
int pos = 0;
|
||||
|
||||
while (pos < src.Length)
|
||||
{
|
||||
byte h = src[pos++];
|
||||
|
||||
if ((h & 0x80) == 0)
|
||||
{
|
||||
// Fast path: 7-bit ZigZag in low bits
|
||||
uint zz7 = (uint)(h & 0x7F);
|
||||
result.Add(UnZigZag32(zz7));
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = ((h >> 2) & 0x1F) + 1; // 1..32
|
||||
int width = (h & 0x03) + 1; // 1..4
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
uint raw = (uint)ReadLE(src, ref pos, width);
|
||||
int val = UnZigZag32(raw);
|
||||
result.Add(val);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Helpers -----------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint ZigZag32(int v) => (uint)((v << 1) ^ (v >> 31));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int UnZigZag32(uint u) => (int)((u >> 1) ^ (uint)-(int)(u & 1));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int WidthFromZigZag(uint z)
|
||||
{
|
||||
if (z <= 0xFFu) return 1;
|
||||
if (z <= 0xFFFFu) return 2;
|
||||
if (z <= 0xFFFFFFu) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteLE(List<byte> dst, uint value, int width)
|
||||
{
|
||||
for (int i = 0; i < width; i++)
|
||||
dst.Add((byte)((value >> (8 * i)) & 0xFF));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ReadLE(ReadOnlySpan<byte> src, ref int pos, int width)
|
||||
{
|
||||
if ((uint)(pos + width) > (uint)src.Length)
|
||||
throw new ArgumentException("Buffer underflow while reading group payload.");
|
||||
|
||||
ulong v = 0;
|
||||
for (int i = 0; i < width; i++)
|
||||
v |= (ulong)src[pos++] << (8 * i);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
135
Libraries/Esiur/Data/Gvwie/GroupInt64Codec.cs
Normal file
135
Libraries/Esiur/Data/Gvwie/GroupInt64Codec.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Data.Gvwie;
|
||||
|
||||
public static class GroupInt64Codec
|
||||
{
|
||||
// ----------------- Encoder -----------------
|
||||
public static byte[] Encode(IList<long> values)
|
||||
{
|
||||
var dst = new List<byte>(values.Count * 2);
|
||||
int i = 0;
|
||||
|
||||
while (i < values.Count)
|
||||
{
|
||||
ulong zz = ZigZag64(values[i]);
|
||||
|
||||
// Fast path: 1 byte when ZigZag fits in 7 bits
|
||||
if (zz <= 0x7Ful)
|
||||
{
|
||||
dst.Add((byte)zz); // MSB = 0 implicitly
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group path: up to 16 items sharing a common width (1..8 bytes)
|
||||
int start = i;
|
||||
int count = 1;
|
||||
int width = WidthFromZigZag(zz);
|
||||
|
||||
while (count < 16 && (i + count) < values.Count)
|
||||
{
|
||||
ulong z2 = ZigZag64(values[i + count]);
|
||||
int w2 = WidthFromZigZag(z2);
|
||||
width = Math.Max(width, w2); // widen as needed
|
||||
count++;
|
||||
}
|
||||
|
||||
// Header: 1 | (count-1)[4 bits] | (width-1)[3 bits]
|
||||
byte header = 0x80;
|
||||
header |= (byte)(((count - 1) & 0x0F) << 3);
|
||||
header |= (byte)((width - 1) & 0x07);
|
||||
dst.Add(header);
|
||||
|
||||
// Payload: 'count' ZigZag values, LE, 'width' bytes each
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
ulong z = ZigZag64(values[start + k]);
|
||||
WriteLE(dst, z, width);
|
||||
}
|
||||
|
||||
i += count;
|
||||
}
|
||||
|
||||
return dst.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Decoder -----------------
|
||||
public static long[] Decode(ReadOnlySpan<byte> src)
|
||||
{
|
||||
var result = new List<long>();
|
||||
int pos = 0;
|
||||
|
||||
while (pos < src.Length)
|
||||
{
|
||||
byte h = src[pos++];
|
||||
|
||||
if ((h & 0x80) == 0)
|
||||
{
|
||||
// Fast path: 7-bit ZigZag
|
||||
ulong zz7 = (ulong)(h & 0x7F);
|
||||
result.Add(UnZigZag64(zz7));
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = ((h >> 3) & 0x0F) + 1; // 1..16
|
||||
int width = (h & 0x07) + 1; // 1..8
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
ulong raw = ReadLE(src, ref pos, width);
|
||||
long val = UnZigZag64(raw);
|
||||
result.Add(val);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Helpers -----------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ZigZag64(long v) => (ulong)((v << 1) ^ (v >> 63));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static long UnZigZag64(ulong u) => (long)((u >> 1) ^ (ulong)-(long)(u & 1));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int WidthFromZigZag(ulong z)
|
||||
{
|
||||
if (z <= 0xFFUL) return 1;
|
||||
if (z <= 0xFFFFUL) return 2;
|
||||
if (z <= 0xFFFFFFUL) return 3;
|
||||
if (z <= 0xFFFFFFFFUL) return 4;
|
||||
if (z <= 0xFFFFFFFFFFUL) return 5;
|
||||
if (z <= 0xFFFFFFFFFFFFUL) return 6;
|
||||
if (z <= 0xFFFFFFFFFFFFFFUL) return 7;
|
||||
return 8;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteLE(List<byte> dst, ulong value, int width)
|
||||
{
|
||||
for (int i = 0; i < width; i++)
|
||||
dst.Add((byte)((value >> (8 * i)) & 0xFF));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ReadLE(ReadOnlySpan<byte> src, ref int pos, int width)
|
||||
{
|
||||
if ((uint)(pos + width) > (uint)src.Length)
|
||||
throw new ArgumentException("Buffer underflow while reading group payload.");
|
||||
|
||||
ulong v = 0;
|
||||
for (int i = 0; i < width; i++)
|
||||
v |= (ulong)src[pos++] << (8 * i);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
121
Libraries/Esiur/Data/Gvwie/GroupUInt16Codec.cs
Normal file
121
Libraries/Esiur/Data/Gvwie/GroupUInt16Codec.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Data.Gvwie;
|
||||
|
||||
|
||||
public static class GroupUInt16Codec
|
||||
{
|
||||
// ----------------- Encoder -----------------
|
||||
public static byte[] Encode(IList<ushort> values)
|
||||
{
|
||||
if (values is null) throw new ArgumentNullException(nameof(values));
|
||||
|
||||
var dst = new List<byte>(values.Count * 2);
|
||||
int i = 0;
|
||||
|
||||
while (i < values.Count)
|
||||
{
|
||||
ushort v = values[i];
|
||||
|
||||
// Fast path: single byte for 0..127
|
||||
if (v <= 0x7F)
|
||||
{
|
||||
dst.Add((byte)v); // MSB=0 implicitly
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group path: up to 16 items sharing a common width (1..2 bytes for uint16)
|
||||
int start = i;
|
||||
int count = 1;
|
||||
int width = WidthFromUnsigned(v);
|
||||
|
||||
while (count < 16 && (i + count) < values.Count)
|
||||
{
|
||||
ushort v2 = values[i + count];
|
||||
int w2 = WidthFromUnsigned(v2);
|
||||
if (w2 > width) width = w2; // widen group if needed
|
||||
count++;
|
||||
}
|
||||
|
||||
// Header: 1 | (count-1)[4b] | (width-1)[3b]
|
||||
byte header = 0x80;
|
||||
header |= (byte)(((count - 1) & 0xF) << 3);
|
||||
header |= (byte)((width - 1) & 0x7);
|
||||
dst.Add(header);
|
||||
|
||||
// Payload
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
WriteLE(dst, values[start + k], width);
|
||||
}
|
||||
|
||||
i += count;
|
||||
}
|
||||
|
||||
return dst.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Decoder -----------------
|
||||
public static ushort[] Decode(ReadOnlySpan<byte> src)
|
||||
{
|
||||
var result = new List<ushort>();
|
||||
int pos = 0;
|
||||
|
||||
while (pos < src.Length)
|
||||
{
|
||||
byte h = src[pos++];
|
||||
|
||||
if ((h & 0x80) == 0)
|
||||
{
|
||||
// Fast path byte (0..127)
|
||||
result.Add(h);
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = ((h >> 3) & 0xF) + 1; // 1..16
|
||||
int width = (h & 0x7) + 1; // 1..8 (expect 1..2)
|
||||
|
||||
if (width > 2)
|
||||
throw new NotSupportedException($"Width {width} bytes exceeds uint16 capacity.");
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
uint val = (uint)ReadLE(src, ref pos, width);
|
||||
if (val > 0xFFFFu)
|
||||
throw new OverflowException("Decoded value exceeds UInt16 range.");
|
||||
result.Add((ushort)val);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Helpers -----------------
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int WidthFromUnsigned(ushort v) => (v <= 0xFF) ? 1 : 2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteLE(List<byte> dst, ushort value, int width)
|
||||
{
|
||||
// width is 1 or 2
|
||||
dst.Add((byte)(value & 0xFF));
|
||||
if (width == 2) dst.Add((byte)(value >> 8));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ReadLE(ReadOnlySpan<byte> src, ref int pos, int width)
|
||||
{
|
||||
if (pos + width > src.Length)
|
||||
throw new ArgumentException("Buffer underflow while reading payload.");
|
||||
|
||||
ulong v = src[pos++]; // first byte (LSB)
|
||||
if (width == 2) v |= (ulong)src[pos++] << 8;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
125
Libraries/Esiur/Data/Gvwie/GroupUInt32Codec.cs
Normal file
125
Libraries/Esiur/Data/Gvwie/GroupUInt32Codec.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Data.Gvwie;
|
||||
|
||||
public static class GroupUInt32Codec
|
||||
{
|
||||
// ----------------- Encoder -----------------
|
||||
public static byte[] Encode(IList<uint> values)
|
||||
{
|
||||
if (values is null) throw new ArgumentNullException(nameof(values));
|
||||
|
||||
var dst = new List<byte>(values.Count * 2);
|
||||
int i = 0;
|
||||
|
||||
while (i < values.Count)
|
||||
{
|
||||
uint v = values[i];
|
||||
|
||||
// Fast path: single byte for 0..127
|
||||
if (v <= 0x7Fu)
|
||||
{
|
||||
dst.Add((byte)v); // MSB=0 implicitly
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group path: up to 16 items sharing a common width (1..4 bytes for uint32)
|
||||
int start = i;
|
||||
int count = 1;
|
||||
int width = WidthFromUnsigned(v);
|
||||
|
||||
while (count < 16 && (i + count) < values.Count)
|
||||
{
|
||||
uint v2 = values[i + count];
|
||||
int w2 = WidthFromUnsigned(v2);
|
||||
if (w2 > width) width = w2;
|
||||
count++;
|
||||
}
|
||||
|
||||
// Header: 1 | (count-1)[4b] | (width-1)[3b]
|
||||
byte header = 0x80;
|
||||
header |= (byte)(((count - 1) & 0xF) << 3);
|
||||
header |= (byte)((width - 1) & 0x7);
|
||||
dst.Add(header);
|
||||
|
||||
// Payload
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
WriteLE(dst, values[start + k], width);
|
||||
}
|
||||
|
||||
i += count;
|
||||
}
|
||||
|
||||
return dst.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Decoder -----------------
|
||||
public static uint[] Decode(ReadOnlySpan<byte> src)
|
||||
{
|
||||
var result = new List<uint>();
|
||||
int pos = 0;
|
||||
|
||||
while (pos < src.Length)
|
||||
{
|
||||
byte h = src[pos++];
|
||||
|
||||
if ((h & 0x80) == 0)
|
||||
{
|
||||
// Fast path byte (0..127)
|
||||
result.Add(h);
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = ((h >> 3) & 0xF) + 1; // 1..16
|
||||
int width = (h & 0x7) + 1; // 1..8 (we expect 1..4)
|
||||
|
||||
if (width > 4)
|
||||
throw new NotSupportedException($"Width {width} bytes exceeds uint32 capacity.");
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
uint val = (uint)ReadLE(src, ref pos, width);
|
||||
result.Add(val);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Helpers -----------------
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int WidthFromUnsigned(uint v)
|
||||
{
|
||||
if (v <= 0xFFu) return 1;
|
||||
if (v <= 0xFFFFu) return 2;
|
||||
if (v <= 0xFFFFFFu) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteLE(List<byte> dst, uint value, int width)
|
||||
{
|
||||
for (int i = 0; i < width; i++)
|
||||
dst.Add((byte)((value >> (8 * i)) & 0xFF));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ReadLE(ReadOnlySpan<byte> src, ref int pos, int width)
|
||||
{
|
||||
if (pos + width > src.Length)
|
||||
throw new ArgumentException("Buffer underflow while reading payload.");
|
||||
|
||||
ulong v = 0;
|
||||
for (int i = 0; i < width; i++)
|
||||
v |= (ulong)src[pos++] << (8 * i);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
133
Libraries/Esiur/Data/Gvwie/GroupUInt64Codec.cs
Normal file
133
Libraries/Esiur/Data/Gvwie/GroupUInt64Codec.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Data.Gvwie;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public static class GroupUInt64Codec
|
||||
{
|
||||
// ----------------- Encoder -----------------
|
||||
public static byte[] Encode(IList<ulong> values)
|
||||
{
|
||||
if (values is null) throw new ArgumentNullException(nameof(values));
|
||||
|
||||
var dst = new List<byte>(values.Count * 2);
|
||||
int i = 0;
|
||||
|
||||
while (i < values.Count)
|
||||
{
|
||||
ulong v = values[i];
|
||||
|
||||
// Fast path: single byte for 0..127
|
||||
if (v <= 0x7FUL)
|
||||
{
|
||||
dst.Add((byte)v); // MSB = 0 implicitly
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group path: up to 16 items sharing max width (1..8 bytes)
|
||||
int start = i;
|
||||
int count = 1;
|
||||
int width = WidthFromUnsigned(v);
|
||||
|
||||
while (count < 16 && (i + count) < values.Count)
|
||||
{
|
||||
ulong v2 = values[i + count];
|
||||
int w2 = WidthFromUnsigned(v2);
|
||||
if (w2 > width) width = w2;
|
||||
count++;
|
||||
}
|
||||
|
||||
// Header: 1 | (count-1)[4b] | (width-1)[3b]
|
||||
byte header = 0x80;
|
||||
header |= (byte)(((count - 1) & 0xF) << 3);
|
||||
header |= (byte)((width - 1) & 0x7);
|
||||
dst.Add(header);
|
||||
|
||||
// Payload
|
||||
for (int k = 0; k < count; k++)
|
||||
WriteLE(dst, values[start + k], width);
|
||||
|
||||
i += count;
|
||||
}
|
||||
|
||||
return dst.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Decoder -----------------
|
||||
public static ulong[] Decode(ReadOnlySpan<byte> src)
|
||||
{
|
||||
var result = new List<ulong>();
|
||||
int pos = 0;
|
||||
|
||||
while (pos < src.Length)
|
||||
{
|
||||
byte h = src[pos++];
|
||||
|
||||
if ((h & 0x80) == 0)
|
||||
{
|
||||
// Fast path byte (0..127)
|
||||
result.Add(h);
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = ((h >> 3) & 0xF) + 1; // 1..16
|
||||
int width = (h & 0x7) + 1; // 1..8
|
||||
|
||||
if (width < 1 || width > 8)
|
||||
throw new NotSupportedException($"Invalid width {width} in header.");
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
ulong val = ReadLE(src, ref pos, width);
|
||||
result.Add(val);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// ----------------- Helpers -----------------
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int WidthFromUnsigned(ulong v)
|
||||
{
|
||||
if (v <= 0xFFUL) return 1;
|
||||
if (v <= 0xFFFFUL) return 2;
|
||||
if (v <= 0xFFFFFFUL) return 3;
|
||||
if (v <= 0xFFFFFFFFUL) return 4;
|
||||
if (v <= 0xFFFFFFFFFFUL) return 5;
|
||||
if (v <= 0xFFFFFFFFFFFFUL) return 6;
|
||||
if (v <= 0xFFFFFFFFFFFFFFUL) return 7;
|
||||
return 8;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteLE(List<byte> dst, ulong value, int width)
|
||||
{
|
||||
for (int i = 0; i < width; i++)
|
||||
dst.Add((byte)((value >> (8 * i)) & 0xFF));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ReadLE(ReadOnlySpan<byte> src, ref int pos, int width)
|
||||
{
|
||||
if (pos + width > src.Length)
|
||||
throw new ArgumentException("Buffer underflow while reading payload.");
|
||||
|
||||
ulong v = 0;
|
||||
for (int i = 0; i < width; i++)
|
||||
v |= (ulong)src[pos++] << (8 * i);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
20
Libraries/Esiur/Data/IDynamicResource.cs
Normal file
20
Libraries/Esiur/Data/IDynamicResource.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Esiur.Core;
|
||||
using Esiur.Data.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public interface IDynamicResource
|
||||
{
|
||||
public PropertyValue[] SerializeResource();
|
||||
public Map<byte, PropertyValue> SerializeResourceAfter(ulong age);
|
||||
|
||||
public object GetResourceProperty(byte index);
|
||||
public AsyncReply SetResourcePropertyAsync(byte index, object value);
|
||||
public void SetResourceProperty(byte index, object value);
|
||||
|
||||
public TypeDef ResourceDefinition { get; }
|
||||
}
|
||||
}
|
||||
10
Libraries/Esiur/Data/IRecord.cs
Normal file
10
Libraries/Esiur/Data/IRecord.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
public interface IRecord
|
||||
{
|
||||
|
||||
}
|
||||
13
Libraries/Esiur/Data/IUserType.cs
Normal file
13
Libraries/Esiur/Data/IUserType.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data;
|
||||
public interface IUserType
|
||||
{
|
||||
object Get();
|
||||
void Set(object value);
|
||||
|
||||
object SetAndGet(object value);
|
||||
}
|
||||
|
||||
18
Libraries/Esiur/Data/Int128.cs
Normal file
18
Libraries/Esiur/Data/Int128.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public struct Int128
|
||||
{
|
||||
public Int128( ulong lsb, ulong msb)
|
||||
{
|
||||
this.MSB = msb;
|
||||
this.LSB = lsb;
|
||||
}
|
||||
|
||||
public ulong MSB { get; set; }
|
||||
public ulong LSB { get; set; }
|
||||
}
|
||||
}
|
||||
242
Libraries/Esiur/Data/KeyList.cs
Normal file
242
Libraries/Esiur/Data/KeyList.cs
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Esiur.Core;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
public class KeyList<KT, T> : IEnumerable<KeyValuePair<KT, T>>
|
||||
{
|
||||
private readonly object syncRoot = new object();
|
||||
private Dictionary<KT, T> dic;
|
||||
|
||||
public delegate void Modified(KT key, T oldValue, T newValue, KeyList<KT, T> sender);
|
||||
public delegate void Added(T value, KeyList<KT, T> sender);
|
||||
public delegate void Removed(KT key, T value, KeyList<KT, T> sender);
|
||||
public delegate void Cleared(KeyList<KT, T> sender);
|
||||
|
||||
public event Modified OnModified;
|
||||
public event Removed OnRemoved;
|
||||
public event Cleared OnCleared;
|
||||
public event Added OnAdd;
|
||||
|
||||
bool removableList;
|
||||
|
||||
public object SyncRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
return syncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
public T Take(KT key)
|
||||
{
|
||||
if (dic.ContainsKey(key))
|
||||
{
|
||||
var v = dic[key];
|
||||
Remove(key);
|
||||
return v;
|
||||
}
|
||||
else
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public void Sort(Func<KeyValuePair<KT, T>, object> keySelector)
|
||||
{
|
||||
dic = dic.OrderBy(keySelector).ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
var a = new T[Count];
|
||||
dic.Values.CopyTo(a, 0);
|
||||
return a;
|
||||
}
|
||||
|
||||
public void Add(KT key, T value)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (removableList)
|
||||
if (value != null)
|
||||
((IDestructible)value).OnDestroy += ItemDestroyed;
|
||||
|
||||
if (dic.ContainsKey(key))
|
||||
{
|
||||
var oldValue = dic[key];
|
||||
if (removableList)
|
||||
if (oldValue != null)
|
||||
((IDestructible)oldValue).OnDestroy -= ItemDestroyed;
|
||||
dic[key] = value;
|
||||
if (OnModified != null)
|
||||
OnModified(key, oldValue, value, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
dic.Add(key, value);
|
||||
|
||||
if (OnAdd != null)
|
||||
OnAdd(value, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemDestroyed(object sender)
|
||||
{
|
||||
RemoveValue((T)sender);
|
||||
}
|
||||
|
||||
public void RemoveValue(T value)
|
||||
{
|
||||
var toRemove = new List<KT>();
|
||||
foreach (var kv in dic)
|
||||
if (kv.Value.Equals(value))
|
||||
toRemove.Add(kv.Key);
|
||||
|
||||
foreach (var k in toRemove)
|
||||
Remove(k);
|
||||
}
|
||||
|
||||
public T this[KT key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (dic.ContainsKey(key))
|
||||
return dic[key];
|
||||
else
|
||||
return default(T);
|
||||
}
|
||||
set
|
||||
{
|
||||
Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IEnumerator<KeyValuePair<KT, T>> GetEnumerator()
|
||||
{
|
||||
return dic.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return dic.GetEnumerator();
|
||||
}
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (removableList)
|
||||
foreach (IDestructible v in dic.Values)
|
||||
if (v != null)
|
||||
v.OnDestroy -= ItemDestroyed;
|
||||
|
||||
lock (syncRoot)
|
||||
dic.Clear();
|
||||
|
||||
if (OnCleared != null)
|
||||
OnCleared(this);
|
||||
}
|
||||
|
||||
public Dictionary<KT, T>.KeyCollection Keys
|
||||
{
|
||||
get { return dic.Keys; }
|
||||
}
|
||||
|
||||
public Dictionary<KT, T>.ValueCollection Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return dic.Values;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(KT key)
|
||||
{
|
||||
if (!dic.ContainsKey(key))
|
||||
return;
|
||||
|
||||
var value = dic[key];
|
||||
|
||||
if (removableList)
|
||||
if (value != null)
|
||||
((IDestructible)value).OnDestroy -= ItemDestroyed;
|
||||
|
||||
lock (syncRoot)
|
||||
dic.Remove(key);
|
||||
|
||||
if (OnRemoved != null)
|
||||
OnRemoved(key, value, this);
|
||||
}
|
||||
|
||||
public object Owner
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return dic.Count; }
|
||||
}
|
||||
public bool Contains(KT Key)
|
||||
{
|
||||
return dic.ContainsKey(Key);
|
||||
}
|
||||
public bool ContainsKey(KT Key)
|
||||
{
|
||||
return dic.ContainsKey(Key);
|
||||
}
|
||||
public bool ContainsValue(T Value)
|
||||
{
|
||||
return dic.ContainsValue(Value);
|
||||
}
|
||||
|
||||
|
||||
public KeyList(object owner = null)
|
||||
{
|
||||
#if NETSTANDARD
|
||||
removableList = (typeof(IDestructible).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()));
|
||||
#else
|
||||
removableList = (typeof(IDestructible).IsAssignableFrom(typeof(T)));
|
||||
#endif
|
||||
|
||||
this.Owner = owner;
|
||||
|
||||
if (typeof(KT) == typeof(string))
|
||||
dic = (Dictionary<KT, T>)(object)new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
|
||||
else
|
||||
dic = new Dictionary<KT, T>();
|
||||
}
|
||||
}
|
||||
264
Libraries/Esiur/Data/Map.cs
Normal file
264
Libraries/Esiur/Data/Map.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Misc;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
//public class Map : IEnumerable<KeyValuePair<object, object>>
|
||||
//{
|
||||
// private Dictionary<object, object> dic = new();
|
||||
|
||||
// public IEnumerator<KeyValuePair<object, object>> GetEnumerator()
|
||||
// {
|
||||
// return dic.GetEnumerator();
|
||||
// }
|
||||
|
||||
// IEnumerator IEnumerable.GetEnumerator()
|
||||
// {
|
||||
// return dic.GetEnumerator();
|
||||
// }
|
||||
//}
|
||||
|
||||
public interface IMap
|
||||
{
|
||||
public void Add(object key, object value);
|
||||
//public void Remove(object key);
|
||||
//public void Clear();
|
||||
//public bool ContainsKey(object key);
|
||||
public object[] Serialize();
|
||||
|
||||
public IEnumerable GetKeys();
|
||||
public IEnumerable GetValues();
|
||||
}
|
||||
|
||||
public class Map<KT, VT> : Dictionary<KT, VT>, IMap // IEnumerable<KeyValuePair<KT, VT>>
|
||||
{
|
||||
|
||||
//private Dictionary<KT, VT> dic = new Dictionary<KT, VT>();
|
||||
private object syncRoot = new object();
|
||||
|
||||
//public static implicit operator Map<KT, VT>(Dictionary<KT, VT> dictionary)
|
||||
// => new Map<KT, VT>() { dic = dictionary };
|
||||
|
||||
//public static implicit operator Dictionary<KT, VT>(Map<KT, VT> map)
|
||||
// => map.ToDictionary();
|
||||
|
||||
//Change map types
|
||||
public Map<NewKeyType, NewValueType> Select<NewKeyType, NewValueType>
|
||||
(Func<KeyValuePair<KT, VT>, KeyValuePair<NewKeyType, NewValueType>> selector)
|
||||
{
|
||||
var rt = new Map<NewKeyType, NewValueType>();
|
||||
foreach (var kv in this)
|
||||
{
|
||||
var nt = selector(kv);
|
||||
rt.Add(nt.Key, nt.Value);
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
//public bool ContainsKey(KT key)
|
||||
//{
|
||||
// return dic.ContainsKey(key);
|
||||
//}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var rt = "";
|
||||
foreach (var kv in this)
|
||||
rt += kv.Key + ": " + kv.Value.ToString() + " \r\n";
|
||||
|
||||
return rt.TrimEnd('\r', '\n');
|
||||
}
|
||||
|
||||
//public Map(Map<KT,VT> source)
|
||||
//{
|
||||
// dic = source.dic;
|
||||
//}
|
||||
public Map()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//public static Map<KT,VT> FromMap(Map<KT,VT> source, Type destinationType)
|
||||
//{
|
||||
// var rt = Activator.CreateInstance(destinationType) as Map<KT, VT>;
|
||||
// rt.dic = source.dic;
|
||||
// return rt;
|
||||
//}
|
||||
|
||||
|
||||
|
||||
// public static explicit operator Map<string, object>(ExpandoObject obj) => FromDynamic(obj);
|
||||
|
||||
public static Map<string, object> FromDynamic(ExpandoObject obj)
|
||||
{
|
||||
var rt = new Map<string, object>();
|
||||
foreach (var kv in obj)
|
||||
rt[kv.Key] = kv.Value;
|
||||
return rt;
|
||||
}
|
||||
|
||||
//public static Map<KT, VT> FromDictionary(Dictionary<KT, VT> dictionary)
|
||||
// => new Map<KT, VT>() { dic = dictionary };
|
||||
|
||||
//public Dictionary<KT, VT> ToDictionary() => dic;
|
||||
|
||||
public static Map<string,object> FromObject(object obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
|
||||
var st = new Map<string,object>();
|
||||
|
||||
var pi = type.GetTypeInfo().GetProperties().Where(x => x.CanRead);
|
||||
foreach (var p in pi)
|
||||
st[p.Name] = p.GetValue(obj);
|
||||
|
||||
var fi = type.GetTypeInfo().GetFields().Where(x => x.IsPublic);
|
||||
foreach (var f in fi)
|
||||
st[f.Name] = f.GetValue(obj);
|
||||
|
||||
return st;
|
||||
|
||||
|
||||
// if (obj is Structure)
|
||||
// return obj as Structure;
|
||||
// else //if (Codec.IsAnonymous(type))
|
||||
// {
|
||||
// var st = new Structure();
|
||||
|
||||
// var pi = type.GetTypeInfo().GetProperties().Where(x => x.CanRead);
|
||||
// foreach (var p in pi)
|
||||
// st[p.Name] = p.GetValue(obj);
|
||||
|
||||
// var fi = type.GetTypeInfo().GetFields().Where(x => x.IsPublic);
|
||||
// foreach (var f in fi)
|
||||
// st[f.Name] = f.GetValue(obj);
|
||||
|
||||
// return st;
|
||||
// }
|
||||
// //else
|
||||
// // return null;
|
||||
}
|
||||
|
||||
//public IEnumerator<KeyValuePair<KT, VT>> GetEnumerator()
|
||||
//{
|
||||
// return dic.GetEnumerator();
|
||||
//}
|
||||
|
||||
//IEnumerator IEnumerable.GetEnumerator()
|
||||
//{
|
||||
// return dic.GetEnumerator();
|
||||
//}
|
||||
|
||||
//public int Length
|
||||
//{
|
||||
// get { return dic.Count; }
|
||||
//}
|
||||
|
||||
//public KeyValuePair<KT, VT> At(int index)
|
||||
//{
|
||||
// return dic.ElementAt(index);
|
||||
//}
|
||||
|
||||
public object SyncRoot
|
||||
{
|
||||
get { return syncRoot; }
|
||||
}
|
||||
|
||||
//public KT[] GetKeys() => dic.Keys.ToArray();
|
||||
|
||||
//public void Add(KT key, VT value)
|
||||
//{
|
||||
// if (dic.ContainsKey(key))
|
||||
// dic[key] = value;
|
||||
// else
|
||||
// dic.Add(key, value);
|
||||
// }
|
||||
|
||||
public void Add(object key, object value)
|
||||
{
|
||||
base.Add((KT)key, (VT)value);
|
||||
}
|
||||
|
||||
//public void Remove(object key)
|
||||
//{
|
||||
// Remove((KT)key);
|
||||
//}
|
||||
|
||||
//public void Clear()
|
||||
//{
|
||||
// dic.Clear();
|
||||
//}
|
||||
|
||||
//public bool ContainsKey(object key)
|
||||
//{
|
||||
// return ContainsKey((KT)key);
|
||||
//}
|
||||
|
||||
public object[] Serialize()
|
||||
{
|
||||
var rt = new List<object>();
|
||||
foreach(var kv in this)
|
||||
{
|
||||
rt.Add(kv.Key);
|
||||
rt.Add(kv.Value);
|
||||
}
|
||||
|
||||
return rt.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable GetKeys() => Keys.ToArray();
|
||||
public IEnumerable GetValues() => Values.ToArray();
|
||||
|
||||
//public VT this[KT index]
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// if (dic.ContainsKey(index))
|
||||
// return dic[index];
|
||||
// else
|
||||
// return default;
|
||||
// }
|
||||
// set
|
||||
// {
|
||||
// if (dic.ContainsKey(index))
|
||||
// dic[index] = value;
|
||||
// else
|
||||
// dic.Add(index, value);
|
||||
// }
|
||||
//}
|
||||
|
||||
}
|
||||
35
Libraries/Esiur/Data/NotModified.cs
Normal file
35
Libraries/Esiur/Data/NotModified.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Data;
|
||||
public class NotModified
|
||||
{
|
||||
public static NotModified Default { get; set; } = new NotModified();
|
||||
}
|
||||
64
Libraries/Esiur/Data/NullabilityInfo.cs
Normal file
64
Libraries/Esiur/Data/NullabilityInfo.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that represents nullability info
|
||||
/// </summary>
|
||||
public sealed class NullabilityInfo
|
||||
{
|
||||
internal NullabilityInfo(Type type, NullabilityState readState, NullabilityState writeState,
|
||||
NullabilityInfo? elementType, NullabilityInfo[] typeArguments)
|
||||
{
|
||||
Type = type;
|
||||
ReadState = readState;
|
||||
WriteState = writeState;
|
||||
ElementType = elementType;
|
||||
GenericTypeArguments = typeArguments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="System.Type" /> of the member or generic parameter
|
||||
/// to which this NullabilityInfo belongs
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
/// <summary>
|
||||
/// The nullability read state of the member
|
||||
/// </summary>
|
||||
public NullabilityState ReadState { get; internal set; }
|
||||
/// <summary>
|
||||
/// The nullability write state of the member
|
||||
/// </summary>
|
||||
public NullabilityState WriteState { get; internal set; }
|
||||
/// <summary>
|
||||
/// If the member type is an array, gives the <see cref="NullabilityInfo" /> of the elements of the array, null otherwise
|
||||
/// </summary>
|
||||
public NullabilityInfo? ElementType { get; }
|
||||
/// <summary>
|
||||
/// If the member type is a generic type, gives the array of <see cref="NullabilityInfo" /> for each type parameter
|
||||
/// </summary>
|
||||
public NullabilityInfo[] GenericTypeArguments { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An enum that represents nullability state
|
||||
/// </summary>
|
||||
public enum NullabilityState
|
||||
{
|
||||
/// <summary>
|
||||
/// Nullability context not enabled (oblivious)
|
||||
/// </summary>
|
||||
Unknown,
|
||||
/// <summary>
|
||||
/// Non nullable value or reference type
|
||||
/// </summary>
|
||||
NotNull,
|
||||
/// <summary>
|
||||
/// Nullable value or reference type
|
||||
/// </summary>
|
||||
Nullable
|
||||
}
|
||||
}
|
||||
714
Libraries/Esiur/Data/NullabilityInfoContext.cs
Normal file
714
Libraries/Esiur/Data/NullabilityInfoContext.cs
Normal file
@@ -0,0 +1,714 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides APIs for populating nullability information/context from reflection members:
|
||||
/// <see cref="ParameterInfo"/>, <see cref="FieldInfo"/>, <see cref="PropertyInfo"/> and <see cref="EventInfo"/>.
|
||||
/// </summary>
|
||||
public sealed class NullabilityInfoContext
|
||||
{
|
||||
private const string CompilerServicesNameSpace = "System.Runtime.CompilerServices";
|
||||
private readonly Dictionary<Module, NotAnnotatedStatus> _publicOnlyModules = new();
|
||||
private readonly Dictionary<MemberInfo, NullabilityState> _context = new();
|
||||
|
||||
internal static bool IsSupported { get; } =
|
||||
AppContext.TryGetSwitch("System.Reflection.NullabilityInfoContext.IsSupported", out bool isSupported) ? isSupported : true;
|
||||
|
||||
[Flags]
|
||||
private enum NotAnnotatedStatus
|
||||
{
|
||||
None = 0x0, // no restriction, all members annotated
|
||||
Private = 0x1, // private members not annotated
|
||||
Internal = 0x2 // internal members not annotated
|
||||
}
|
||||
|
||||
private NullabilityState? GetNullableContext(MemberInfo? memberInfo)
|
||||
{
|
||||
while (memberInfo != null)
|
||||
{
|
||||
if (_context.TryGetValue(memberInfo, out NullabilityState state))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
foreach (CustomAttributeData attribute in memberInfo.GetCustomAttributesData())
|
||||
{
|
||||
if (attribute.AttributeType.Name == "NullableContextAttribute" &&
|
||||
attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
|
||||
attribute.ConstructorArguments.Count == 1)
|
||||
{
|
||||
state = TranslateByte(attribute.ConstructorArguments[0].Value);
|
||||
_context.Add(memberInfo, state);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
memberInfo = memberInfo.DeclaringType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates <see cref="NullabilityInfo" /> for the given <see cref="ParameterInfo" />.
|
||||
/// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
|
||||
/// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
|
||||
/// </summary>
|
||||
/// <param name="parameterInfo">The parameter which nullability info gets populated</param>
|
||||
/// <exception cref="ArgumentNullException">If the parameterInfo parameter is null</exception>
|
||||
/// <returns><see cref="NullabilityInfo" /></returns>
|
||||
public NullabilityInfo Create(ParameterInfo parameterInfo)
|
||||
{
|
||||
if (parameterInfo == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
EnsureIsSupported();
|
||||
|
||||
IList<CustomAttributeData> attributes = parameterInfo.GetCustomAttributesData();
|
||||
NullableAttributeStateParser parser = parameterInfo.Member is MethodBase method && IsPrivateOrInternalMethodAndAnnotationDisabled(method)
|
||||
? NullableAttributeStateParser.Unknown
|
||||
: CreateParser(attributes);
|
||||
NullabilityInfo nullability = GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, parser);
|
||||
|
||||
if (nullability.ReadState != NullabilityState.Unknown)
|
||||
{
|
||||
CheckParameterMetadataType(parameterInfo, nullability);
|
||||
}
|
||||
|
||||
CheckNullabilityAttributes(nullability, attributes);
|
||||
return nullability;
|
||||
}
|
||||
|
||||
private void CheckParameterMetadataType(ParameterInfo parameter, NullabilityInfo nullability)
|
||||
{
|
||||
if (parameter.Member is MethodInfo method)
|
||||
{
|
||||
MethodInfo metaMethod = GetMethodMetadataDefinition(method);
|
||||
ParameterInfo? metaParameter = null;
|
||||
if (string.IsNullOrEmpty(parameter.Name))
|
||||
{
|
||||
metaParameter = metaMethod.ReturnParameter;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParameterInfo[] parameters = metaMethod.GetParameters();
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (parameter.Position == i &&
|
||||
parameter.Name == parameters[i].Name)
|
||||
{
|
||||
metaParameter = parameters[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (metaParameter != null)
|
||||
{
|
||||
CheckGenericParameters(nullability, metaMethod, metaParameter.ParameterType, parameter.Member.ReflectedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodInfo GetMethodMetadataDefinition(MethodInfo method)
|
||||
{
|
||||
if (method.IsGenericMethod && !method.IsGenericMethodDefinition)
|
||||
{
|
||||
method = method.GetGenericMethodDefinition();
|
||||
}
|
||||
|
||||
return (MethodInfo)GetMemberMetadataDefinition(method);
|
||||
}
|
||||
|
||||
private static void CheckNullabilityAttributes(NullabilityInfo nullability, IList<CustomAttributeData> attributes)
|
||||
{
|
||||
var codeAnalysisReadState = NullabilityState.Unknown;
|
||||
var codeAnalysisWriteState = NullabilityState.Unknown;
|
||||
|
||||
foreach (CustomAttributeData attribute in attributes)
|
||||
{
|
||||
if (attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
|
||||
{
|
||||
if (attribute.AttributeType.Name == "NotNullAttribute")
|
||||
{
|
||||
codeAnalysisReadState = NullabilityState.NotNull;
|
||||
}
|
||||
else if ((attribute.AttributeType.Name == "MaybeNullAttribute" ||
|
||||
attribute.AttributeType.Name == "MaybeNullWhenAttribute") &&
|
||||
codeAnalysisReadState == NullabilityState.Unknown &&
|
||||
!IsValueTypeOrValueTypeByRef(nullability.Type))
|
||||
{
|
||||
codeAnalysisReadState = NullabilityState.Nullable;
|
||||
}
|
||||
else if (attribute.AttributeType.Name == "DisallowNullAttribute")
|
||||
{
|
||||
codeAnalysisWriteState = NullabilityState.NotNull;
|
||||
}
|
||||
else if (attribute.AttributeType.Name == "AllowNullAttribute" &&
|
||||
codeAnalysisWriteState == NullabilityState.Unknown &&
|
||||
!IsValueTypeOrValueTypeByRef(nullability.Type))
|
||||
{
|
||||
codeAnalysisWriteState = NullabilityState.Nullable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (codeAnalysisReadState != NullabilityState.Unknown)
|
||||
{
|
||||
nullability.ReadState = codeAnalysisReadState;
|
||||
}
|
||||
if (codeAnalysisWriteState != NullabilityState.Unknown)
|
||||
{
|
||||
nullability.WriteState = codeAnalysisWriteState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates <see cref="NullabilityInfo" /> for the given <see cref="PropertyInfo" />.
|
||||
/// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
|
||||
/// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The parameter which nullability info gets populated</param>
|
||||
/// <exception cref="ArgumentNullException">If the propertyInfo parameter is null</exception>
|
||||
/// <returns><see cref="NullabilityInfo" /></returns>
|
||||
public NullabilityInfo Create(PropertyInfo propertyInfo)
|
||||
{
|
||||
if (propertyInfo == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
EnsureIsSupported();
|
||||
|
||||
MethodInfo? getter = propertyInfo.GetGetMethod(true);
|
||||
MethodInfo? setter = propertyInfo.GetSetMethod(true);
|
||||
bool annotationsDisabled = (getter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(getter))
|
||||
&& (setter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(setter));
|
||||
NullableAttributeStateParser parser = annotationsDisabled ? NullableAttributeStateParser.Unknown : CreateParser(propertyInfo.GetCustomAttributesData());
|
||||
NullabilityInfo nullability = GetNullabilityInfo(propertyInfo, propertyInfo.PropertyType, parser);
|
||||
|
||||
if (getter != null)
|
||||
{
|
||||
CheckNullabilityAttributes(nullability, getter.ReturnParameter.GetCustomAttributesData());
|
||||
}
|
||||
else
|
||||
{
|
||||
nullability.ReadState = NullabilityState.Unknown;
|
||||
}
|
||||
|
||||
if (setter != null)
|
||||
{
|
||||
CheckNullabilityAttributes(nullability, setter.GetParameters().Last().GetCustomAttributesData());
|
||||
}
|
||||
else
|
||||
{
|
||||
nullability.WriteState = NullabilityState.Unknown;
|
||||
}
|
||||
|
||||
return nullability;
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// Populates <see cref="NullabilityInfo" /> for the given <see cref="MethodInfo" />.
|
||||
///// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
|
||||
///// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
|
||||
///// </summary>
|
||||
///// <param name="propertyInfo">The parameter which nullability info gets populated</param>
|
||||
///// <exception cref="ArgumentNullException">If the propertyInfo parameter is null</exception>
|
||||
///// <returns><see cref="NullabilityInfo" /></returns>
|
||||
//public NullabilityInfo Create(MethodInfo memberInfo)
|
||||
//{
|
||||
// if (memberInfo == null)
|
||||
// throw new ArgumentNullException();
|
||||
|
||||
// EnsureIsSupported();
|
||||
|
||||
|
||||
// bool annotationsDisabled = IsPrivateOrInternalMethodAndAnnotationDisabled(memberInfo);
|
||||
|
||||
// NullableAttributeStateParser parser = annotationsDisabled ? NullableAttributeStateParser.Unknown : CreateParser(memberInfo.GetCustomAttributesData());
|
||||
// NullabilityInfo nullability = GetNullabilityInfo(memberInfo, memberInfo.ReturnType, parser);
|
||||
|
||||
|
||||
// CheckNullabilityAttributes(nullability, memberInfo.ReturnParameter.GetCustomAttributesData());
|
||||
|
||||
// return nullability;
|
||||
//}
|
||||
|
||||
private bool IsPrivateOrInternalMethodAndAnnotationDisabled(MethodBase method)
|
||||
{
|
||||
if ((method.IsPrivate || method.IsFamilyAndAssembly || method.IsAssembly) &&
|
||||
IsPublicOnly(method.IsPrivate, method.IsFamilyAndAssembly, method.IsAssembly, method.Module))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates <see cref="NullabilityInfo" /> for the given <see cref="EventInfo" />.
|
||||
/// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
|
||||
/// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
|
||||
/// </summary>
|
||||
/// <param name="eventInfo">The parameter which nullability info gets populated</param>
|
||||
/// <exception cref="ArgumentNullException">If the eventInfo parameter is null</exception>
|
||||
/// <returns><see cref="NullabilityInfo" /></returns>
|
||||
public NullabilityInfo Create(EventInfo eventInfo)
|
||||
{
|
||||
if (eventInfo == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
EnsureIsSupported();
|
||||
|
||||
return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, CreateParser(eventInfo.GetCustomAttributesData()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates <see cref="NullabilityInfo" /> for the given <see cref="FieldInfo" />
|
||||
/// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
|
||||
/// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
|
||||
/// </summary>
|
||||
/// <param name="fieldInfo">The parameter which nullability info gets populated</param>
|
||||
/// <exception cref="ArgumentNullException">If the fieldInfo parameter is null</exception>
|
||||
/// <returns><see cref="NullabilityInfo" /></returns>
|
||||
public NullabilityInfo Create(FieldInfo fieldInfo)
|
||||
{
|
||||
if (fieldInfo == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
EnsureIsSupported();
|
||||
|
||||
IList<CustomAttributeData> attributes = fieldInfo.GetCustomAttributesData();
|
||||
NullableAttributeStateParser parser = IsPrivateOrInternalFieldAndAnnotationDisabled(fieldInfo) ? NullableAttributeStateParser.Unknown : CreateParser(attributes);
|
||||
NullabilityInfo nullability = GetNullabilityInfo(fieldInfo, fieldInfo.FieldType, parser);
|
||||
CheckNullabilityAttributes(nullability, attributes);
|
||||
return nullability;
|
||||
}
|
||||
|
||||
private static void EnsureIsSupported()
|
||||
{
|
||||
if (!IsSupported)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPrivateOrInternalFieldAndAnnotationDisabled(FieldInfo fieldInfo)
|
||||
{
|
||||
if ((fieldInfo.IsPrivate || fieldInfo.IsFamilyAndAssembly || fieldInfo.IsAssembly) &&
|
||||
IsPublicOnly(fieldInfo.IsPrivate, fieldInfo.IsFamilyAndAssembly, fieldInfo.IsAssembly, fieldInfo.Module))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsPublicOnly(bool isPrivate, bool isFamilyAndAssembly, bool isAssembly, Module module)
|
||||
{
|
||||
if (!_publicOnlyModules.TryGetValue(module, out NotAnnotatedStatus value))
|
||||
{
|
||||
value = PopulateAnnotationInfo(module.GetCustomAttributesData());
|
||||
_publicOnlyModules.Add(module, value);
|
||||
}
|
||||
|
||||
if (value == NotAnnotatedStatus.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((isPrivate || isFamilyAndAssembly) && value.HasFlag(NotAnnotatedStatus.Private) ||
|
||||
isAssembly && value.HasFlag(NotAnnotatedStatus.Internal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static NotAnnotatedStatus PopulateAnnotationInfo(IList<CustomAttributeData> customAttributes)
|
||||
{
|
||||
foreach (CustomAttributeData attribute in customAttributes)
|
||||
{
|
||||
if (attribute.AttributeType.Name == "NullablePublicOnlyAttribute" &&
|
||||
attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
|
||||
attribute.ConstructorArguments.Count == 1)
|
||||
{
|
||||
if (attribute.ConstructorArguments[0].Value is bool boolValue && boolValue)
|
||||
{
|
||||
return NotAnnotatedStatus.Internal | NotAnnotatedStatus.Private;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotAnnotatedStatus.Private;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NotAnnotatedStatus.None;
|
||||
}
|
||||
|
||||
private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser)
|
||||
{
|
||||
int index = 0;
|
||||
NullabilityInfo nullability = GetNullabilityInfo(memberInfo, type, parser, ref index);
|
||||
|
||||
if (nullability.ReadState != NullabilityState.Unknown)
|
||||
{
|
||||
TryLoadGenericMetaTypeNullability(memberInfo, nullability);
|
||||
}
|
||||
|
||||
return nullability;
|
||||
}
|
||||
|
||||
private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser, ref int index)
|
||||
{
|
||||
NullabilityState state = NullabilityState.Unknown;
|
||||
NullabilityInfo? elementState = null;
|
||||
NullabilityInfo[] genericArgumentsState = Array.Empty<NullabilityInfo>();
|
||||
Type underlyingType = type;
|
||||
|
||||
if (underlyingType.IsByRef || underlyingType.IsPointer)
|
||||
{
|
||||
underlyingType = underlyingType.GetElementType()!;
|
||||
}
|
||||
|
||||
if (underlyingType.IsValueType)
|
||||
{
|
||||
if (Nullable.GetUnderlyingType(underlyingType) is { } nullableUnderlyingType)
|
||||
{
|
||||
underlyingType = nullableUnderlyingType;
|
||||
state = NullabilityState.Nullable;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = NullabilityState.NotNull;
|
||||
}
|
||||
|
||||
if (underlyingType.IsGenericType)
|
||||
{
|
||||
++index;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!parser.ParseNullableState(index++, ref state)
|
||||
&& GetNullableContext(memberInfo) is { } contextState)
|
||||
{
|
||||
state = contextState;
|
||||
}
|
||||
|
||||
if (underlyingType.IsArray)
|
||||
{
|
||||
elementState = GetNullabilityInfo(memberInfo, underlyingType.GetElementType()!, parser, ref index);
|
||||
}
|
||||
}
|
||||
|
||||
if (underlyingType.IsGenericType)
|
||||
{
|
||||
Type[] genericArguments = underlyingType.GetGenericArguments();
|
||||
genericArgumentsState = new NullabilityInfo[genericArguments.Length];
|
||||
|
||||
for (int i = 0; i < genericArguments.Length; i++)
|
||||
{
|
||||
genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], parser, ref index);
|
||||
}
|
||||
}
|
||||
|
||||
return new NullabilityInfo(type, state, state, elementState, genericArgumentsState);
|
||||
}
|
||||
|
||||
private static NullableAttributeStateParser CreateParser(IList<CustomAttributeData> customAttributes)
|
||||
{
|
||||
foreach (CustomAttributeData attribute in customAttributes)
|
||||
{
|
||||
if (attribute.AttributeType.Name == "NullableAttribute" &&
|
||||
attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
|
||||
attribute.ConstructorArguments.Count == 1)
|
||||
{
|
||||
return new NullableAttributeStateParser(attribute.ConstructorArguments[0].Value);
|
||||
}
|
||||
}
|
||||
|
||||
return new NullableAttributeStateParser(null);
|
||||
}
|
||||
|
||||
private void TryLoadGenericMetaTypeNullability(MemberInfo memberInfo, NullabilityInfo nullability)
|
||||
{
|
||||
MemberInfo? metaMember = GetMemberMetadataDefinition(memberInfo);
|
||||
Type? metaType = null;
|
||||
if (metaMember is FieldInfo field)
|
||||
{
|
||||
metaType = field.FieldType;
|
||||
}
|
||||
else if (metaMember is PropertyInfo property)
|
||||
{
|
||||
metaType = GetPropertyMetaType(property);
|
||||
}
|
||||
|
||||
if (metaType != null)
|
||||
{
|
||||
CheckGenericParameters(nullability, metaMember!, metaType, memberInfo.ReflectedType);
|
||||
}
|
||||
}
|
||||
|
||||
private static MemberInfo GetMemberMetadataDefinition(MemberInfo member)
|
||||
{
|
||||
Type? type = member.DeclaringType;
|
||||
if ((type != null) && type.IsGenericType && !type.IsGenericTypeDef)
|
||||
{
|
||||
return GetMemberWithSameMetadataDefinitionAs(type.GetGenericTypeDef(), member);
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
static bool HasSameMetadataDefinitionAs(MemberInfo mi, MemberInfo other) { throw new NotImplementedException(); }
|
||||
|
||||
static MemberInfo GetMemberWithSameMetadataDefinitionAs(Type type, MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
const BindingFlags all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
|
||||
foreach (MemberInfo myMemberInfo in type.GetMembers(all))
|
||||
{
|
||||
if (HasSameMetadataDefinitionAs(myMemberInfo, member))
|
||||
{
|
||||
return myMemberInfo;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
private static Type GetPropertyMetaType(PropertyInfo property)
|
||||
{
|
||||
if (property.GetGetMethod(true) is MethodInfo method)
|
||||
{
|
||||
return method.ReturnType;
|
||||
}
|
||||
|
||||
return property.GetSetMethod(true)!.GetParameters()[0].ParameterType;
|
||||
}
|
||||
|
||||
private void CheckGenericParameters(NullabilityInfo nullability, MemberInfo metaMember, Type metaType, Type? reflectedType)
|
||||
{
|
||||
if (metaType.IsGenericParameter)
|
||||
{
|
||||
if (nullability.ReadState == NullabilityState.NotNull)
|
||||
{
|
||||
TryUpdateGenericParameterNullability(nullability, metaType, reflectedType);
|
||||
}
|
||||
}
|
||||
else if (metaType.ContainsGenericParameters)
|
||||
{
|
||||
if (nullability.GenericTypeArguments.Length > 0)
|
||||
{
|
||||
Type[] genericArguments = metaType.GetGenericArguments();
|
||||
|
||||
for (int i = 0; i < genericArguments.Length; i++)
|
||||
{
|
||||
CheckGenericParameters(nullability.GenericTypeArguments[i], metaMember, genericArguments[i], reflectedType);
|
||||
}
|
||||
}
|
||||
else if (nullability.ElementType is { } elementNullability && metaType.IsArray)
|
||||
{
|
||||
CheckGenericParameters(elementNullability, metaMember, metaType.GetElementType()!, reflectedType);
|
||||
}
|
||||
// We could also follow this branch for metaType.IsPointer, but since pointers must be unmanaged this
|
||||
// will be a no-op regardless
|
||||
else if (metaType.IsByRef)
|
||||
{
|
||||
CheckGenericParameters(nullability, metaMember, metaType.GetElementType()!, reflectedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryUpdateGenericParameterNullability(NullabilityInfo nullability, Type genericParameter, Type? reflectedType)
|
||||
{
|
||||
Debug.Assert(genericParameter.IsGenericParameter);
|
||||
|
||||
if (reflectedType is not null
|
||||
&& !IsGenericMethodParameter(genericParameter)
|
||||
&& TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, reflectedType, reflectedType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsValueTypeOrValueTypeByRef(nullability.Type))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var state = NullabilityState.Unknown;
|
||||
if (CreateParser(genericParameter.GetCustomAttributesData()).ParseNullableState(0, ref state))
|
||||
{
|
||||
nullability.ReadState = state;
|
||||
nullability.WriteState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GetNullableContext(genericParameter) is { } contextState)
|
||||
{
|
||||
nullability.ReadState = contextState;
|
||||
nullability.WriteState = contextState;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsGenericMethodParameter(Type genericParameter) => genericParameter.IsGenericParameter && genericParameter.DeclaringMethod != null;
|
||||
|
||||
private bool TryUpdateGenericTypeParameterNullabilityFromReflectedType(NullabilityInfo nullability, Type genericParameter, Type context, Type reflectedType)
|
||||
{
|
||||
Debug.Assert(genericParameter.IsGenericParameter && !IsGenericMethodParameter(genericParameter));
|
||||
|
||||
Type contextTypeDef = context.IsGenericType && !context.IsGenericTypeDef ? context.GetGenericTypeDef() : context;
|
||||
if (genericParameter.DeclaringType == contextTypeDef)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type? baseType = contextTypeDef.BaseType;
|
||||
if (baseType is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!baseType.IsGenericType
|
||||
|| (baseType.IsGenericTypeDef ? baseType : baseType.GetGenericTypeDef()) != genericParameter.DeclaringType)
|
||||
{
|
||||
return TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, baseType, reflectedType);
|
||||
}
|
||||
|
||||
Type[] genericArguments = baseType.GetGenericArguments();
|
||||
Type genericArgument = genericArguments[genericParameter.GenericParameterPosition];
|
||||
if (genericArgument.IsGenericParameter)
|
||||
{
|
||||
return TryUpdateGenericParameterNullability(nullability, genericArgument, reflectedType);
|
||||
}
|
||||
|
||||
NullableAttributeStateParser parser = CreateParser(contextTypeDef.GetCustomAttributesData());
|
||||
int nullabilityStateIndex = 1; // start at 1 since index 0 is the type itself
|
||||
for (int i = 0; i < genericParameter.GenericParameterPosition; i++)
|
||||
{
|
||||
nullabilityStateIndex += CountNullabilityStates(genericArguments[i]);
|
||||
}
|
||||
return TryPopulateNullabilityInfo(nullability, parser, ref nullabilityStateIndex);
|
||||
|
||||
static int CountNullabilityStates(Type type)
|
||||
{
|
||||
Type underlyingType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
if (underlyingType.IsGenericType)
|
||||
{
|
||||
int count = 1;
|
||||
foreach (Type genericArgument in underlyingType.GetGenericArguments())
|
||||
{
|
||||
count += CountNullabilityStates(genericArgument);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
if (underlyingType.HasElementType)
|
||||
{
|
||||
return (underlyingType.IsArray ? 1 : 0) + CountNullabilityStates(underlyingType.GetElementType()!);
|
||||
}
|
||||
|
||||
return type.IsValueType ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryPopulateNullabilityInfo(NullabilityInfo nullability, NullableAttributeStateParser parser, ref int index)
|
||||
{
|
||||
bool isValueType = IsValueTypeOrValueTypeByRef(nullability.Type);
|
||||
if (!isValueType)
|
||||
{
|
||||
var state = NullabilityState.Unknown;
|
||||
if (!parser.ParseNullableState(index, ref state))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
nullability.ReadState = state;
|
||||
nullability.WriteState = state;
|
||||
}
|
||||
|
||||
if (!isValueType || (Nullable.GetUnderlyingType(nullability.Type) ?? nullability.Type).IsGenericType)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (nullability.GenericTypeArguments.Length > 0)
|
||||
{
|
||||
foreach (NullabilityInfo genericTypeArgumentNullability in nullability.GenericTypeArguments)
|
||||
{
|
||||
TryPopulateNullabilityInfo(genericTypeArgumentNullability, parser, ref index);
|
||||
}
|
||||
}
|
||||
else if (nullability.ElementType is { } elementTypeNullability)
|
||||
{
|
||||
TryPopulateNullabilityInfo(elementTypeNullability, parser, ref index);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static NullabilityState TranslateByte(object? value)
|
||||
{
|
||||
return value is byte b ? TranslateByte(b) : NullabilityState.Unknown;
|
||||
}
|
||||
|
||||
private static NullabilityState TranslateByte(byte b) =>
|
||||
b switch
|
||||
{
|
||||
1 => NullabilityState.NotNull,
|
||||
2 => NullabilityState.Nullable,
|
||||
_ => NullabilityState.Unknown
|
||||
};
|
||||
|
||||
private static bool IsValueTypeOrValueTypeByRef(Type type) =>
|
||||
type.IsValueType || ((type.IsByRef || type.IsPointer) && type.GetElementType()!.IsValueType);
|
||||
|
||||
private readonly struct NullableAttributeStateParser
|
||||
{
|
||||
private static readonly object UnknownByte = (byte)0;
|
||||
|
||||
private readonly object? _nullableAttributeArgument;
|
||||
|
||||
public NullableAttributeStateParser(object? nullableAttributeArgument)
|
||||
{
|
||||
this._nullableAttributeArgument = nullableAttributeArgument;
|
||||
}
|
||||
|
||||
public static NullableAttributeStateParser Unknown => new(UnknownByte);
|
||||
|
||||
public bool ParseNullableState(int index, ref NullabilityState state)
|
||||
{
|
||||
switch (this._nullableAttributeArgument)
|
||||
{
|
||||
case byte b:
|
||||
state = TranslateByte(b);
|
||||
return true;
|
||||
case ReadOnlyCollection<CustomAttributeTypedArgument> args
|
||||
when index < args.Count && args[index].Value is byte elementB:
|
||||
state = TranslateByte(elementB);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Libraries/Esiur/Data/NullableAttribute.cs
Normal file
23
Libraries/Esiur/Data/NullableAttribute.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, Inherited = false)]
|
||||
public sealed class NullableAttribute : Attribute
|
||||
{
|
||||
/// <summary>Flags specifying metadata related to nullable reference types.</summary>
|
||||
public readonly byte[] NullableFlags;
|
||||
|
||||
/// <summary>Initializes the attribute.</summary>
|
||||
/// <param name="value">The flags value.</param>
|
||||
public NullableAttribute(byte value)
|
||||
{
|
||||
NullableFlags = new[] { value };
|
||||
}
|
||||
|
||||
/// <summary>Initializes the attribute.</summary>
|
||||
/// <param name="value">The flags value.</param>
|
||||
public NullableAttribute(byte[] value)
|
||||
{
|
||||
NullableFlags = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Libraries/Esiur/Data/NullableContextAttribute.cs
Normal file
16
Libraries/Esiur/Data/NullableContextAttribute.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
|
||||
public sealed class NullableContextAttribute : Attribute
|
||||
{
|
||||
/// <summary>Flag specifying metadata related to nullable reference types.</summary>
|
||||
public readonly byte Flag;
|
||||
|
||||
/// <summary>Initializes the attribute.</summary>
|
||||
/// <param name="value">The flag value.</param>
|
||||
public NullableContextAttribute(byte value)
|
||||
{
|
||||
Flag = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
149
Libraries/Esiur/Data/ParsedTdu.cs
Normal file
149
Libraries/Esiur/Data/ParsedTdu.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public struct ParsedTdu
|
||||
{
|
||||
public TduIdentifier Identifier;
|
||||
public int Index;
|
||||
public TduClass Class;
|
||||
public uint Offset;
|
||||
public ulong ContentLength;
|
||||
public byte[] Data;
|
||||
public byte Exponent;
|
||||
public ulong TotalLength;
|
||||
public byte[] Metadata;
|
||||
public uint Ends;
|
||||
|
||||
public static ParsedTdu Parse(byte[] data, uint offset, uint ends)
|
||||
{
|
||||
|
||||
var h = data[offset++];
|
||||
|
||||
var cls = (TduClass)(h >> 6);
|
||||
|
||||
if (cls == TduClass.Fixed)
|
||||
{
|
||||
var exp = (h & 0x38) >> 3;
|
||||
|
||||
if (exp == 0)
|
||||
return new ParsedTdu()
|
||||
{
|
||||
Identifier = (TduIdentifier)h,
|
||||
Data = data,
|
||||
Offset = offset,
|
||||
Class = cls,
|
||||
Exponent = (byte)exp,
|
||||
Index = (byte)h & 0x7,
|
||||
ContentLength = 0,
|
||||
TotalLength = 1,
|
||||
Ends = ends
|
||||
};
|
||||
|
||||
ulong cl = (ulong)(1 << (exp - 1));
|
||||
|
||||
if (ends - offset < cl)
|
||||
return new ParsedTdu()
|
||||
{
|
||||
Class = TduClass.Invalid,
|
||||
TotalLength = (cl - (ends - offset))
|
||||
};
|
||||
|
||||
//offset += (uint)cl;
|
||||
|
||||
return new ParsedTdu()
|
||||
{
|
||||
Identifier = (TduIdentifier)h,
|
||||
Data = data,
|
||||
Offset = offset,
|
||||
Class = cls,
|
||||
ContentLength = cl,
|
||||
TotalLength = 1 + cl,
|
||||
Exponent = (byte)exp,
|
||||
Index = (byte)h & 0x7,
|
||||
Ends = ends
|
||||
};
|
||||
}
|
||||
else if (cls == TduClass.Typed)
|
||||
{
|
||||
ulong cll = (ulong)(h >> 3) & 0x7;
|
||||
|
||||
if (ends - offset < cll)
|
||||
return new ParsedTdu()
|
||||
{
|
||||
Class = TduClass.Invalid,
|
||||
TotalLength = (cll - (ends - offset))
|
||||
};
|
||||
|
||||
ulong cl = 0;
|
||||
|
||||
for (uint i = 0; i < cll; i++)
|
||||
cl = cl << 8 | data[offset++];
|
||||
|
||||
if (ends - offset < cl)
|
||||
return new ParsedTdu()
|
||||
{
|
||||
TotalLength = (cl - (ends - offset)),
|
||||
Class = TduClass.Invalid,
|
||||
};
|
||||
|
||||
var metaData = DC.Clip(data, offset + 1, data[offset]);
|
||||
offset += data[offset] + (uint)1;
|
||||
|
||||
|
||||
return new ParsedTdu()
|
||||
{
|
||||
Identifier = (TduIdentifier)(h & 0xC7),
|
||||
Data = data,
|
||||
Offset = offset,
|
||||
Class = cls,
|
||||
ContentLength = cl - 1 - (uint)metaData.Length,
|
||||
TotalLength = 1 + cl + cll,
|
||||
Index = (byte)h & 0x7,
|
||||
Metadata = metaData,
|
||||
Ends = ends
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong cll = (ulong)(h >> 3) & 0x7;
|
||||
|
||||
if (ends - offset < cll)
|
||||
return new ParsedTdu()
|
||||
{
|
||||
Class = TduClass.Invalid,
|
||||
TotalLength = (cll - (ends - offset))
|
||||
};
|
||||
|
||||
ulong cl = 0;
|
||||
|
||||
for (uint i = 0; i < cll; i++)
|
||||
cl = cl << 8 | data[offset++];
|
||||
|
||||
if (ends - offset < cl)
|
||||
return new ParsedTdu()
|
||||
{
|
||||
Class = TduClass.Invalid,
|
||||
TotalLength = (cl - (ends - offset))
|
||||
};
|
||||
|
||||
|
||||
return
|
||||
new ParsedTdu()
|
||||
{
|
||||
Identifier = (TduIdentifier)(h & 0xC7),
|
||||
Data = data,
|
||||
Offset = offset,
|
||||
Class = cls,
|
||||
ContentLength = cl,
|
||||
TotalLength = 1 + cl + cll,
|
||||
Index = (byte)h & 0x7,
|
||||
Ends = ends
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
34
Libraries/Esiur/Data/PropertyValue.cs
Normal file
34
Libraries/Esiur/Data/PropertyValue.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
public class PropertyValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or set the value.
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
/// <summary>
|
||||
/// Get or set date of modification or occurrence.
|
||||
/// </summary>
|
||||
public DateTime? Date { get; set; }
|
||||
/// <summary>
|
||||
/// Get or set property age.
|
||||
/// </summary>
|
||||
public ulong? Age { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of PropertyValue.
|
||||
/// </summary>
|
||||
/// <param name="value">Value.</param>
|
||||
/// <param name="age">Age.</param>
|
||||
/// <param name="date">Date.</param>
|
||||
public PropertyValue(object value, ulong? age, DateTime? date)
|
||||
{
|
||||
Value = value;
|
||||
Age = age;
|
||||
Date = date;
|
||||
}
|
||||
}
|
||||
9
Libraries/Esiur/Data/Record.cs
Normal file
9
Libraries/Esiur/Data/Record.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data;
|
||||
public class Record : KeyList<string, object>, IRecord
|
||||
{
|
||||
|
||||
}
|
||||
12
Libraries/Esiur/Data/ResourceArrayType.cs
Normal file
12
Libraries/Esiur/Data/ResourceArrayType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
public enum ResourceArrayType
|
||||
{
|
||||
Dynamic = 0x0,
|
||||
Static = 0x10,
|
||||
Wrapper = 0x20,
|
||||
}
|
||||
18
Libraries/Esiur/Data/ResourceId.cs
Normal file
18
Libraries/Esiur/Data/ResourceId.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public struct ResourceId
|
||||
{
|
||||
public bool Local;
|
||||
public uint Id;
|
||||
|
||||
public ResourceId(bool local, uint id)
|
||||
{
|
||||
this.Id = id;
|
||||
this.Local = local;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Libraries/Esiur/Data/ResourceJsonConverter.cs
Normal file
98
Libraries/Esiur/Data/ResourceJsonConverter.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2021 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
class ResourceJsonConverter : JsonConverter<IResource>
|
||||
{
|
||||
public override IResource Read(
|
||||
ref Utf8JsonReader reader,
|
||||
Type typeToConvert,
|
||||
JsonSerializerOptions options)
|
||||
{
|
||||
return (IResource)JsonSerializer.Deserialize(ref reader, typeof(IResource), options);
|
||||
}
|
||||
|
||||
|
||||
public override void Write(
|
||||
Utf8JsonWriter writer,
|
||||
IResource resource,
|
||||
JsonSerializerOptions options)
|
||||
{
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
foreach (var pt in resource.Instance.Definition.Properties)
|
||||
{
|
||||
var rt = pt.PropertyInfo.GetValue(resource, null);
|
||||
if (rt != null && rt.GetType().IsGenericType)
|
||||
continue;
|
||||
|
||||
writer.WritePropertyName(options.PropertyNamingPolicy?.ConvertName(pt.Name) ?? pt.Name);
|
||||
|
||||
if (rt is IResource)
|
||||
JsonSerializer.Serialize(writer, (IResource)rt, options);
|
||||
else
|
||||
JsonSerializer.Serialize(writer, rt, options);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class DoubleJsonConverter : JsonConverter<double>
|
||||
{
|
||||
public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String && reader.GetString() == "NaN")
|
||||
{
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
return reader.GetDouble(); // JsonException thrown if reader.TokenType != JsonTokenType.Number
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
|
||||
{
|
||||
if (double.IsNaN(value))
|
||||
{
|
||||
writer.WriteStringValue("NaN");
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNumberValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
Libraries/Esiur/Data/ResourceLink.cs
Normal file
27
Libraries/Esiur/Data/ResourceLink.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public class ResourceLink
|
||||
{
|
||||
readonly string value;
|
||||
|
||||
public ResourceLink(string value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
public static implicit operator string(ResourceLink d)
|
||||
{
|
||||
return d.value;
|
||||
}
|
||||
public static implicit operator ResourceLink(string d)
|
||||
{
|
||||
return new ResourceLink(d);
|
||||
}
|
||||
|
||||
public override string ToString() => value;
|
||||
|
||||
}
|
||||
}
|
||||
27
Libraries/Esiur/Data/ResourceLinkGeneric.cs
Normal file
27
Libraries/Esiur/Data/ResourceLinkGeneric.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public class ResourceLink<T>
|
||||
{
|
||||
readonly string value;
|
||||
|
||||
public ResourceLink(string value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
public static implicit operator string(ResourceLink<T> d)
|
||||
{
|
||||
return d.value;
|
||||
}
|
||||
public static implicit operator ResourceLink<T>(string d)
|
||||
{
|
||||
return new ResourceLink<T>(d);
|
||||
}
|
||||
|
||||
public override string ToString() => value;
|
||||
|
||||
}
|
||||
}
|
||||
273
Libraries/Esiur/Data/ResourceList.cs
Normal file
273
Libraries/Esiur/Data/ResourceList.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2020 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using Esiur.Core;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
public class ResourceList<T, ST> : IEnumerable<T>, ICollection, ICollection<T>
|
||||
{
|
||||
|
||||
private readonly object syncRoot = new object();
|
||||
private List<T> list = new List<T>();
|
||||
|
||||
public delegate void Modified(ST sender, int index, T oldValue, T newValue);
|
||||
public delegate void Added(ST sender, T value);
|
||||
public delegate void Removed(ST sender, int index, T value);
|
||||
public delegate void Cleared(ST sender);
|
||||
|
||||
|
||||
public event Modified OnModified;
|
||||
public event Removed OnRemoved;
|
||||
public event Cleared OnCleared;
|
||||
public event Added OnAdd;
|
||||
|
||||
ST state;
|
||||
|
||||
public void Sort()
|
||||
{
|
||||
list.Sort();
|
||||
}
|
||||
|
||||
public void Sort(IComparer<T> comparer)
|
||||
{
|
||||
list.Sort(comparer);
|
||||
}
|
||||
|
||||
public void Sort(Comparison<T> comparison)
|
||||
{
|
||||
list.Sort(comparison);
|
||||
}
|
||||
|
||||
public IEnumerable<T> Where(Func<T, bool> predicate)
|
||||
{
|
||||
return list.Where(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert AutoList to array
|
||||
/// </summary>
|
||||
/// <returns>Array</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
// list.OrderBy()
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of AutoList
|
||||
/// </summary>
|
||||
/// <param name="state">State object to be included when an event is raised.</param>
|
||||
public ResourceList(ST state)
|
||||
{
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of AutoList
|
||||
/// </summary>
|
||||
/// <param name="values">Populate the list with items</param>
|
||||
/// <returns></returns>
|
||||
public ResourceList(ST state, T[] values)
|
||||
{
|
||||
this.state = state;
|
||||
AddRange(values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronization lock of the list
|
||||
/// </summary>
|
||||
public object SyncRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
return syncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First item in the list
|
||||
/// </summary>
|
||||
public T First()
|
||||
{
|
||||
return list.First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an item at a specified index
|
||||
/// </summary>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return list[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
var oldValue = list[index];
|
||||
|
||||
lock (syncRoot)
|
||||
list[index] = value;
|
||||
|
||||
OnModified?.Invoke(state, index, oldValue, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add item to the list
|
||||
/// </summary>
|
||||
public void Add(T value)
|
||||
{
|
||||
lock (syncRoot)
|
||||
list.Add(value);
|
||||
|
||||
OnAdd?.Invoke(state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an array of items to the list
|
||||
/// </summary>
|
||||
public void AddRange(T[] values)
|
||||
{
|
||||
foreach (var v in values)
|
||||
Add(v);
|
||||
}
|
||||
|
||||
private void ItemDestroyed(object sender)
|
||||
{
|
||||
Remove((T)sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the list
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
|
||||
lock (syncRoot)
|
||||
list.Clear();
|
||||
|
||||
OnCleared?.Invoke(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove an item from the list
|
||||
/// <param name="value">Item to remove</param>
|
||||
/// </summary>
|
||||
public void Remove(T value)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
lock (syncRoot)
|
||||
{
|
||||
index = list.IndexOf(value);
|
||||
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
list.RemoveAt(index);
|
||||
|
||||
|
||||
}
|
||||
|
||||
OnRemoved?.Invoke(state, index, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of items in the list
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get { return list.Count; }
|
||||
}
|
||||
|
||||
public bool IsSynchronized => (list as ICollection).IsSynchronized;
|
||||
|
||||
public bool IsReadOnly => throw new NotImplementedException();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if an item exists in the list
|
||||
/// </summary>
|
||||
/// <param name="value">Item to check if exists</param>
|
||||
public bool Contains(T value)
|
||||
{
|
||||
return list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any item of the given array is in the list
|
||||
/// </summary>
|
||||
/// <param name="values">Array of items</param>
|
||||
public bool ContainsAny(T[] values)
|
||||
{
|
||||
foreach (var v in values)
|
||||
if (list.Contains(v))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any item of the given list is in the list
|
||||
/// </summary>
|
||||
/// <param name="values">List of items</param>
|
||||
public bool ContainsAny(AutoList<T, ST> values)
|
||||
{
|
||||
foreach (var v in values)
|
||||
if (list.Contains((T)v))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)list).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)list).GetEnumerator();
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
(list as ICollection).CopyTo(array, index);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
return list.Remove(item);
|
||||
}
|
||||
}
|
||||
560
Libraries/Esiur/Data/RuntimeCaster.cs
Normal file
560
Libraries/Esiur/Data/RuntimeCaster.cs
Normal file
@@ -0,0 +1,560 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
// --- Policies & Options (NET Standard 2.0 compatible) --------------------
|
||||
|
||||
public enum NaNInfinityPolicy
|
||||
{
|
||||
Throw,
|
||||
NullIfNullable,
|
||||
CoerceZero
|
||||
}
|
||||
|
||||
public sealed class RuntimeCastOptions
|
||||
{
|
||||
// Reusable default to avoid per-call allocations
|
||||
public static readonly RuntimeCastOptions Default = new RuntimeCastOptions();
|
||||
|
||||
public bool CheckedNumeric { get; set; } = true;
|
||||
|
||||
public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture;
|
||||
|
||||
public DateTimeStyles DateTimeStyles { get; set; } =
|
||||
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal;
|
||||
|
||||
public bool EnumIgnoreCase { get; set; } = true;
|
||||
public bool EnumMustBeDefined { get; set; } = false;
|
||||
|
||||
public NaNInfinityPolicy NaNInfinityPolicy { get; set; } = NaNInfinityPolicy.Throw;
|
||||
}
|
||||
|
||||
// --- Core Caster ----------------------------------------------------------
|
||||
|
||||
public static class RuntimeCaster
|
||||
{
|
||||
// (fromType, toType) -> converter(value, options)
|
||||
private static readonly ConcurrentDictionary<(Type from, Type to),
|
||||
Func<object?, RuntimeCastOptions, object?>> _cache =
|
||||
new ConcurrentDictionary<(Type, Type), Func<object?, RuntimeCastOptions, object?>>();
|
||||
|
||||
// Numeric-only compiled converters
|
||||
private static readonly ConcurrentDictionary<(Type from, Type to, bool @checked),
|
||||
Func<object, object>> _numericCache =
|
||||
new ConcurrentDictionary<(Type, Type, bool), Func<object, object>>();
|
||||
|
||||
// Per-element converters for collections
|
||||
private static readonly ConcurrentDictionary<(Type from, Type to),
|
||||
Func<object?, RuntimeCastOptions, object?>> _elemConvCache =
|
||||
new ConcurrentDictionary<(Type, Type), Func<object?, RuntimeCastOptions, object?>>();
|
||||
|
||||
// --------- Zero-allocation convenience overloads ---------
|
||||
public static object? Cast(object? value, Type toType)
|
||||
=> Cast(value, toType, RuntimeCastOptions.Default);
|
||||
|
||||
public static object? CastSequence(object? value, Type toType)
|
||||
=> CastSequence(value, toType, RuntimeCastOptions.Default);
|
||||
|
||||
// --------- Main API (options accepted if you want different policies) ---------
|
||||
public static object? Cast(object? value, Type toType, RuntimeCastOptions? options)
|
||||
{
|
||||
if (toType == null) throw new ArgumentNullException(nameof(toType));
|
||||
var opts = options ?? RuntimeCastOptions.Default;
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
if (IsNonNullableValueType(toType))
|
||||
throw new InvalidCastException("Cannot cast null to non-nullable " + toType + ".");
|
||||
return null;
|
||||
}
|
||||
|
||||
var fromType = value.GetType();
|
||||
if (toType.IsAssignableFrom(fromType)) return value;
|
||||
|
||||
var fn = _cache.GetOrAdd((fromType, toType), k => BuildConverter(k.from, k.to));
|
||||
return fn(value, opts);
|
||||
}
|
||||
|
||||
public static object? CastSequence(object? value, Type toType, RuntimeCastOptions? options)
|
||||
{
|
||||
var opts = options ?? RuntimeCastOptions.Default;
|
||||
if (value == null) return null;
|
||||
|
||||
var fromType = value.GetType();
|
||||
var toUnderlying = Nullable.GetUnderlyingType(toType) ?? toType;
|
||||
|
||||
if (!IsSupportedSeqType(fromType) || !IsSupportedSeqType(toUnderlying))
|
||||
throw new InvalidCastException("Only 1D arrays and List<T> are supported. " + fromType + " → " + toType);
|
||||
|
||||
var fromElem = GetElementType(fromType)!;
|
||||
var toElem = GetElementType(toUnderlying)!;
|
||||
|
||||
// Fast path: same element type
|
||||
if (fromElem == toElem)
|
||||
{
|
||||
if (fromType.IsArray && IsListType(toUnderlying))
|
||||
return ArrayToListDirect((Array)value, toUnderlying);
|
||||
|
||||
if (IsListType(fromType) && toUnderlying.IsArray)
|
||||
return ListToArrayDirect((IList)value, toElem);
|
||||
|
||||
if (fromType.IsArray && toUnderlying.IsArray)
|
||||
return ArrayToArrayDirect((Array)value, toElem);
|
||||
|
||||
if (IsListType(fromType) && IsListType(toUnderlying))
|
||||
return ListToListDirect((IList)value, toUnderlying, toElem);
|
||||
}
|
||||
|
||||
// General path with per-element converter
|
||||
var elemConv = _elemConvCache.GetOrAdd((fromElem, toElem),
|
||||
k => (object? elem, RuntimeCastOptions o) =>
|
||||
{
|
||||
if (elem == null) return null;
|
||||
return Cast(elem, toElem, o);
|
||||
});
|
||||
|
||||
if (fromType.IsArray && IsListType(toUnderlying))
|
||||
return ArrayToListConverted((Array)value, toUnderlying, toElem, elemConv, opts);
|
||||
|
||||
if (IsListType(fromType) && toUnderlying.IsArray)
|
||||
return ListToArrayConverted((IList)value, toElem, elemConv, opts);
|
||||
|
||||
if (fromType.IsArray && toUnderlying.IsArray)
|
||||
return ArrayToArrayConverted((Array)value, toElem, elemConv, opts);
|
||||
|
||||
if (IsListType(fromType) && IsListType(toUnderlying))
|
||||
return ListToListConverted((IList)value, toUnderlying, toElem, elemConv, opts);
|
||||
|
||||
throw new InvalidCastException("Unsupported sequence cast " + fromType + " → " + toType + ".");
|
||||
}
|
||||
|
||||
// ------------------------ Builder ------------------------
|
||||
|
||||
private static Func<object?, RuntimeCastOptions, object?> BuildConverter(Type fromType, Type toType)
|
||||
{
|
||||
return (value, opts) => ConvertCore(value!, fromType, toType, opts);
|
||||
}
|
||||
|
||||
// ------------------------ Core Routing ------------------------
|
||||
|
||||
private static object? ConvertCore(object value, Type fromType, Type toType, RuntimeCastOptions opts)
|
||||
{
|
||||
var toUnderlying = Nullable.GetUnderlyingType(toType) ?? toType;
|
||||
var fromUnderlying = Nullable.GetUnderlyingType(fromType) ?? fromType;
|
||||
|
||||
// Collections early
|
||||
{
|
||||
bool handled;
|
||||
var coll = ConvertCollectionsIfAny(value, fromType, toType, opts, out handled);
|
||||
if (handled) return coll;
|
||||
}
|
||||
|
||||
// Enum
|
||||
if (toUnderlying.IsEnum)
|
||||
return ConvertToEnum(value, fromUnderlying, toType, toUnderlying, opts);
|
||||
|
||||
// Guid
|
||||
if (toUnderlying == typeof(Guid))
|
||||
return ConvertToGuid(value, fromUnderlying, toType);
|
||||
|
||||
// Date/Time
|
||||
if (toUnderlying == typeof(DateTime))
|
||||
return ConvertToDateTime(value, fromUnderlying, toType, opts);
|
||||
|
||||
if (toUnderlying == typeof(DateTimeOffset))
|
||||
return ConvertToDateTimeOffset(value, fromUnderlying, toType, opts);
|
||||
|
||||
// float/double -> decimal with policy
|
||||
if (toUnderlying == typeof(decimal) &&
|
||||
(fromUnderlying == typeof(float) || fromUnderlying == typeof(double)))
|
||||
{
|
||||
bool useNull;
|
||||
var dec = ConvertFloatDoubleToDecimal(value, fromUnderlying, opts, out useNull);
|
||||
if (toType != toUnderlying) // Nullable<decimal>
|
||||
return useNull ? null : (decimal?)dec;
|
||||
if (useNull) throw new OverflowException("NaN/Infinity cannot be converted to decimal.");
|
||||
return dec;
|
||||
}
|
||||
|
||||
// Numeric -> Numeric
|
||||
if (IsNumeric(fromUnderlying) && IsNumeric(toUnderlying))
|
||||
{
|
||||
var nc = _numericCache.GetOrAdd((fromUnderlying, toUnderlying, opts.CheckedNumeric),
|
||||
k => BuildNumericConverter(k.from, k.to, k.@checked));
|
||||
var result = nc(value);
|
||||
if (toType != toUnderlying) return BoxNullable(result, toUnderlying);
|
||||
return result;
|
||||
}
|
||||
|
||||
// To string
|
||||
if (toUnderlying == typeof(string))
|
||||
return value != null ? value.ToString() : null;
|
||||
|
||||
// TypeConverter(target)
|
||||
var tc = System.ComponentModel.TypeDescriptor.GetConverter(toUnderlying);
|
||||
if (tc.CanConvertFrom(fromUnderlying))
|
||||
{
|
||||
var r = tc.ConvertFrom(null, opts.Culture, value);
|
||||
if (toType != toUnderlying) return BoxNullable(r!, toUnderlying);
|
||||
return r!;
|
||||
}
|
||||
|
||||
// TypeConverter(source)
|
||||
var tc2 = System.ComponentModel.TypeDescriptor.GetConverter(fromUnderlying);
|
||||
if (tc2.CanConvertTo(toUnderlying))
|
||||
{
|
||||
var r = tc2.ConvertTo(null, opts.Culture, value, toUnderlying);
|
||||
if (toType != toUnderlying) return BoxNullable(r!, toUnderlying);
|
||||
return r!;
|
||||
}
|
||||
|
||||
// Convert.ChangeType fallback
|
||||
try
|
||||
{
|
||||
var r = Convert.ChangeType(value, toUnderlying, opts.Culture);
|
||||
if (toType != toUnderlying) return BoxNullable(r!, toUnderlying);
|
||||
return r!;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (toUnderlying.IsInstanceOfType(value))
|
||||
{
|
||||
if (toType != toUnderlying) return BoxNullable(value, toUnderlying);
|
||||
return value;
|
||||
}
|
||||
throw new InvalidCastException("Cannot cast " + fromType + " to " + toType + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private static object? ConvertCollectionsIfAny(object value, Type fromType, Type toType, RuntimeCastOptions opts, out bool handled)
|
||||
{
|
||||
handled = false;
|
||||
var toUnderlying = Nullable.GetUnderlyingType(toType) ?? toType;
|
||||
|
||||
if (!IsSupportedSeqType(fromType) || !IsSupportedSeqType(toUnderlying))
|
||||
return value;
|
||||
|
||||
handled = true;
|
||||
return CastSequence(value, toUnderlying, opts);
|
||||
}
|
||||
|
||||
// ------------------------ Numeric Helpers ------------------------
|
||||
|
||||
private static Func<object, object> BuildNumericConverter(Type from, Type to, bool @checked)
|
||||
{
|
||||
var p = Expression.Parameter(typeof(object), "v");
|
||||
Expression val = from.IsValueType ? (Expression)Expression.Unbox(p, from)
|
||||
: (Expression)Expression.Convert(p, from);
|
||||
|
||||
Expression body;
|
||||
try
|
||||
{
|
||||
body = @checked ? (Expression)Expression.ConvertChecked(val, to)
|
||||
: (Expression)Expression.Convert(val, to);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
throw new InvalidCastException("Numeric conversion not supported: " + from + " -> " + to);
|
||||
}
|
||||
|
||||
Expression boxed = to.IsValueType ? (Expression)Expression.Convert(body, typeof(object)) : body;
|
||||
return Expression.Lambda<Func<object, object>>(boxed, p).Compile();
|
||||
}
|
||||
|
||||
private static bool IsNumeric(Type t)
|
||||
{
|
||||
t = Nullable.GetUnderlyingType(t) ?? t;
|
||||
return t == typeof(byte) || t == typeof(sbyte) ||
|
||||
t == typeof(short) || t == typeof(ushort) ||
|
||||
t == typeof(int) || t == typeof(uint) ||
|
||||
t == typeof(long) || t == typeof(ulong) ||
|
||||
t == typeof(float) || t == typeof(double) ||
|
||||
t == typeof(decimal);
|
||||
}
|
||||
|
||||
private static bool IsNonNullableValueType(Type t)
|
||||
{
|
||||
return t.IsValueType && Nullable.GetUnderlyingType(t) == null;
|
||||
}
|
||||
|
||||
private static object BoxNullable(object value, Type underlying)
|
||||
{
|
||||
var nt = typeof(Nullable<>).MakeGenericType(underlying);
|
||||
var ctor = nt.GetConstructor(new[] { underlying });
|
||||
return ctor!.Invoke(new[] { value });
|
||||
}
|
||||
|
||||
// ------------------------ NaN/∞ to decimal ------------------------
|
||||
|
||||
private static decimal ConvertFloatDoubleToDecimal(object value, Type fromUnderlying, RuntimeCastOptions opts, out bool useNull)
|
||||
{
|
||||
useNull = false;
|
||||
|
||||
if (fromUnderlying == typeof(float))
|
||||
{
|
||||
var f = (float)value;
|
||||
if (float.IsNaN(f) || float.IsInfinity(f))
|
||||
{
|
||||
if (opts.NaNInfinityPolicy == NaNInfinityPolicy.NullIfNullable) { useNull = true; return 0m; }
|
||||
if (opts.NaNInfinityPolicy == NaNInfinityPolicy.CoerceZero) return 0m;
|
||||
throw new OverflowException("Cannot convert NaN/Infinity to decimal.");
|
||||
}
|
||||
return opts.CheckedNumeric ? checked((decimal)f) : (decimal)f;
|
||||
}
|
||||
else
|
||||
{
|
||||
var d = (double)value;
|
||||
if (double.IsNaN(d) || double.IsInfinity(d))
|
||||
{
|
||||
if (opts.NaNInfinityPolicy == NaNInfinityPolicy.NullIfNullable) { useNull = true; return 0m; }
|
||||
if (opts.NaNInfinityPolicy == NaNInfinityPolicy.CoerceZero) return 0m;
|
||||
throw new OverflowException("Cannot convert NaN/Infinity to decimal.");
|
||||
}
|
||||
return opts.CheckedNumeric ? checked((decimal)d) : (decimal)d;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ Enum ------------------------
|
||||
// Note: .NET Standard 2.0 lacks non-generic TryParse(Type, …, ignoreCase).
|
||||
// We use Enum.Parse(Type, string, bool ignoreCase) with try/catch.
|
||||
|
||||
private static object? ConvertToEnum(object value, Type fromUnderlying, Type toType, Type enumType, RuntimeCastOptions opts)
|
||||
{
|
||||
bool wrapNullable = toType != enumType;
|
||||
|
||||
if (fromUnderlying == typeof(string))
|
||||
{
|
||||
object parsed;
|
||||
try
|
||||
{
|
||||
parsed = Enum.Parse(enumType, (string)value, opts.EnumIgnoreCase);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
throw new InvalidCastException("Cannot parse '" + value + "' to " + enumType.Name + ".");
|
||||
}
|
||||
|
||||
if (opts.EnumMustBeDefined && !Enum.IsDefined(enumType, parsed))
|
||||
throw new InvalidCastException("Value '" + value + "' is not a defined member of " + enumType.Name + ".");
|
||||
|
||||
return wrapNullable ? BoxNullable(parsed, enumType) : parsed;
|
||||
}
|
||||
|
||||
if (IsNumeric(fromUnderlying))
|
||||
{
|
||||
var et = Enum.GetUnderlyingType(enumType);
|
||||
var numConv = _numericCache.GetOrAdd((fromUnderlying, et, true),
|
||||
k => BuildNumericConverter(k.from, k.to, k.@checked));
|
||||
var integral = numConv(value);
|
||||
|
||||
var enumObj = Enum.ToObject(enumType, integral);
|
||||
if (opts.EnumMustBeDefined && !Enum.IsDefined(enumType, enumObj))
|
||||
throw new InvalidCastException("Numeric value " + integral + " is not a defined member of " + enumType.Name + ".");
|
||||
|
||||
return wrapNullable ? BoxNullable(enumObj, enumType) : enumObj;
|
||||
}
|
||||
|
||||
throw new InvalidCastException("Cannot cast " + fromUnderlying + " to enum " + enumType.Name + ".");
|
||||
}
|
||||
|
||||
// ------------------------ Guid ------------------------
|
||||
|
||||
private static object? ConvertToGuid(object value, Type fromUnderlying, Type toType)
|
||||
{
|
||||
bool wrapNullable = toType != typeof(Guid);
|
||||
|
||||
if (fromUnderlying == typeof(string))
|
||||
{
|
||||
Guid g;
|
||||
if (!Guid.TryParse((string)value, out g))
|
||||
throw new InvalidCastException("Cannot parse '" + value + "' to Guid.");
|
||||
return wrapNullable ? (Guid?)g : g;
|
||||
}
|
||||
|
||||
if (fromUnderlying == typeof(byte[]))
|
||||
{
|
||||
var bytes = (byte[])value;
|
||||
if (bytes.Length != 16)
|
||||
throw new InvalidCastException("Guid requires a 16-byte array.");
|
||||
var g = new Guid(bytes);
|
||||
return wrapNullable ? (Guid?)g : g;
|
||||
}
|
||||
|
||||
throw new InvalidCastException("Cannot cast " + fromUnderlying + " to Guid.");
|
||||
}
|
||||
|
||||
// ------------------------ DateTime / DateTimeOffset ------------------------
|
||||
|
||||
private static object? ConvertToDateTime(object value, Type fromUnderlying, Type toType, RuntimeCastOptions opts)
|
||||
{
|
||||
bool wrapNullable = toType != typeof(DateTime);
|
||||
|
||||
if (fromUnderlying == typeof(string))
|
||||
{
|
||||
DateTime dt;
|
||||
if (!DateTime.TryParse((string)value, opts.Culture, opts.DateTimeStyles, out dt))
|
||||
throw new InvalidCastException("Cannot parse '" + value + "' to DateTime.");
|
||||
return wrapNullable ? (DateTime?)dt : dt;
|
||||
}
|
||||
|
||||
if (fromUnderlying == typeof(long))
|
||||
{
|
||||
var dt = new DateTime((long)value, DateTimeKind.Unspecified);
|
||||
return wrapNullable ? (DateTime?)dt : dt;
|
||||
}
|
||||
|
||||
if (fromUnderlying == typeof(double))
|
||||
{
|
||||
var d = (double)value;
|
||||
if (double.IsNaN(d) || double.IsInfinity(d))
|
||||
throw new InvalidCastException("Cannot convert NaN/Infinity to DateTime.");
|
||||
var dt = DateTime.FromOADate(d);
|
||||
return wrapNullable ? (DateTime?)dt : dt;
|
||||
}
|
||||
|
||||
throw new InvalidCastException("Cannot cast " + fromUnderlying + " to DateTime.");
|
||||
}
|
||||
|
||||
private static object? ConvertToDateTimeOffset(object value, Type fromUnderlying, Type toType, RuntimeCastOptions opts)
|
||||
{
|
||||
bool wrapNullable = toType != typeof(DateTimeOffset);
|
||||
|
||||
if (fromUnderlying == typeof(string))
|
||||
{
|
||||
DateTimeOffset dto;
|
||||
if (!DateTimeOffset.TryParse((string)value, opts.Culture, opts.DateTimeStyles, out dto))
|
||||
throw new InvalidCastException("Cannot parse '" + value + "' to DateTimeOffset.");
|
||||
return wrapNullable ? (DateTimeOffset?)dto : dto;
|
||||
}
|
||||
|
||||
if (fromUnderlying == typeof(long))
|
||||
{
|
||||
var dto = new DateTimeOffset(new DateTime((long)value, DateTimeKind.Unspecified));
|
||||
return wrapNullable ? (DateTimeOffset?)dto : dto;
|
||||
}
|
||||
|
||||
if (fromUnderlying == typeof(double))
|
||||
{
|
||||
var d = (double)value;
|
||||
if (double.IsNaN(d) || double.IsInfinity(d))
|
||||
throw new InvalidCastException("Cannot convert NaN/Infinity to DateTimeOffset.");
|
||||
var dt = DateTime.FromOADate(d);
|
||||
var dto = new DateTimeOffset(dt);
|
||||
return wrapNullable ? (DateTimeOffset?)dto : dto;
|
||||
}
|
||||
|
||||
throw new InvalidCastException("Cannot cast " + fromUnderlying + " to DateTimeOffset.");
|
||||
}
|
||||
|
||||
// ------------------------ Collections (arrays/lists) ------------------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsListType(Type t)
|
||||
{
|
||||
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsSupportedSeqType(Type t)
|
||||
{
|
||||
return (t.IsArray && t.GetArrayRank() == 1) || IsListType(t);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Type? GetElementType(Type t)
|
||||
{
|
||||
if (t.IsArray) return t.GetElementType();
|
||||
if (IsListType(t)) return t.GetGenericArguments()[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fast-path (same element types)
|
||||
private static object ArrayToListDirect(Array src, Type listTarget)
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(listTarget, src.Length)!;
|
||||
foreach (var e in src) list.Add(e);
|
||||
return list;
|
||||
}
|
||||
|
||||
private static object ListToArrayDirect(IList src, Type elemType)
|
||||
{
|
||||
var arr = Array.CreateInstance(elemType, src.Count);
|
||||
for (int i = 0; i < src.Count; i++) arr.SetValue(src[i], i);
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static object ArrayToArrayDirect(Array src, Type elemType)
|
||||
{
|
||||
var dst = Array.CreateInstance(elemType, src.Length);
|
||||
Array.Copy(src, dst, src.Length);
|
||||
return dst;
|
||||
}
|
||||
|
||||
private static object ListToListDirect(IList src, Type listTarget, Type elemType)
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(listTarget, src.Count)!;
|
||||
for (int i = 0; i < src.Count; i++) list.Add(src[i]);
|
||||
return list;
|
||||
}
|
||||
|
||||
// Converted element paths
|
||||
private static object ArrayToListConverted(
|
||||
Array src, Type listTarget, Type toElem,
|
||||
Func<object?, RuntimeCastOptions, object?> elemConv, RuntimeCastOptions opts)
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(listTarget, src.Length)!;
|
||||
for (int i = 0; i < src.Length; i++)
|
||||
{
|
||||
var v = src.GetValue(i);
|
||||
list.Add(elemConv(v, opts));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static object ListToArrayConverted(
|
||||
IList src, Type toElem,
|
||||
Func<object?, RuntimeCastOptions, object?> elemConv, RuntimeCastOptions opts)
|
||||
{
|
||||
var arr = Array.CreateInstance(toElem, src.Count);
|
||||
for (int i = 0; i < src.Count; i++)
|
||||
{
|
||||
var v = src[i];
|
||||
arr.SetValue(elemConv(v, opts), i);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static object ArrayToArrayConverted(
|
||||
Array src, Type toElem,
|
||||
Func<object?, RuntimeCastOptions, object?> elemConv, RuntimeCastOptions opts)
|
||||
{
|
||||
var dst = Array.CreateInstance(toElem, src.Length);
|
||||
for (int i = 0; i < src.Length; i++)
|
||||
{
|
||||
var v = src.GetValue(i);
|
||||
dst.SetValue(elemConv(v, opts), i);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
private static object ListToListConverted(
|
||||
IList src, Type listTarget, Type toElem,
|
||||
Func<object?, RuntimeCastOptions, object?> elemConv, RuntimeCastOptions opts)
|
||||
{
|
||||
var dst = (IList)Activator.CreateInstance(listTarget, src.Count)!;
|
||||
for (int i = 0; i < src.Count; i++)
|
||||
{
|
||||
var v = src[i];
|
||||
dst.Add(elemConv(v, opts));
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
|
||||
183
Libraries/Esiur/Data/StringKeyList.cs
Normal file
183
Libraries/Esiur/Data/StringKeyList.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
|
||||
public class StringKeyList : IEnumerable<KeyValuePair<string, string>>
|
||||
{
|
||||
|
||||
private List<KeyValuePair<string, string>> m_Variables = new List<KeyValuePair<string, string>>();
|
||||
|
||||
private bool allowMultiple;
|
||||
|
||||
public delegate void Modified(string key, string newValue);
|
||||
public event Modified OnModified;
|
||||
|
||||
public StringKeyList(bool allowMultipleValues = false)
|
||||
{
|
||||
allowMultiple = allowMultipleValues;
|
||||
}
|
||||
|
||||
public void Add(string key, string value)
|
||||
{
|
||||
if (OnModified != null)
|
||||
OnModified(key, value);
|
||||
|
||||
key = key.ToLower();
|
||||
|
||||
if (!allowMultiple)
|
||||
{
|
||||
foreach (var kv in m_Variables)
|
||||
{
|
||||
if (kv.Key.ToLower() == key)
|
||||
{
|
||||
m_Variables.Remove(kv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_Variables.Add(new KeyValuePair<string, string>(key, value));
|
||||
}
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
key = key.ToLower();
|
||||
foreach (var kv in m_Variables)
|
||||
if (kv.Key.ToLower() == key)
|
||||
return kv.Value;
|
||||
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
key = key.ToLower();
|
||||
|
||||
var toRemove = m_Variables.Where(x => x.Key.ToLower() == key).ToArray();
|
||||
|
||||
foreach (var item in toRemove)
|
||||
m_Variables.Remove(item);
|
||||
|
||||
|
||||
m_Variables.Add(new KeyValuePair<string, string>(key, value));
|
||||
|
||||
OnModified?.Invoke(key, value);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
|
||||
{
|
||||
return m_Variables.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return m_Variables.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (OnModified != null)
|
||||
OnModified(null, null);
|
||||
|
||||
m_Variables.Clear();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public List<string> GetValues(string Key)
|
||||
{
|
||||
var key = Key.ToLower();
|
||||
|
||||
List<string> values = new List<string>();
|
||||
|
||||
foreach (var kv in m_Variables)
|
||||
if (kv.Key.ToLower() == key)
|
||||
values.Add(kv.Value);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public void RemoveAll(string key)
|
||||
{
|
||||
while (Remove(key)) { }
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
key = key.ToLower();
|
||||
|
||||
foreach (var kv in m_Variables)
|
||||
{
|
||||
if (kv.Key.ToLower() == key)
|
||||
{
|
||||
if (OnModified != null)
|
||||
OnModified(key, null);
|
||||
m_Variables.Remove(kv);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return m_Variables.Count; }
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
key = key.ToLower();
|
||||
foreach (var kv in m_Variables)
|
||||
if (kv.Key.ToLower() == key)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public bool ContainsValue(string value)
|
||||
{
|
||||
value = value.ToLower();
|
||||
foreach (var kv in m_Variables)
|
||||
if (kv.Value.ToLower() == value)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
313
Libraries/Esiur/Data/Tdu.cs
Normal file
313
Libraries/Esiur/Data/Tdu.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.Schema;
|
||||
using static Esiur.Data.Codec;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
// Transmission Data Unit
|
||||
public struct Tdu
|
||||
{
|
||||
public TduIdentifier Identifier;
|
||||
//public int Index;
|
||||
public TduClass Class;
|
||||
//public ulong ContentLength;
|
||||
|
||||
public byte[] Composed;
|
||||
//public uint Offset;
|
||||
|
||||
public byte[] Metadata;
|
||||
|
||||
public uint ContentOffset;
|
||||
|
||||
//public ulong Size
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// if (TotalSize != ulong.MaxValue)
|
||||
// return TotalSize;
|
||||
// else
|
||||
// {
|
||||
// if (ContentLength <= 0xFF)
|
||||
// return 2 + ContentLength;
|
||||
// else if (ContentLength <= 0xFF_FF)
|
||||
// return 3 + ContentLength;
|
||||
// else if (ContentLength <= 0xFF_FF_FF)
|
||||
// return 4 + ContentLength;
|
||||
// else if (ContentLength <= 0xFF_FF_FF_FF)
|
||||
// return 5 + ContentLength;
|
||||
// else if (ContentLength <= 0xFF_FF_FF_FF_FF)
|
||||
// return 6 + ContentLength;
|
||||
// else if (ContentLength <= 0xFF_FF_FF_FF_FF_FF)
|
||||
// return 7 + ContentLength;
|
||||
// else if (ContentLength <= 0xFF_FF_FF_FF_FF_FF_FF)
|
||||
// return 8 + ContentLength;
|
||||
// else //if (ContentLength <= 0xFF_FF_FF_FF_FF_FF_FF_FF)
|
||||
// return 9 + ContentLength;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//private ulong TotalSize;
|
||||
|
||||
public Tdu()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Tdu(TduIdentifier identifier)
|
||||
{
|
||||
Identifier = identifier;
|
||||
Composed = new byte[0];
|
||||
}
|
||||
|
||||
public Tdu(TduIdentifier identifier,
|
||||
byte[] data, ulong length, byte[] metadata = null)
|
||||
{
|
||||
Identifier = identifier;
|
||||
//Index = (byte)identifier & 0x7;
|
||||
Class = (TduClass)((byte)identifier >> 6);
|
||||
Metadata = metadata;
|
||||
|
||||
|
||||
if (Class == TduClass.Fixed)
|
||||
{
|
||||
if (length == 0)
|
||||
Composed = new byte[1] { (byte)identifier };
|
||||
else
|
||||
Composed = DC.Combine(new byte[] { (byte)Identifier }, 0, 1, data, 0, (uint)length);
|
||||
}
|
||||
else if (Class == TduClass.Dynamic
|
||||
|| Class == TduClass.Extension)
|
||||
{
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
Composed = new byte[1] { (byte)Identifier };
|
||||
}
|
||||
else if (length <= 0xFF)
|
||||
{
|
||||
Composed = new byte[2 + length];
|
||||
Composed[0] = (byte)((byte)Identifier | 0x8);
|
||||
Composed[1] = (byte)length;
|
||||
ContentOffset = 2;
|
||||
Buffer.BlockCopy(data, 0, Composed, 2, (int)length);
|
||||
}
|
||||
else if (length <= 0xFF_FF)
|
||||
{
|
||||
Composed = new byte[3 + length];
|
||||
Composed[0] = (byte)((byte)Identifier | 0x10);
|
||||
Composed[1] = (byte)((length >> 8) & 0xFF);
|
||||
Composed[2] = (byte)(length & 0xFF);
|
||||
ContentOffset = 3;
|
||||
|
||||
Buffer.BlockCopy(data, 0, Composed, 3, (int)length);
|
||||
}
|
||||
else if (length <= 0xFF_FF_FF)
|
||||
{
|
||||
Composed = new byte[4 + length];
|
||||
Composed[0] = (byte)((byte)Identifier | 0x18);
|
||||
Composed[1] = (byte)((length >> 16) & 0xFF);
|
||||
Composed[2] = (byte)((length >> 8) & 0xFF);
|
||||
Composed[3] = (byte)(length & 0xFF);
|
||||
ContentOffset = 4;
|
||||
|
||||
Buffer.BlockCopy(data, 0, Composed, 4, (int)length);
|
||||
}
|
||||
else if (length <= 0xFF_FF_FF_FF)
|
||||
{
|
||||
Composed = new byte[5 + length];
|
||||
Composed[0] = (byte)((byte)Identifier | 0x20);
|
||||
Composed[1] = (byte)((length >> 24) & 0xFF);
|
||||
Composed[2] = (byte)((length >> 16) & 0xFF);
|
||||
Composed[3] = (byte)((length >> 8) & 0xFF);
|
||||
Composed[4] = (byte)(length & 0xFF);
|
||||
ContentOffset = 5;
|
||||
|
||||
Buffer.BlockCopy(data, 0, Composed, 5, (int)length);
|
||||
}
|
||||
else if (length <= 0xFF_FF_FF_FF_FF)
|
||||
{
|
||||
Composed = new byte[6 + length];
|
||||
Composed[0] = (byte)((byte)Identifier | 0x28);
|
||||
Composed[1] = (byte)((length >> 32) & 0xFF);
|
||||
Composed[2] = (byte)((length >> 24) & 0xFF);
|
||||
Composed[3] = (byte)((length >> 16) & 0xFF);
|
||||
Composed[4] = (byte)((length >> 8) & 0xFF);
|
||||
Composed[5] = (byte)(length & 0xFF);
|
||||
ContentOffset = 6;
|
||||
|
||||
Buffer.BlockCopy(data, 0, Composed, 6, (int)length);
|
||||
}
|
||||
else if (length <= 0xFF_FF_FF_FF_FF_FF)
|
||||
{
|
||||
Composed = new byte[7 + length];
|
||||
Composed[0] = (byte)((byte)Identifier | 0x30);
|
||||
Composed[1] = (byte)((length >> 40) & 0xFF);
|
||||
Composed[2] = (byte)((length >> 32) & 0xFF);
|
||||
Composed[3] = (byte)((length >> 24) & 0xFF);
|
||||
Composed[4] = (byte)((length >> 16) & 0xFF);
|
||||
Composed[5] = (byte)((length >> 8) & 0xFF);
|
||||
Composed[6] = (byte)(length & 0xFF);
|
||||
ContentOffset = 7;
|
||||
Buffer.BlockCopy(data, 0, Composed, 7, (int)length);
|
||||
}
|
||||
else //if (len <= 0xFF_FF_FF_FF_FF_FF_FF)
|
||||
{
|
||||
Composed = new byte[8 + length];
|
||||
Composed[0] = (byte)((byte)Identifier | 0x38);
|
||||
Composed[1] = (byte)((length >> 48) & 0xFF);
|
||||
Composed[2] = (byte)((length >> 40) & 0xFF);
|
||||
Composed[3] = (byte)((length >> 32) & 0xFF);
|
||||
Composed[4] = (byte)((length >> 24) & 0xFF);
|
||||
Composed[5] = (byte)((length >> 16) & 0xFF);
|
||||
Composed[6] = (byte)((length >> 8) & 0xFF);
|
||||
Composed[7] = (byte)(length & 0xFF);
|
||||
ContentOffset = 8;
|
||||
Buffer.BlockCopy(data, 0, Composed, 8, (int)length);
|
||||
}
|
||||
}
|
||||
else if (Class == TduClass.Typed)
|
||||
{
|
||||
if (metadata == null)
|
||||
throw new Exception("Metadata must be provided for types.");
|
||||
|
||||
|
||||
if (metadata.Length > 0xFF)
|
||||
throw new Exception("Metadata can't exceed 255 bytes in length.");
|
||||
|
||||
var metaLen = (byte)metadata.Length;
|
||||
|
||||
var len = 1 + (ulong)metaLen + length;
|
||||
|
||||
|
||||
if (length == 0 && (metadata == null || metadata.Length == 0))
|
||||
{
|
||||
Composed = new byte[1] { (byte)Identifier };
|
||||
throw new Exception("Need check");
|
||||
}
|
||||
else if (metadata.Length > 0xFF)
|
||||
{
|
||||
throw new Exception("Metadata can't exceed 255 bytes in length.");
|
||||
}
|
||||
else if (length <= 0xFF)
|
||||
{
|
||||
Composed = new byte[2 + len];
|
||||
Composed[0] = (byte)((byte)Identifier | 0x8);
|
||||
Composed[1] = (byte)len;
|
||||
Composed[2] = metaLen;
|
||||
ContentOffset = metaLen + (uint)3;
|
||||
|
||||
Buffer.BlockCopy(metadata, 0, Composed, 3, metaLen);
|
||||
Buffer.BlockCopy(data, 0, Composed, 3 + metaLen, (int)length);
|
||||
}
|
||||
else if (len <= 0xFF_FF)
|
||||
{
|
||||
Composed = new byte[3 + len];
|
||||
Composed[0] = (byte)((byte)identifier | 0x10);
|
||||
Composed[1] = (byte)((len >> 8) & 0xFF);
|
||||
Composed[2] = (byte)(len & 0xFF);
|
||||
Composed[3] = metaLen;
|
||||
ContentOffset = metaLen + (uint)4;
|
||||
|
||||
Buffer.BlockCopy(metadata, 0, Composed, 4, metaLen);
|
||||
Buffer.BlockCopy(data, 0, Composed, 4 + metaLen, (int)length);
|
||||
}
|
||||
else if (len <= 0xFF_FF_FF)
|
||||
{
|
||||
Composed = new byte[4 + len];
|
||||
Composed[0] = (byte)((byte)identifier | 0x18);
|
||||
Composed[1] = (byte)((len >> 16) & 0xFF);
|
||||
Composed[2] = (byte)((len >> 8) & 0xFF);
|
||||
Composed[3] = (byte)(len & 0xFF);
|
||||
Composed[4] = metaLen;
|
||||
ContentOffset = metaLen + (uint)5;
|
||||
|
||||
Buffer.BlockCopy(metadata, 0, Composed, 5, metaLen);
|
||||
Buffer.BlockCopy(data, 0, Composed, 5 + metaLen, (int)length);
|
||||
|
||||
}
|
||||
else if (len <= 0xFF_FF_FF_FF)
|
||||
{
|
||||
Composed = new byte[5 + len];
|
||||
Composed[0] = (byte)((byte)identifier | 0x20);
|
||||
Composed[1] = (byte)((len >> 24) & 0xFF);
|
||||
Composed[2] = (byte)((len >> 16) & 0xFF);
|
||||
Composed[3] = (byte)((len >> 8) & 0xFF);
|
||||
Composed[4] = (byte)(len & 0xFF);
|
||||
Composed[5] = metaLen;
|
||||
ContentOffset = metaLen + (uint)6;
|
||||
|
||||
Buffer.BlockCopy(metadata, 0, Composed, 6, metaLen);
|
||||
Buffer.BlockCopy(data, 0, Composed, 6 + metaLen, (int)length);
|
||||
}
|
||||
else if (len <= 0xFF_FF_FF_FF_FF)
|
||||
{
|
||||
Composed = new byte[6 + len];
|
||||
Composed[0] = (byte)((byte)identifier | 0x28);
|
||||
Composed[1] = (byte)((len >> 32) & 0xFF);
|
||||
Composed[2] = (byte)((len >> 24) & 0xFF);
|
||||
Composed[3] = (byte)((len >> 16) & 0xFF);
|
||||
Composed[4] = (byte)((len >> 8) & 0xFF);
|
||||
Composed[5] = (byte)(len & 0xFF);
|
||||
Composed[6] = metaLen;
|
||||
ContentOffset = metaLen + (uint)7;
|
||||
|
||||
Buffer.BlockCopy(metadata, 0, Composed, 7, metaLen);
|
||||
Buffer.BlockCopy(data, 0, Composed, 7 + metaLen, (int)length);
|
||||
}
|
||||
else if (len <= 0xFF_FF_FF_FF_FF_FF)
|
||||
{
|
||||
Composed = new byte[7 + len];
|
||||
Composed[0] = (byte)((byte)identifier | 0x30);
|
||||
Composed[1] = (byte)((len >> 40) & 0xFF);
|
||||
Composed[2] = (byte)((len >> 32) & 0xFF);
|
||||
Composed[3] = (byte)((len >> 24) & 0xFF);
|
||||
Composed[4] = (byte)((len >> 16) & 0xFF);
|
||||
Composed[5] = (byte)((len >> 8) & 0xFF);
|
||||
Composed[6] = (byte)(len & 0xFF);
|
||||
Composed[7] = metaLen;
|
||||
ContentOffset = metaLen + (uint)8;
|
||||
|
||||
Buffer.BlockCopy(metadata, 0, Composed, 8, metaLen);
|
||||
Buffer.BlockCopy(data, 0, Composed, 8 + metaLen, (int)length);
|
||||
}
|
||||
else //if (len <= 0xFF_FF_FF_FF_FF_FF_FF)
|
||||
{
|
||||
Composed = new byte[8 + len];
|
||||
Composed[0] = (byte)((byte)identifier | 0x38);
|
||||
Composed[1] = (byte)((len >> 48) & 0xFF);
|
||||
Composed[2] = (byte)((len >> 40) & 0xFF);
|
||||
Composed[3] = (byte)((len >> 32) & 0xFF);
|
||||
Composed[4] = (byte)((len >> 24) & 0xFF);
|
||||
Composed[5] = (byte)((len >> 16) & 0xFF);
|
||||
Composed[6] = (byte)((len >> 8) & 0xFF);
|
||||
Composed[7] = (byte)(len & 0xFF);
|
||||
Composed[8] = metaLen;
|
||||
ContentOffset = metaLen + (uint)9;
|
||||
|
||||
Buffer.BlockCopy(metadata, 0, Composed, 9, metaLen);
|
||||
Buffer.BlockCopy(data, 0, Composed, 9 + metaLen, (int)length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public bool MatchType(Tdu with)
|
||||
{
|
||||
if (Identifier != with.Identifier)
|
||||
return false;
|
||||
|
||||
if (Class != TduClass.Typed || with.Class != TduClass.Typed)
|
||||
return false;
|
||||
|
||||
if (!Metadata.SequenceEqual(with.Metadata))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
15
Libraries/Esiur/Data/TduClass.cs
Normal file
15
Libraries/Esiur/Data/TduClass.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public enum TduClass
|
||||
{
|
||||
Fixed = 0x0,
|
||||
Dynamic = 0x1,
|
||||
Typed = 0x2,
|
||||
Extension = 0x3,
|
||||
Invalid
|
||||
}
|
||||
}
|
||||
64
Libraries/Esiur/Data/TduIdentifier.cs
Normal file
64
Libraries/Esiur/Data/TduIdentifier.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public enum TduIdentifier
|
||||
{
|
||||
Null = 0x0,
|
||||
False = 0x1,
|
||||
True = 0x2,
|
||||
NotModified = 0x3,
|
||||
Infinity = 0x4,
|
||||
UInt8 = 0x8,
|
||||
Int8 = 0x9,
|
||||
Char8 = 0xA,
|
||||
LocalResource8 = 0xB,
|
||||
RemoteResource8 = 0xC,
|
||||
LocalProcedure8 = 0xD,
|
||||
RemoteProcedure8 = 0xE,
|
||||
UInt16 = 0x10,
|
||||
Int16 = 0x11,
|
||||
Char16 = 0x12,
|
||||
LocalResource16 = 0x13,
|
||||
RemoteResource16 = 0x14,
|
||||
LocalProcedure16 = 0x15,
|
||||
RemoteProcedure16 = 0x16,
|
||||
UInt32 = 0x18,
|
||||
Int32 = 0x19,
|
||||
Float32 = 0x1A,
|
||||
LocalResource32 = 0x1B,
|
||||
RemoteResource32 = 0x1C,
|
||||
LocalProcedure32 = 0x1D,
|
||||
RemoteProcedure32 = 0x1E,
|
||||
UInt64 = 0x20,
|
||||
Int64 = 0x21,
|
||||
Float64 = 0x22,
|
||||
DateTime = 0x23,
|
||||
UInt128 = 0x28,
|
||||
Int128 = 0x29,
|
||||
Decimal128 = 0x2A,
|
||||
UUID = 0x2B,
|
||||
|
||||
RawData = 0x40,
|
||||
String = 0x41,
|
||||
List = 0x42,
|
||||
ResourceList = 0x43,
|
||||
RecordList = 0x44,
|
||||
ResourceLink = 0x45,
|
||||
Map = 0x46,
|
||||
MapList = 0x47,
|
||||
|
||||
Record = 0x80,
|
||||
TypedList = 0x81,
|
||||
TypedMap = 0x82,
|
||||
TypedTuple = 0x83,
|
||||
TypedEnum = 0x84,
|
||||
TypedConstant = 0x85,
|
||||
|
||||
TypeContinuation = 0xC0,
|
||||
TypeOfTarget = 0xC1,
|
||||
|
||||
}
|
||||
}
|
||||
596
Libraries/Esiur/Data/Tru.cs
Normal file
596
Libraries/Esiur/Data/Tru.cs
Normal file
@@ -0,0 +1,596 @@
|
||||
using Esiur.Core;
|
||||
using Esiur.Data.Types;
|
||||
using Esiur.Resource;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public class Tru
|
||||
{
|
||||
|
||||
static TruIdentifier[] refTypes = new TruIdentifier[]
|
||||
{
|
||||
TruIdentifier.Dynamic,
|
||||
TruIdentifier.RawData,
|
||||
TruIdentifier.String,
|
||||
TruIdentifier.Resource,
|
||||
TruIdentifier.Record,
|
||||
TruIdentifier.Map,
|
||||
TruIdentifier.List,
|
||||
TruIdentifier.TypedList,
|
||||
TruIdentifier.TypedMap,
|
||||
TruIdentifier.Tuple2,
|
||||
TruIdentifier.Tuple3,
|
||||
TruIdentifier.Tuple4,
|
||||
TruIdentifier.Tuple5,
|
||||
TruIdentifier.Tuple6,
|
||||
TruIdentifier.Tuple7,
|
||||
TruIdentifier.TypedRecord,
|
||||
TruIdentifier.TypedResource
|
||||
};
|
||||
|
||||
static Map<TduIdentifier, TruIdentifier> typesMap = new Map<TduIdentifier, TruIdentifier>()
|
||||
{
|
||||
[TduIdentifier.UInt8] = TruIdentifier.UInt8,
|
||||
[TduIdentifier.Int8] = TruIdentifier.Int8,
|
||||
[TduIdentifier.UInt16] = TruIdentifier.UInt16,
|
||||
[TduIdentifier.Int16] = TruIdentifier.Int16,
|
||||
[TduIdentifier.UInt32] = TruIdentifier.UInt32,
|
||||
[TduIdentifier.Int32] = TruIdentifier.Int32,
|
||||
[TduIdentifier.UInt64] = TruIdentifier.UInt64,
|
||||
[TduIdentifier.Int64] = TruIdentifier.Int64,
|
||||
[TduIdentifier.UInt128] = TruIdentifier.UInt128,
|
||||
[TduIdentifier.Int128] = TruIdentifier.Int128,
|
||||
[TduIdentifier.Char8] = TruIdentifier.Char,
|
||||
[TduIdentifier.DateTime] = TruIdentifier.DateTime,
|
||||
[TduIdentifier.Float32] = TruIdentifier.Float32,
|
||||
[TduIdentifier.Float64] = TruIdentifier.Float64,
|
||||
[TduIdentifier.Decimal128] = TruIdentifier.Decimal,
|
||||
[TduIdentifier.False] = TruIdentifier.Bool,
|
||||
[TduIdentifier.True] = TruIdentifier.Bool,
|
||||
[TduIdentifier.Map] = TruIdentifier.Map,
|
||||
[TduIdentifier.List] = TruIdentifier.List,
|
||||
[TduIdentifier.RawData] = TruIdentifier.RawData,
|
||||
[TduIdentifier.Record] = TruIdentifier.Record,
|
||||
[TduIdentifier.String] = TruIdentifier.String,
|
||||
};
|
||||
|
||||
|
||||
|
||||
public void SetNull(List<byte> flags)
|
||||
{
|
||||
if (refTypes.Contains(Identifier))
|
||||
{
|
||||
Nullable = (flags.FirstOrDefault() == 2);
|
||||
if (flags.Count > 0)
|
||||
flags.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (SubTypes != null)
|
||||
foreach (var st in SubTypes)
|
||||
st.SetNull(flags);
|
||||
}
|
||||
|
||||
public void SetNull(byte flag)
|
||||
{
|
||||
if (refTypes.Contains(Identifier))
|
||||
{
|
||||
Nullable = (flag == 2);
|
||||
}
|
||||
|
||||
if (SubTypes != null)
|
||||
foreach (var st in SubTypes)
|
||||
st.SetNull(flag);
|
||||
}
|
||||
|
||||
|
||||
public void SetNotNull(List<byte> flags)
|
||||
{
|
||||
if (refTypes.Contains(Identifier))
|
||||
{
|
||||
Nullable = (flags.FirstOrDefault() != 1);
|
||||
if (flags.Count > 0)
|
||||
flags.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (SubTypes != null)
|
||||
foreach (var st in SubTypes)
|
||||
st.SetNotNull(flags);
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (SubTypes != null && SubTypes.Length > 0)
|
||||
return Identifier.ToString() + "<" + String.Join(",", SubTypes.Select(x => x.ToString())) + ">" + (Nullable ? "?" : "");
|
||||
return Identifier.ToString() + (Nullable ? "?" : "");
|
||||
}
|
||||
public void SetNotNull(byte flag)
|
||||
{
|
||||
if (refTypes.Contains(Identifier))
|
||||
{
|
||||
Nullable = (flag != 1);
|
||||
}
|
||||
|
||||
if (SubTypes != null)
|
||||
foreach (var st in SubTypes)
|
||||
st.SetNotNull(flag);
|
||||
}
|
||||
|
||||
public Type? GetRuntimeType(Warehouse warehouse)
|
||||
{
|
||||
|
||||
if (Identifier == TruIdentifier.TypedList)
|
||||
{
|
||||
var sub = SubTypes?[0].GetRuntimeType(warehouse);
|
||||
if (sub == null)
|
||||
return null;
|
||||
|
||||
var rt = sub.MakeArrayType();
|
||||
|
||||
return rt;
|
||||
}
|
||||
else if (Identifier == TruIdentifier.TypedMap)
|
||||
{
|
||||
var subs = SubTypes.Select(x => x.GetRuntimeType(warehouse)).ToArray();
|
||||
var rt = typeof(Map<,>).MakeGenericType(subs);
|
||||
return rt;
|
||||
}
|
||||
|
||||
return Identifier switch
|
||||
{
|
||||
(TruIdentifier.Void) => typeof(void),
|
||||
(TruIdentifier.Dynamic) => typeof(object),
|
||||
(TruIdentifier.Bool) => Nullable ? typeof(bool?) : typeof(bool),
|
||||
(TruIdentifier.Char) => Nullable ? typeof(char?) : typeof(char),
|
||||
(TruIdentifier.UInt8) => Nullable ? typeof(byte?) : typeof(byte),
|
||||
(TruIdentifier.Int8) => Nullable ? typeof(sbyte?) : typeof(sbyte),
|
||||
(TruIdentifier.Int16) => Nullable ? typeof(short?) : typeof(short),
|
||||
(TruIdentifier.UInt16) => Nullable ? typeof(ushort?) : typeof(ushort),
|
||||
(TruIdentifier.Int32) => Nullable ? typeof(int?) : typeof(int),
|
||||
(TruIdentifier.UInt32) => Nullable ? typeof(uint?) : typeof(uint),
|
||||
(TruIdentifier.Int64) => Nullable ? typeof(ulong?) : typeof(long),
|
||||
(TruIdentifier.UInt64) => Nullable ? typeof(ulong?) : typeof(ulong),
|
||||
(TruIdentifier.Float32) => Nullable ? typeof(float?) : typeof(float),
|
||||
(TruIdentifier.Float64) => Nullable ? typeof(double?) : typeof(double),
|
||||
(TruIdentifier.Decimal) => Nullable ? typeof(decimal?) : typeof(decimal),
|
||||
(TruIdentifier.String) => typeof(string),
|
||||
(TruIdentifier.DateTime) => Nullable ? typeof(DateTime?) : typeof(DateTime),
|
||||
(TruIdentifier.Resource) => typeof(IResource),
|
||||
(TruIdentifier.Record) => typeof(IRecord),
|
||||
(TruIdentifier.TypedRecord) => warehouse.GetTypeDefById((Uuid)UUID!, TypeDefKind.Record)?.DefinedType,
|
||||
(TruIdentifier.TypedResource) => warehouse.GetTypeDefById((Uuid)UUID!, TypeDefKind.Resource)?.DefinedType,
|
||||
(TruIdentifier.Enum) => warehouse.GetTypeDefById((Uuid)UUID!, TypeDefKind.Enum)?.DefinedType,
|
||||
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public TruIdentifier Identifier;
|
||||
public bool Nullable;
|
||||
public Uuid? UUID;
|
||||
//public RepresentationType? SubType1; // List + Map
|
||||
//public RepresentationType? SubType2; // Map
|
||||
//public RepresentationType? SubType3; // No types yet
|
||||
|
||||
public Tru[]? SubTypes = null;
|
||||
|
||||
public Tru ToNullable()
|
||||
{
|
||||
return new Tru(Identifier, true, UUID, SubTypes);
|
||||
}
|
||||
|
||||
public bool IsTyped()
|
||||
{
|
||||
if (Identifier == TruIdentifier.TypedList && SubTypes[0].Identifier == TruIdentifier.UInt8)
|
||||
return false;
|
||||
|
||||
if (Identifier == TruIdentifier.TypedResource)
|
||||
return false;
|
||||
|
||||
return (UUID != null) || (SubTypes != null && SubTypes.Length > 0);
|
||||
}
|
||||
|
||||
public bool Match(Tru other)
|
||||
{
|
||||
//if (UUID == null && (SubTypes == null || SubTypes.Length == 0))
|
||||
// return false;
|
||||
|
||||
if (other.Identifier != Identifier)
|
||||
return false;
|
||||
if (other.UUID != UUID)
|
||||
return false;
|
||||
|
||||
if (other.SubTypes != null)
|
||||
{
|
||||
if (other.SubTypes.Length != (SubTypes?.Length ?? -1))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < SubTypes?.Length; i++)
|
||||
if (!SubTypes[i].Match(other.SubTypes[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public (TduIdentifier, byte[]) GetMetadata()
|
||||
{
|
||||
switch (Identifier)
|
||||
{
|
||||
case TruIdentifier.TypedList:
|
||||
return (TduIdentifier.TypedList, SubTypes[0].Compose());
|
||||
case TruIdentifier.TypedRecord:
|
||||
return (TduIdentifier.Record, UUID?.Data);
|
||||
case TruIdentifier.TypedMap:
|
||||
return (TduIdentifier.TypedMap,
|
||||
SubTypes[0].Compose().Concat(SubTypes[1].Compose()).ToArray());
|
||||
case TruIdentifier.Enum:
|
||||
return (TduIdentifier.TypedEnum, UUID?.Data);
|
||||
|
||||
default:
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
//public TDUIdentifier GetTDUIdentifer()
|
||||
//{
|
||||
// switch (Identifier)
|
||||
// {
|
||||
|
||||
// case TRUIdentifier.TypedList: return TDUIdentifier.TypedList
|
||||
|
||||
// case TRUIdentifier.Int8: return TDUIdentifier.Int8;
|
||||
// case TRUIdentifier.Int16: return TDUIdentifier.Int16;
|
||||
// case TRUIdentifier.Int32: return TDUIdentifier.Int32;
|
||||
// case TRUIdentifier.Int64: return TDUIdentifier.Int64;
|
||||
|
||||
// case TRUIdentifier.UInt8: return TDUIdentifier.UInt8;
|
||||
// case TRUIdentifier.UInt16: return TDUIdentifier.UInt16;
|
||||
// case TRUIdentifier.UInt32: return TDUIdentifier.UInt32;
|
||||
// case TRUIdentifier.UInt64: return TDUIdentifier.UInt64;
|
||||
|
||||
// case TRUIdentifier.String: return TDUIdentifier.String;
|
||||
// case TRUIdentifier.Float32: return TDUIdentifier.Float32;
|
||||
// case TRUIdentifier.Float64: return TDUIdentifier.Float64;
|
||||
// case TRUIdentifier. }
|
||||
//}
|
||||
|
||||
private static Dictionary<Type, Tru> _cache = new Dictionary<Type, Tru>();
|
||||
|
||||
public static Tru? FromType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return new Tru(TruIdentifier.Void, true);
|
||||
|
||||
if (_cache.ContainsKey(type))
|
||||
return _cache[type];
|
||||
|
||||
var nullable = false;
|
||||
|
||||
var nullType = System.Nullable.GetUnderlyingType(type);
|
||||
|
||||
if (nullType != null)
|
||||
{
|
||||
type = nullType;
|
||||
nullable = true;
|
||||
}
|
||||
|
||||
Tru? tru = null;
|
||||
|
||||
if (type == typeof(IResource))
|
||||
{
|
||||
return new Tru(TruIdentifier.Resource, nullable);
|
||||
}
|
||||
else if (type == typeof(IRecord) || type == typeof(Record))
|
||||
{
|
||||
return new Tru(TruIdentifier.Record, nullable);
|
||||
}
|
||||
else if (type == typeof(Map<object, object>)
|
||||
|| type == typeof(Dictionary<object, object>))
|
||||
{
|
||||
return new Tru(TruIdentifier.Map, nullable);
|
||||
}
|
||||
else if (Codec.ImplementsInterface(type, typeof(IResource)))
|
||||
{
|
||||
tru = new Tru(
|
||||
TruIdentifier.TypedResource,
|
||||
nullable,
|
||||
TypeDef.GetTypeUUID(type)
|
||||
);
|
||||
}
|
||||
else if (Codec.ImplementsInterface(type, typeof(IRecord)))
|
||||
{
|
||||
tru = new Tru(
|
||||
TruIdentifier.TypedRecord,
|
||||
nullable,
|
||||
TypeDef.GetTypeUUID(type)
|
||||
);
|
||||
}
|
||||
else if (type.IsGenericType)
|
||||
{
|
||||
var genericType = type.GetGenericTypeDefinition();
|
||||
|
||||
if (genericType == typeof(List<>)
|
||||
|| genericType == typeof(VarList<>)
|
||||
|| genericType == typeof(IList<>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
if (args[0] == typeof(object))
|
||||
{
|
||||
tru = new Tru(TruIdentifier.List, nullable);
|
||||
}
|
||||
else
|
||||
{
|
||||
var subType = FromType(args[0]);
|
||||
if (subType == null) // unrecongnized type
|
||||
return null;
|
||||
|
||||
|
||||
tru = new Tru(TruIdentifier.TypedList, nullable, null,
|
||||
new Tru[] { subType });
|
||||
|
||||
}
|
||||
}
|
||||
else if (genericType == typeof(Map<,>)
|
||||
|| genericType == typeof(Dictionary<,>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
if (args[0] == typeof(object) && args[1] == typeof(object))
|
||||
{
|
||||
tru = new Tru(TruIdentifier.Map, nullable);
|
||||
}
|
||||
else
|
||||
{
|
||||
var subType1 = FromType(args[0]);
|
||||
if (subType1 == null)
|
||||
return null;
|
||||
|
||||
var subType2 = FromType(args[1]);
|
||||
if (subType2 == null)
|
||||
return null;
|
||||
|
||||
tru = new Tru(TruIdentifier.TypedMap, nullable, null,
|
||||
new Tru[] { subType1, subType2 });
|
||||
|
||||
}
|
||||
}
|
||||
else if (genericType == typeof(ResourceLink<>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
|
||||
return FromType(args[0]);
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
var subTypes = new Tru[args.Length];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var t = FromType(args[i]);
|
||||
if (t == null)
|
||||
return null;
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
|
||||
tru = new Tru(TruIdentifier.Tuple2, nullable, null, subTypes);
|
||||
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
var subTypes = new Tru[args.Length];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var t = FromType(args[i]);
|
||||
if (t == null)
|
||||
return null;
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
tru = new Tru(TruIdentifier.Tuple3, nullable, null, subTypes);
|
||||
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,,>))
|
||||
{
|
||||
|
||||
var args = type.GetGenericArguments();
|
||||
var subTypes = new Tru[args.Length];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var t = FromType(args[i]);
|
||||
if (t == null)
|
||||
return null;
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
tru = new Tru(TruIdentifier.Tuple4, nullable, null, subTypes);
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,,,>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
var subTypes = new Tru[args.Length];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var t = FromType(args[i]);
|
||||
if (t == null)
|
||||
return null;
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
tru = new Tru(TruIdentifier.Tuple5, nullable, null, subTypes);
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,,,,>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
var subTypes = new Tru[args.Length];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var t = FromType(args[i]);
|
||||
if (t == null)
|
||||
return null;
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
tru = new Tru(TruIdentifier.Tuple6, nullable, null, subTypes);
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,,,,,>))
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
var subTypes = new Tru[args.Length];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var t = FromType(args[i]);
|
||||
if (t == null)
|
||||
return null;
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
tru = new Tru(TruIdentifier.Tuple7, nullable, null, subTypes);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
else if (type.IsArray)
|
||||
{
|
||||
var elementType = type.GetElementType();
|
||||
if (elementType == typeof(object))
|
||||
tru = new Tru(TruIdentifier.List, nullable);
|
||||
else
|
||||
{
|
||||
var subType = FromType(elementType);
|
||||
|
||||
if (subType == null)
|
||||
return null;
|
||||
|
||||
tru = new Tru(TruIdentifier.TypedList, nullable, null,
|
||||
new Tru[] { subType });
|
||||
|
||||
}
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
tru = new Tru(TruIdentifier.Enum, nullable, TypeDef.GetTypeUUID(type));
|
||||
}
|
||||
else if (type.IsInterface)
|
||||
{
|
||||
return null; // other interfaces are not supported
|
||||
}
|
||||
|
||||
//else if (typeof(Structure).IsAssignableFrom(t) || t == typeof(ExpandoObject) => TRUIdentifier.Structure)
|
||||
//{
|
||||
|
||||
//}
|
||||
|
||||
if (tru != null)
|
||||
{
|
||||
_cache.Add(type, tru);
|
||||
return tru;
|
||||
}
|
||||
|
||||
// last check
|
||||
return type switch
|
||||
{
|
||||
_ when type == typeof(void) => new Tru(TruIdentifier.Void, nullable),
|
||||
_ when type == typeof(object) => new Tru(TruIdentifier.Dynamic, nullable),
|
||||
_ when type == typeof(bool) => new Tru(TruIdentifier.Bool, nullable),
|
||||
_ when type == typeof(char) => new Tru(TruIdentifier.Char, nullable),
|
||||
_ when type == typeof(byte) => new Tru(TruIdentifier.UInt8, nullable),
|
||||
_ when type == typeof(sbyte) => new Tru(TruIdentifier.Int8, nullable),
|
||||
_ when type == typeof(short) => new Tru(TruIdentifier.Int16, nullable),
|
||||
_ when type == typeof(ushort) => new Tru(TruIdentifier.UInt16, nullable),
|
||||
_ when type == typeof(int) => new Tru(TruIdentifier.Int32, nullable),
|
||||
_ when type == typeof(uint) => new Tru(TruIdentifier.UInt32, nullable),
|
||||
_ when type == typeof(long) => new Tru(TruIdentifier.Int64, nullable),
|
||||
_ when type == typeof(ulong) => new Tru(TruIdentifier.UInt64, nullable),
|
||||
_ when type == typeof(float) => new Tru(TruIdentifier.Float32, nullable),
|
||||
_ when type == typeof(double) => new Tru(TruIdentifier.Float64, nullable),
|
||||
_ when type == typeof(decimal) => new Tru(TruIdentifier.Decimal, nullable),
|
||||
_ when type == typeof(string) => new Tru(TruIdentifier.String, nullable),
|
||||
_ when type == typeof(DateTime) => new Tru(TruIdentifier.DateTime, nullable),
|
||||
_ when type == typeof(ResourceLink) => new Tru(TruIdentifier.Resource, nullable),
|
||||
_ => null
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public Tru(TruIdentifier identifier, bool nullable, Uuid? uuid = null, Tru[]? subTypes = null)
|
||||
{
|
||||
Nullable = nullable;
|
||||
Identifier = identifier;
|
||||
UUID = uuid;
|
||||
SubTypes = subTypes;
|
||||
}
|
||||
|
||||
public byte[] Compose()
|
||||
{
|
||||
var rt = new BinaryList();
|
||||
|
||||
if (Nullable)
|
||||
rt.AddUInt8((byte)(0x80 | (byte)Identifier));
|
||||
else
|
||||
rt.AddUInt8((byte)Identifier);
|
||||
|
||||
if (UUID != null)
|
||||
rt.AddUInt8Array(UUID.Value.Data);
|
||||
|
||||
if (SubTypes != null)
|
||||
for (var i = 0; i < SubTypes.Length; i++)
|
||||
rt.AddUInt8Array(SubTypes[i].Compose());
|
||||
|
||||
return rt.ToArray();
|
||||
}
|
||||
|
||||
public static (uint, Tru) Parse(byte[] data, uint offset)
|
||||
{
|
||||
var oOffset = offset;
|
||||
|
||||
var header = data[offset++];
|
||||
bool nullable = (header & 0x80) > 0;
|
||||
var identifier = (TruIdentifier)(header & 0x7F);
|
||||
|
||||
|
||||
if ((header & 0x40) > 0)
|
||||
{
|
||||
|
||||
var hasUUID = (header & 0x4) > 0;
|
||||
var subsCount = (header >> 3) & 0x7;
|
||||
|
||||
Uuid? uuid = null;
|
||||
|
||||
if (hasUUID)
|
||||
{
|
||||
uuid = data.GetUUID(offset);
|
||||
offset += 16;
|
||||
}
|
||||
|
||||
var subs = new Tru[subsCount];
|
||||
|
||||
for (var i = 0; i < subsCount; i++)
|
||||
{
|
||||
(var len, subs[i]) = Tru.Parse(data, offset);
|
||||
offset += len;
|
||||
}
|
||||
|
||||
return (offset - oOffset, new Tru(identifier, nullable, uuid, subs));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (1, new Tru(identifier, nullable));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
46
Libraries/Esiur/Data/TruIdentifier.cs
Normal file
46
Libraries/Esiur/Data/TruIdentifier.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public enum TruIdentifier
|
||||
{
|
||||
Void = 0x0,
|
||||
Dynamic = 0x1,
|
||||
Bool = 0x2,
|
||||
UInt8,
|
||||
Int8,
|
||||
Char,
|
||||
UInt16,
|
||||
Int16,
|
||||
UInt32,
|
||||
Int32,
|
||||
Float32,
|
||||
UInt64,
|
||||
Int64,
|
||||
Float64,
|
||||
DateTime,
|
||||
UInt128,
|
||||
Int128,
|
||||
Decimal,
|
||||
String,
|
||||
RawData,
|
||||
Resource,
|
||||
Record,
|
||||
List,
|
||||
Map,
|
||||
Enum = 0x44,
|
||||
TypedResource = 0x45, // Followed by UUID
|
||||
TypedRecord = 0x46, // Followed by UUID
|
||||
TypedList = 0x48, // Followed by element type
|
||||
Tuple2 = 0x50, // Followed by element type
|
||||
TypedMap = 0x51, // Followed by key type and value type
|
||||
Tuple3 = 0x58,
|
||||
Tuple4 = 0x60,
|
||||
Tuple5 = 0x68,
|
||||
Tuple6 = 0x70,
|
||||
Tuple7 = 0x78
|
||||
}
|
||||
|
||||
}
|
||||
99
Libraries/Esiur/Data/Types/ArgumentDef.cs
Normal file
99
Libraries/Esiur/Data/Types/ArgumentDef.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
public class ArgumentDef
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool Optional { get; set; }
|
||||
|
||||
public Tru Type { get; set; }
|
||||
|
||||
public ParameterInfo ParameterInfo { get; set; }
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public Map<string, string> Annotations { get; set; }
|
||||
|
||||
public static (uint, ArgumentDef) Parse(byte[] data, uint offset, int index)
|
||||
{
|
||||
var optional = (data[offset] & 0x1) == 0x1;
|
||||
var hasAnnotations = (data[offset++] & 0x2) == 0x2;
|
||||
|
||||
var cs = (uint)data[offset++];
|
||||
var name = data.GetString(offset, cs);
|
||||
offset += cs;
|
||||
var (size, type) = Tru.Parse(data, offset);
|
||||
|
||||
offset += size;
|
||||
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
if (hasAnnotations)
|
||||
{
|
||||
//var acs = data.GetUInt32(offset, Endian.Little);
|
||||
//offset += 2;
|
||||
var (l, a) = Codec.ParseSync(data, offset, null);
|
||||
// for saftey, Map<string, string> might change in the future
|
||||
if (a is Map<string, string> ann)
|
||||
annotations = ann;
|
||||
|
||||
cs += l;
|
||||
}
|
||||
|
||||
return (cs + 2 + size, new ArgumentDef()
|
||||
{
|
||||
Name = name,
|
||||
Index = index,
|
||||
Type = type,
|
||||
Optional = optional,
|
||||
Annotations = annotations
|
||||
});
|
||||
}
|
||||
|
||||
public ArgumentDef()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Optional)
|
||||
return $"[{Name}: {Type}]";
|
||||
else
|
||||
return $"{Name}: {Type} ";
|
||||
}
|
||||
|
||||
public byte[] Compose()
|
||||
{
|
||||
var name = DC.ToBytes(Name);
|
||||
|
||||
if (Annotations == null)
|
||||
{
|
||||
return new BinaryList()
|
||||
.AddUInt8(Optional ? (byte)1 : (byte)0)
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(Type.Compose())
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
var exp = Codec.Compose(Annotations, null, null);
|
||||
|
||||
return new BinaryList()
|
||||
.AddUInt8((byte)(0x2 | (Optional ? 1 : 0)))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(Type.Compose())
|
||||
.AddUInt8Array(exp)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Libraries/Esiur/Data/Types/AttributeDef.cs
Normal file
32
Libraries/Esiur/Data/Types/AttributeDef.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
public class AttributeDef : MemberDef
|
||||
{
|
||||
|
||||
public PropertyInfo PropertyInfo
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public static AttributeDef MakeAttributeDef(Type type, PropertyInfo pi, byte index, string name, TypeDef typeDef)
|
||||
{
|
||||
return new AttributeDef()
|
||||
{
|
||||
Index = index,
|
||||
Inherited = pi.DeclaringType != type,
|
||||
Name = name,
|
||||
PropertyInfo = pi,
|
||||
Definition = typeDef
|
||||
};
|
||||
}
|
||||
}
|
||||
138
Libraries/Esiur/Data/Types/ConstantDef.cs
Normal file
138
Libraries/Esiur/Data/Types/ConstantDef.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Esiur.Data;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
public class ConstantDef : MemberDef
|
||||
{
|
||||
public object Value { get; set; }
|
||||
|
||||
public Map<string, string> Annotations { get; set; }
|
||||
public Tru ValueType { get; set; }
|
||||
|
||||
public FieldInfo FieldInfo { get; set; }
|
||||
|
||||
|
||||
public static (uint, ConstantDef) Parse(byte[] data, uint offset, byte index, bool inherited)
|
||||
{
|
||||
var oOffset = offset;
|
||||
|
||||
var hasAnnotation = ((data[offset++] & 0x10) == 0x10);
|
||||
|
||||
var name = data.GetString(offset + 1, data[offset]);
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
var (dts, valueType) = Tru.Parse(data, offset);
|
||||
|
||||
offset += dts;
|
||||
|
||||
(dts, var value) = Codec.ParseSync(data, offset, Warehouse.Default);
|
||||
|
||||
offset += dts;
|
||||
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
// arguments
|
||||
if (hasAnnotation) // Annotation ?
|
||||
{
|
||||
var (len, anns) = Codec.ParseSync(data, offset, null);
|
||||
|
||||
if (anns is Map<string, string> map)
|
||||
annotations = map;
|
||||
|
||||
offset += len;
|
||||
}
|
||||
|
||||
return (offset - oOffset, new ConstantDef()
|
||||
{
|
||||
Index = index,
|
||||
Name = name,
|
||||
Inherited = inherited,
|
||||
ValueType = valueType,
|
||||
Value = value,
|
||||
Annotations = annotations
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public byte[] Compose()
|
||||
{
|
||||
var name = DC.ToBytes(Name);
|
||||
|
||||
var hdr = Inherited ? (byte)0x80 : (byte)0;
|
||||
|
||||
|
||||
if (Annotations != null)
|
||||
{
|
||||
var exp = Codec.Compose(Annotations, null, null);// DC.ToBytes(Annotation);
|
||||
hdr |= 0x70;
|
||||
return new BinaryList()
|
||||
.AddUInt8(hdr)
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(ValueType.Compose())
|
||||
.AddUInt8Array(Codec.Compose(Value, null, null))
|
||||
.AddInt32(exp.Length)
|
||||
.AddUInt8Array(exp)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
hdr |= 0x60;
|
||||
|
||||
return new BinaryList()
|
||||
.AddUInt8(hdr)
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(ValueType.Compose())
|
||||
.AddUInt8Array(Codec.Compose(Value, null, null))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static ConstantDef MakeConstantDef(Type type, FieldInfo ci, byte index = 0, string customName = null, TypeDef typeDef = null)
|
||||
{
|
||||
var annotationAttrs = ci.GetCustomAttributes<AnnotationAttribute>(true);
|
||||
|
||||
var valueType = Tru.FromType(ci.FieldType);
|
||||
|
||||
if (valueType == null)
|
||||
throw new Exception($"Unsupported type `{ci.FieldType}` in constant `{type.Name}.{ci.Name}`");
|
||||
|
||||
var value = ci.GetValue(null);
|
||||
|
||||
if (typeDef?.Kind == TypeDefKind.Enum)
|
||||
value = Convert.ChangeType(value, ci.FieldType.GetEnumUnderlyingType());
|
||||
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
if (annotationAttrs != null && annotationAttrs.Count() > 0)
|
||||
{
|
||||
annotations = new Map<string, string>();
|
||||
foreach (var attr in annotationAttrs)
|
||||
annotations.Add(attr.Key, attr.Value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return new ConstantDef()
|
||||
{
|
||||
Name = customName,
|
||||
Index = index,
|
||||
Inherited = ci.DeclaringType != type,
|
||||
ValueType = valueType,
|
||||
Value = value,
|
||||
FieldInfo = ci,
|
||||
Annotations = annotations,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
9
Libraries/Esiur/Data/Types/CustomEventOccurredEvent.cs
Normal file
9
Libraries/Esiur/Data/Types/CustomEventOccurredEvent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
internal class CustomEventOccurredEvent
|
||||
{
|
||||
}
|
||||
1
Libraries/Esiur/Data/Types/DefinitionDataType.cs
Normal file
1
Libraries/Esiur/Data/Types/DefinitionDataType.cs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
180
Libraries/Esiur/Data/Types/EventDef.cs
Normal file
180
Libraries/Esiur/Data/Types/EventDef.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
public class EventDef : MemberDef
|
||||
{
|
||||
|
||||
public Map<string, string> Annotations
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}: {ArgumentType}";
|
||||
}
|
||||
|
||||
public bool Subscribable { get; set; }
|
||||
|
||||
public EventInfo EventInfo { get; set; }
|
||||
|
||||
public Tru ArgumentType { get; set; }
|
||||
|
||||
|
||||
public static (uint, EventDef) Parse(byte[] data, uint offset, byte index, bool inherited)
|
||||
{
|
||||
var oOffset = offset;
|
||||
|
||||
var hasAnnotation = ((data[offset] & 0x10) == 0x10);
|
||||
var subscribable = ((data[offset++] & 0x8) == 0x8);
|
||||
|
||||
var name = data.GetString(offset + 1, data[offset]);
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
var (dts, argType) = Tru.Parse(data, offset);
|
||||
|
||||
offset += dts;
|
||||
|
||||
// Annotation ?
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
if (hasAnnotation)
|
||||
{
|
||||
var (len, anns) = Codec.ParseSync(data, offset, null);
|
||||
|
||||
if (anns is Map<string, string> map)
|
||||
annotations = map;
|
||||
|
||||
offset += len;
|
||||
}
|
||||
|
||||
return (offset - oOffset, new EventDef()
|
||||
{
|
||||
Index = index,
|
||||
Name = name,
|
||||
Inherited = inherited,
|
||||
ArgumentType = argType,
|
||||
Subscribable = subscribable,
|
||||
Annotations = annotations
|
||||
});
|
||||
}
|
||||
|
||||
public byte[] Compose()
|
||||
{
|
||||
var name = Name.ToBytes();
|
||||
|
||||
var hdr = Inherited ? (byte)0x80 : (byte)0;
|
||||
|
||||
if (Subscribable)
|
||||
hdr |= 0x8;
|
||||
|
||||
if (Annotations != null)
|
||||
{
|
||||
var exp = Codec.Compose(Annotations, null, null); //( DC.ToBytes(Annotation);
|
||||
hdr |= 0x50;
|
||||
return new BinaryList()
|
||||
.AddUInt8(hdr)
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(ArgumentType.Compose())
|
||||
.AddInt32(exp.Length)
|
||||
.AddUInt8Array(exp)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
hdr |= 0x40;
|
||||
|
||||
return new BinaryList()
|
||||
.AddUInt8(hdr)
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(ArgumentType.Compose())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public static EventDef MakeEventDef(Type type, EventInfo ei, byte index, string name, TypeDef schema)
|
||||
{
|
||||
|
||||
if (!ei.EventHandlerType.IsGenericType)
|
||||
throw new Exception($"Unsupported event handler type in event `{type.Name}.{ei.Name}`");
|
||||
|
||||
if (ei.EventHandlerType.GetGenericTypeDefinition() != typeof(ResourceEventHandler<>)
|
||||
&& ei.EventHandlerType.GetGenericTypeDefinition() != typeof(CustomResourceEventHandler<>))
|
||||
throw new Exception($"Unsupported event handler type in event `{type.Name}.{ei.Name}`");
|
||||
|
||||
|
||||
var argType = ei.EventHandlerType.GenericTypeArguments[0];
|
||||
var evtType = Tru.FromType(argType);
|
||||
|
||||
if (evtType == null)
|
||||
throw new Exception($"Unsupported type `{argType}` in event `{type.Name}.{ei.Name}`");
|
||||
|
||||
var annotationAttrs = ei.GetCustomAttributes<AnnotationAttribute>(true);
|
||||
var subscribableAttr = ei.GetCustomAttribute<SubscribableAttribute>(true);
|
||||
|
||||
//evtType.Nullable = new NullabilityInfoContext().Create(ei).ReadState is NullabilityState.Nullable;
|
||||
|
||||
var nullableAttr = ei.GetCustomAttributes().FirstOrDefault(x => x.GetType().Name == "System.Runtime.CompilerServices.NullableAttribute");// .GetCustomAttribute<NullableAttribute>(true);
|
||||
var nullableContextAttr = ei.GetCustomAttributes().FirstOrDefault(x => x.GetType().Name == "System.Runtime.CompilerServices.NullableContextAttribute");// ei.GetCustomAttribute<NullableContextAttribute>(true);
|
||||
|
||||
|
||||
var nullableAttrFlags = (nullableAttr?.GetType().GetField("NullableFlags")?.GetValue(nullableAttr) as byte[] ?? new byte[0]).ToList();
|
||||
var nullableContextAttrFlag = (byte)(nullableContextAttr?.GetType().GetField("Flag")?.GetValue(nullableContextAttr) ?? (byte)0);
|
||||
|
||||
//var flags = nullableAttr?.Flags?.ToList() ?? new List<byte>();
|
||||
//var flags = ((byte[])nullableAttr?.NullableFlags ?? new byte[0]).ToList();
|
||||
|
||||
// skip the eventHandler class
|
||||
if (nullableAttrFlags.Count > 1)
|
||||
nullableAttrFlags = nullableAttrFlags.Skip(1).ToList();
|
||||
|
||||
if (nullableContextAttrFlag == 2)
|
||||
{
|
||||
if (nullableAttrFlags.Count == 1)
|
||||
evtType.SetNotNull(nullableAttrFlags.FirstOrDefault());
|
||||
else
|
||||
evtType.SetNotNull(nullableAttrFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nullableAttrFlags.Count == 1)
|
||||
evtType.SetNull(nullableAttrFlags.FirstOrDefault());
|
||||
else
|
||||
evtType.SetNull(nullableAttrFlags);
|
||||
}
|
||||
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
if (annotationAttrs != null && annotationAttrs.Count() > 0)
|
||||
{
|
||||
annotations = new Map<string, string>();
|
||||
foreach (var attr in annotationAttrs)
|
||||
annotations.Add(attr.Key, attr.Value);
|
||||
}
|
||||
|
||||
|
||||
return new EventDef()
|
||||
{
|
||||
Name = name,
|
||||
ArgumentType = evtType,
|
||||
Index = index,
|
||||
Inherited = ei.DeclaringType != type,
|
||||
Annotations = annotations,
|
||||
EventInfo = ei,
|
||||
Subscribable = subscribableAttr != null
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
305
Libraries/Esiur/Data/Types/FunctionDef.cs
Normal file
305
Libraries/Esiur/Data/Types/FunctionDef.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Protocol;
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
public class FunctionDef : MemberDef
|
||||
{
|
||||
|
||||
public Map<string, string> Annotations
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
//public bool IsVoid
|
||||
//{
|
||||
// get;
|
||||
// set;
|
||||
//}
|
||||
|
||||
public Tru ReturnType { get; set; }
|
||||
|
||||
public bool IsStatic { get; set; }
|
||||
|
||||
public ArgumentDef[] Arguments { get; set; }
|
||||
|
||||
public MethodInfo MethodInfo
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public static (uint, FunctionDef) Parse(byte[] data, uint offset, byte index, bool inherited)
|
||||
{
|
||||
|
||||
var oOffset = offset;
|
||||
|
||||
var isStatic = ((data[offset] & 0x4) == 0x4);
|
||||
var hasAnnotation = ((data[offset++] & 0x10) == 0x10);
|
||||
|
||||
var name = data.GetString(offset + 1, data[offset]);
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
// return type
|
||||
var (rts, returnType) = Tru.Parse(data, offset);
|
||||
offset += rts;
|
||||
|
||||
// arguments count
|
||||
var argsCount = data[offset++];
|
||||
List<ArgumentDef> arguments = new();
|
||||
|
||||
for (var a = 0; a < argsCount; a++)
|
||||
{
|
||||
var (cs, argType) = ArgumentDef.Parse(data, offset, a);
|
||||
arguments.Add(argType);
|
||||
offset += cs;
|
||||
}
|
||||
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
// arguments
|
||||
if (hasAnnotation) // Annotation ?
|
||||
{
|
||||
var (len, anns) = Codec.ParseSync(data, offset, null);
|
||||
|
||||
if (anns is Map<string, string> map)
|
||||
annotations = map;
|
||||
|
||||
offset += len;
|
||||
}
|
||||
|
||||
return (offset - oOffset, new FunctionDef()
|
||||
{
|
||||
Index = index,
|
||||
Name = name,
|
||||
Arguments = arguments.ToArray(),
|
||||
IsStatic = isStatic,
|
||||
Inherited = inherited,
|
||||
Annotations = annotations,
|
||||
ReturnType = returnType,
|
||||
});
|
||||
}
|
||||
|
||||
public byte[] Compose()
|
||||
{
|
||||
|
||||
var name = DC.ToBytes(Name);
|
||||
|
||||
var bl = new BinaryList()
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(ReturnType.Compose())
|
||||
.AddUInt8((byte)Arguments.Length);
|
||||
|
||||
for (var i = 0; i < Arguments.Length; i++)
|
||||
bl.AddUInt8Array(Arguments[i].Compose());
|
||||
|
||||
|
||||
if (Annotations != null)
|
||||
{
|
||||
var exp = Codec.Compose(Annotations, null, null);// DC.ToBytes(Annotation);
|
||||
bl.AddUInt8Array(exp);
|
||||
bl.InsertUInt8(0, (byte)((Inherited ? (byte)0x90 : (byte)0x10) | (IsStatic ? 0x4 : 0)));
|
||||
}
|
||||
else
|
||||
bl.InsertUInt8(0, (byte)((Inherited ? (byte)0x80 : (byte)0x0) | (IsStatic ? 0x4 : 0)));
|
||||
|
||||
return bl.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public static FunctionDef MakeFunctionDef(Type type, MethodInfo mi, byte index, string name, TypeDef schema)
|
||||
{
|
||||
|
||||
var genericRtType = mi.ReturnType.IsGenericType ? mi.ReturnType.GetGenericTypeDefinition() : null;
|
||||
|
||||
Tru rtType;
|
||||
|
||||
if (genericRtType == typeof(AsyncReply<>))
|
||||
{
|
||||
rtType = Tru.FromType(mi.ReturnType.GetGenericArguments()[0]);
|
||||
}
|
||||
else if (genericRtType == typeof(Task<>))
|
||||
{
|
||||
rtType = Tru.FromType(mi.ReturnType.GetGenericArguments()[0]);
|
||||
}
|
||||
else if (genericRtType == typeof(IEnumerable<>) || genericRtType == typeof(IAsyncEnumerable<>))
|
||||
{
|
||||
// get export
|
||||
rtType = Tru.FromType(mi.ReturnType.GetGenericArguments()[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mi.ReturnType == typeof(Task))
|
||||
rtType = Tru.FromType(null);
|
||||
else
|
||||
rtType = Tru.FromType(mi.ReturnType);
|
||||
}
|
||||
|
||||
if (rtType == null)
|
||||
throw new Exception($"Unsupported type `{mi.ReturnType}` in method `{type.Name}.{mi.Name}` return");
|
||||
|
||||
var annotationAttrs = mi.GetCustomAttributes<AnnotationAttribute>(true);
|
||||
|
||||
//var nullabilityInfoContext = new NullabilityInfoContext();
|
||||
//rtType.Nullable = nullabilityInfoContext.Create(mi.ReturnParameter).WriteState is NullabilityState.Nullable;
|
||||
|
||||
|
||||
var nullableAttr = mi.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
|
||||
var nullableContextAttr = mi.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
|
||||
|
||||
var nullableAttrFlags = (nullableAttr?.GetType().GetField("NullableFlags")?.GetValue(nullableAttr) as byte[] ?? new byte[0]).ToList();
|
||||
var nullableContextAttrFlag = (byte)(nullableContextAttr?.GetType().GetField("Flag")?.GetValue(nullableContextAttr) ?? (byte)0);
|
||||
|
||||
//var flags = ((byte[])nullableAttr?.NullableFlags ?? new byte[0]).ToList();
|
||||
|
||||
//var rtNullableAttr = mi.ReturnTypeCustomAttributes.GetCustomAttributes(typeof(NullableAttribute), true).FirstOrDefault() as NullableAttribute;
|
||||
var rtNullableAttr = mi.ReturnTypeCustomAttributes.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
|
||||
|
||||
|
||||
|
||||
// var rtNullableContextAttr = mi.ReturnTypeCustomAttributes
|
||||
// .GetCustomAttributes(typeof(NullableContextAttribute), true)
|
||||
// .FirstOrDefault() as NullableContextAttribute
|
||||
// ?? nullableContextAttr;
|
||||
|
||||
|
||||
var rtNullableContextAttr = mi.ReturnTypeCustomAttributes
|
||||
.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().Name == "NullableContextAttribute")
|
||||
?? nullableContextAttr;
|
||||
|
||||
var rtNullableAttrFlags = (rtNullableAttr?.GetType().GetField("NullableFlags")?.GetValue(rtNullableAttr) as byte[] ?? new byte[0]).ToList();
|
||||
var rtNullableContextAttrFlag = (byte)(rtNullableContextAttr?.GetType().GetField("Flag")?.GetValue(rtNullableContextAttr) ?? (byte)0);
|
||||
|
||||
//var rtFlags = rtNullableAttr?.Flags?.ToList() ?? new List<byte>();
|
||||
//var rtFlags = ((byte[])rtNullableAttr?.NullableFlags ?? new byte[0]).ToList();
|
||||
|
||||
if (rtNullableAttrFlags.Count > 0 && genericRtType == typeof(AsyncReply<>))
|
||||
rtNullableAttrFlags.RemoveAt(0);
|
||||
|
||||
if (rtNullableContextAttrFlag == 2)
|
||||
{
|
||||
if (rtNullableAttrFlags.Count == 1)
|
||||
rtType.SetNotNull(rtNullableAttrFlags.FirstOrDefault());
|
||||
else
|
||||
rtType.SetNotNull(rtNullableAttrFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rtNullableAttrFlags.Count == 1)
|
||||
rtType.SetNull(rtNullableAttrFlags.FirstOrDefault());
|
||||
else
|
||||
rtType.SetNull(rtNullableAttrFlags);
|
||||
}
|
||||
|
||||
var args = mi.GetParameters();
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
if (args.Last().ParameterType == typeof(EpConnection)
|
||||
|| args.Last().ParameterType == typeof(InvocationContext))
|
||||
args = args.Take(args.Count() - 1).ToArray();
|
||||
}
|
||||
|
||||
var arguments = args.Select(x =>
|
||||
{
|
||||
var argType = Tru.FromType(x.ParameterType);
|
||||
|
||||
if (argType == null)
|
||||
throw new Exception($"Unsupported type `{x.ParameterType}` in method `{type.Name}.{mi.Name}` parameter `{x.Name}`");
|
||||
|
||||
|
||||
var argAnnotationAttrs = x.GetCustomAttributes<AnnotationAttribute>(true);
|
||||
|
||||
|
||||
var argNullableAttr = x.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
|
||||
var argNullableContextAttr = x.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttr");
|
||||
|
||||
var argNullableAttrFlags = (argNullableAttr?.GetType().GetField("NullableFlags")?.GetValue(argNullableAttr) as byte[] ?? new byte[0]).ToList();
|
||||
var argNullableContextAttrFlag = (byte)(argNullableAttr?.GetType().GetField("Flag")?.GetValue(argNullableAttr) ?? (byte)0);
|
||||
|
||||
if (argNullableContextAttrFlag == 2)
|
||||
{
|
||||
if (argNullableAttrFlags.Count == 1)
|
||||
argType.SetNotNull(argNullableAttrFlags.FirstOrDefault());
|
||||
else
|
||||
argType.SetNotNull(argNullableAttrFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (argNullableAttrFlags.Count == 1)
|
||||
argType.SetNull(argNullableAttrFlags.FirstOrDefault());
|
||||
else
|
||||
argType.SetNull(argNullableAttrFlags);
|
||||
}
|
||||
|
||||
Map<string, string> argAnn = null;
|
||||
|
||||
if (argAnnotationAttrs != null && argAnnotationAttrs.Count() > 0)
|
||||
{
|
||||
argAnn = new Map<string, string>();
|
||||
foreach (var attr in argAnnotationAttrs)
|
||||
argAnn.Add(attr.Key, attr.Value);
|
||||
}
|
||||
|
||||
return new ArgumentDef()
|
||||
{
|
||||
Name = x.Name,
|
||||
Type = argType,
|
||||
ParameterInfo = x,
|
||||
Optional = x.IsOptional,
|
||||
Annotations = argAnn
|
||||
};
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
if (annotationAttrs != null && annotationAttrs.Count() > 0)
|
||||
{
|
||||
annotations = new Map<string, string>();
|
||||
foreach (var attr in annotationAttrs)
|
||||
annotations.Add(attr.Key, attr.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
annotations = new Map<string, string>();
|
||||
annotations.Add("", "(" + String.Join(",",
|
||||
mi.GetParameters().Where(x => x.ParameterType != typeof(EpConnection))
|
||||
.Select(x => "[" + x.ParameterType.Name + "] " + x.Name)) + ") -> " + mi.ReturnType.Name);
|
||||
|
||||
}
|
||||
|
||||
return new FunctionDef()
|
||||
{
|
||||
Name = name,
|
||||
Index = index,
|
||||
Inherited = mi.DeclaringType != type,
|
||||
IsStatic = mi.IsStatic,
|
||||
ReturnType = rtType,
|
||||
Arguments = arguments,
|
||||
MethodInfo = mi,
|
||||
Annotations = annotations
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
//return = "(" + String.Join(",", mi.GetParameters().Where(x => x.ParameterType != typeof(EpConnection)).Select(x => "[" + x.ParameterType.Name + "] " + x.Name)) + ") -> " + mi.ReturnType.Name;
|
||||
|
||||
return $"{ReturnType} {Name}({string.Join(", ", Arguments.Select(a => a.ToString()))})";
|
||||
}
|
||||
|
||||
}
|
||||
90
Libraries/Esiur/Data/Types/MemberData.cs
Normal file
90
Libraries/Esiur/Data/Types/MemberData.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class MemberData
|
||||
{
|
||||
public MemberInfo Info;
|
||||
public string Name;
|
||||
public int Order;
|
||||
//public bool Inherited;
|
||||
public MemberData? Parent;
|
||||
public MemberData? Child;
|
||||
public byte Index;
|
||||
|
||||
public PropertyPermission PropertyPermission;
|
||||
|
||||
//public ExportAttribute ExportAttribute;
|
||||
|
||||
|
||||
//public string Name => ExportAttribute?.Name ?? Info.Name;
|
||||
|
||||
public MemberData(MemberInfo info, int order)
|
||||
{
|
||||
var exportAttr = info.GetCustomAttribute<ExportAttribute>();
|
||||
|
||||
if (info is PropertyInfo pi)
|
||||
{
|
||||
if (exportAttr != null && exportAttr.Permission.HasValue)
|
||||
{
|
||||
if ((exportAttr.Permission == PropertyPermission.Write
|
||||
|| exportAttr.Permission == PropertyPermission.ReadWrite) && !pi.CanWrite)
|
||||
{
|
||||
throw new Exception($"Property '{pi.Name}' does not have a setter, but ExportAttribute specifies it as writable.");
|
||||
}
|
||||
|
||||
if ((exportAttr.Permission == PropertyPermission.Read
|
||||
|| exportAttr.Permission == PropertyPermission.ReadWrite) && !pi.CanRead)
|
||||
{
|
||||
throw new Exception($"Property '{pi.Name}' does not have a getter, but ExportAttribute specifies it as readable.");
|
||||
}
|
||||
|
||||
this.PropertyPermission = exportAttr.Permission.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.PropertyPermission = (pi.CanRead && pi.CanWrite) ? PropertyPermission.ReadWrite
|
||||
: pi.CanWrite ? PropertyPermission.Write
|
||||
: PropertyPermission.Read;
|
||||
}
|
||||
}
|
||||
|
||||
this.Name = exportAttr?.Name ?? info.Name;
|
||||
this.Info = info;
|
||||
this.Order = order;
|
||||
}
|
||||
|
||||
public MemberInfo GetMemberInfo()
|
||||
{
|
||||
var rt = Info;
|
||||
var md = Child;
|
||||
while (md != null)
|
||||
{
|
||||
rt = Info;
|
||||
md = md.Child;
|
||||
}
|
||||
return rt;
|
||||
}
|
||||
|
||||
//public string? GetAnnotation()
|
||||
//{
|
||||
// string? rt = null;
|
||||
// var md = this;
|
||||
// while (md != null)
|
||||
// {
|
||||
// var annotationAttr = md.Info.GetCustomAttribute<AnnotationAttribute>();
|
||||
// if (annotationAttr != null)
|
||||
// rt = annotationAttr.Annotation;
|
||||
// md = md.Child;
|
||||
// }
|
||||
|
||||
// return rt;
|
||||
//}
|
||||
}
|
||||
|
||||
21
Libraries/Esiur/Data/Types/MemberDef.cs
Normal file
21
Libraries/Esiur/Data/Types/MemberDef.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Esiur.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
public class MemberDef
|
||||
{
|
||||
|
||||
public byte Index { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool Inherited { get; set; }
|
||||
public TypeDef Definition { get; set; }
|
||||
|
||||
|
||||
public string Fullname => Definition.Name + "." + Name;
|
||||
|
||||
}
|
||||
|
||||
285
Libraries/Esiur/Data/Types/PropertyDef.cs
Normal file
285
Libraries/Esiur/Data/Types/PropertyDef.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using Esiur.Data;
|
||||
using Esiur.Protocol;
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
public class PropertyDef : MemberDef
|
||||
{
|
||||
public Map<string, string> Annotations { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
public PropertyInfo PropertyInfo
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Tru ValueType { get; set; }
|
||||
|
||||
|
||||
/*
|
||||
public bool Serilize
|
||||
{
|
||||
get;set;
|
||||
}
|
||||
*/
|
||||
//bool ReadOnly;
|
||||
//EPTypes::DataType ReturnType;
|
||||
public PropertyPermission Permission
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
//public bool IsNullable { get; set; }
|
||||
|
||||
public bool HasHistory
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/*
|
||||
public PropertyType Mode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}*/
|
||||
|
||||
//public string ReadAnnotation
|
||||
//{
|
||||
// get;
|
||||
// set;
|
||||
//}
|
||||
|
||||
//public string WriteAnnotation
|
||||
//{
|
||||
// get;
|
||||
// set;
|
||||
//}
|
||||
|
||||
/*
|
||||
public bool Storable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}*/
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}: {ValueType}";
|
||||
}
|
||||
|
||||
public static (uint, PropertyDef) Parse(byte[] data, uint offset, byte index, bool inherited)
|
||||
{
|
||||
var oOffset = offset;
|
||||
|
||||
|
||||
|
||||
var hasAnnotation = ((data[offset] & 0x8) == 0x8);
|
||||
var hasHistory = ((data[offset] & 1) == 1);
|
||||
var permission = (PropertyPermission)((data[offset++] >> 1) & 0x3);
|
||||
var name = data.GetString(offset + 1, data[offset]);
|
||||
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
var (dts, valueType) = Tru.Parse(data, offset);
|
||||
|
||||
offset += dts;
|
||||
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
// arguments
|
||||
if (hasAnnotation) // Annotation ?
|
||||
{
|
||||
var (len, anns) = Codec.ParseSync(data, offset, null);
|
||||
|
||||
if (anns is Map<string, string> map)
|
||||
annotations = map;
|
||||
|
||||
offset += len;
|
||||
}
|
||||
|
||||
return (offset - oOffset, new PropertyDef()
|
||||
{
|
||||
Index = index,
|
||||
Name = name,
|
||||
Inherited = inherited,
|
||||
Permission = permission,
|
||||
HasHistory = hasHistory,
|
||||
ValueType = valueType,
|
||||
Annotations = annotations
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public byte[] Compose()
|
||||
{
|
||||
var name = DC.ToBytes(Name);
|
||||
|
||||
var pv = ((byte)(Permission) << 1) | (HasHistory ? 1 : 0);
|
||||
|
||||
if (Inherited)
|
||||
pv |= 0x80;
|
||||
|
||||
//if (WriteAnnotation != null && ReadAnnotation != null)
|
||||
//{
|
||||
// var rexp = DC.ToBytes(ReadAnnotation);
|
||||
// var wexp = DC.ToBytes(WriteAnnotation);
|
||||
// return new BinaryList()
|
||||
// .AddUInt8((byte)(0x38 | pv))
|
||||
// .AddUInt8((byte)name.Length)
|
||||
// .AddUInt8Array(name)
|
||||
// .AddUInt8Array(ValueType.Compose())
|
||||
// .AddInt32(wexp.Length)
|
||||
// .AddUInt8Array(wexp)
|
||||
// .AddInt32(rexp.Length)
|
||||
// .AddUInt8Array(rexp)
|
||||
// .ToArray();
|
||||
//}
|
||||
//else if (WriteAnnotation != null)
|
||||
//{
|
||||
// var wexp = DC.ToBytes(WriteAnnotation);
|
||||
// return new BinaryList()
|
||||
// .AddUInt8((byte)(0x30 | pv))
|
||||
// .AddUInt8((byte)name.Length)
|
||||
// .AddUInt8Array(name)
|
||||
// .AddUInt8Array(ValueType.Compose())
|
||||
// .AddInt32(wexp.Length)
|
||||
// .AddUInt8Array(wexp)
|
||||
// .ToArray();
|
||||
//}
|
||||
//else if (ReadAnnotation != null)
|
||||
//{
|
||||
// var rexp = DC.ToBytes(ReadAnnotation);
|
||||
// return new BinaryList()
|
||||
// .AddUInt8((byte)(0x28 | pv))
|
||||
// .AddUInt8((byte)name.Length)
|
||||
// .AddUInt8Array(name)
|
||||
// .AddUInt8Array(ValueType.Compose())
|
||||
// .AddInt32(rexp.Length)
|
||||
// .AddUInt8Array(rexp)
|
||||
// .ToArray();
|
||||
//}
|
||||
if (Annotations != null)
|
||||
{
|
||||
var rexp = Codec.Compose(Annotations, null, null);
|
||||
return new BinaryList()
|
||||
.AddUInt8((byte)(0x28 | pv))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(ValueType.Compose())
|
||||
.AddUInt8Array(rexp)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new BinaryList()
|
||||
.AddUInt8((byte)(0x20 | pv))
|
||||
.AddUInt8((byte)name.Length)
|
||||
.AddUInt8Array(name)
|
||||
.AddUInt8Array(ValueType.Compose())
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static PropertyDef MakePropertyDef(Type type, PropertyInfo pi, string name, byte index, PropertyPermission permission, TypeDef schema)
|
||||
{
|
||||
var genericPropType = pi.PropertyType.IsGenericType ? pi.PropertyType.GetGenericTypeDefinition() : null;
|
||||
|
||||
var propType = genericPropType == typeof(PropertyContext<>) ?
|
||||
Tru.FromType(pi.PropertyType.GetGenericArguments()[0]) :
|
||||
Tru.FromType(pi.PropertyType);
|
||||
|
||||
if (propType == null)
|
||||
throw new Exception($"Unsupported type `{pi.PropertyType}` in property `{type.Name}.{pi.Name}`");
|
||||
|
||||
var annotationAttrs = pi.GetCustomAttributes<AnnotationAttribute>(true);
|
||||
var storageAttr = pi.GetCustomAttribute<StorageAttribute>(true);
|
||||
|
||||
//var nullabilityContext = new NullabilityInfoContext();
|
||||
//propType.Nullable = nullabilityContext.Create(pi).ReadState is NullabilityState.Nullable;
|
||||
|
||||
var nullableAttr = pi.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
|
||||
var nullableContextAttr = pi.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
|
||||
|
||||
var nullableAttrFlags = (nullableAttr?.GetType().GetField("NullableFlags")?.GetValue(nullableAttr) as byte[] ?? new byte[0]).ToList();
|
||||
var nullableContextAttrFlag = (byte)(nullableContextAttr?.GetType().GetField("Flag")?.GetValue(nullableContextAttr) ?? (byte)0);
|
||||
|
||||
|
||||
//var nullableAttr = pi.GetCustomAttribute<NullableAttribute>(true);
|
||||
//var flags = ((byte[]) nullableAttr?.NullableFlags ?? new byte[0]).ToList();
|
||||
|
||||
if (nullableAttrFlags.Count > 0 && genericPropType == typeof(PropertyContext<>))
|
||||
nullableAttrFlags.RemoveAt(0);
|
||||
|
||||
if (nullableContextAttrFlag == 2)
|
||||
{
|
||||
if (nullableAttrFlags.Count == 1)
|
||||
propType.SetNotNull(nullableAttrFlags.FirstOrDefault());
|
||||
else
|
||||
propType.SetNotNull(nullableAttrFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nullableAttrFlags.Count == 1)
|
||||
propType.SetNull(nullableAttrFlags.FirstOrDefault());
|
||||
else
|
||||
propType.SetNull(nullableAttrFlags);
|
||||
}
|
||||
|
||||
|
||||
Map<string, string> annotations = null;
|
||||
|
||||
if (annotationAttrs != null && annotationAttrs.Count() > 0)
|
||||
{
|
||||
annotations = new Map<string, string>();
|
||||
foreach (var attr in annotationAttrs)
|
||||
annotations.Add(attr.Key, attr.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
annotations = new Map<string, string>();
|
||||
annotations.Add("", GetTypeAnnotationName(pi.PropertyType));
|
||||
}
|
||||
|
||||
|
||||
return new PropertyDef()
|
||||
{
|
||||
Name = name,
|
||||
Index = index,
|
||||
Inherited = pi.DeclaringType != type,
|
||||
ValueType = propType,
|
||||
PropertyInfo = pi,
|
||||
HasHistory = storageAttr == null ? false : storageAttr.Mode == StorageMode.History,
|
||||
Permission = permission,
|
||||
Annotations = annotations,
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static string GetTypeAnnotationName(Type type)
|
||||
{
|
||||
var nullType = Nullable.GetUnderlyingType(type);
|
||||
if (nullType == null)
|
||||
return type.Name;
|
||||
else
|
||||
return type.Name + "?";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
759
Libraries/Esiur/Data/Types/TypeDef.cs
Normal file
759
Libraries/Esiur/Data/Types/TypeDef.cs
Normal file
@@ -0,0 +1,759 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Data;
|
||||
using Esiur.Core;
|
||||
using System.Security.Cryptography;
|
||||
using Esiur.Proxy;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Esiur.Resource;
|
||||
using Esiur.Protocol;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
|
||||
public class TypeDef
|
||||
{
|
||||
|
||||
protected Uuid typeId;
|
||||
protected Uuid? parentId;
|
||||
|
||||
public Map<string, string> Annotations { get; set; }
|
||||
|
||||
string typeName;
|
||||
List<FunctionDef> functions = new List<FunctionDef>();
|
||||
List<EventDef> events = new List<EventDef>();
|
||||
List<PropertyDef> properties = new List<PropertyDef>();
|
||||
List<AttributeDef> attributes = new List<AttributeDef>();
|
||||
List<ConstantDef> constants = new();
|
||||
int version;
|
||||
TypeDefKind typeDefKind;
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return typeName;
|
||||
}
|
||||
|
||||
|
||||
protected byte[] content;
|
||||
|
||||
public Uuid? ParentId => parentId;
|
||||
|
||||
public byte[] Content
|
||||
{
|
||||
get { return content; }
|
||||
}
|
||||
|
||||
public TypeDefKind Kind => typeDefKind;
|
||||
|
||||
|
||||
public Type DefinedType { get; set; }
|
||||
public Type ParentDefinedType { get; set; }
|
||||
|
||||
|
||||
public EventDef GetEventDefByName(string eventName)
|
||||
{
|
||||
foreach (var i in events)
|
||||
if (i.Name == eventName)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public EventDef GetEventDefByIndex(byte index)
|
||||
{
|
||||
foreach (var i in events)
|
||||
if (i.Index == index)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public FunctionDef GetFunctionDefByName(string functionName)
|
||||
{
|
||||
foreach (var i in functions)
|
||||
if (i.Name == functionName)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
public FunctionDef GetFunctionDefByIndex(byte index)
|
||||
{
|
||||
foreach (var i in functions)
|
||||
if (i.Index == index)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public PropertyDef GetPropertyDefByIndex(byte index)
|
||||
{
|
||||
foreach (var i in properties)
|
||||
if (i.Index == index)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public PropertyDef GetPropertyDefByName(string propertyName)
|
||||
{
|
||||
foreach (var i in properties)
|
||||
if (i.Name == propertyName)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public AttributeDef GetAttributeDef(string attributeName)
|
||||
{
|
||||
foreach (var i in attributes)
|
||||
if (i.Name == attributeName)
|
||||
return i;
|
||||
return null;
|
||||
}
|
||||
|
||||
public Uuid Id
|
||||
{
|
||||
get { return typeId; }
|
||||
}
|
||||
public string Name
|
||||
{
|
||||
get { return typeName; }
|
||||
}
|
||||
|
||||
|
||||
public FunctionDef[] Functions
|
||||
{
|
||||
get { return functions.ToArray(); }
|
||||
}
|
||||
|
||||
public EventDef[] Events
|
||||
{
|
||||
get { return events.ToArray(); }
|
||||
}
|
||||
|
||||
public PropertyDef[] Properties
|
||||
{
|
||||
get { return properties.ToArray(); }
|
||||
}
|
||||
|
||||
public ConstantDef[] Constants => constants.ToArray();
|
||||
|
||||
public TypeDef()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static Uuid GetTypeUUID(Type type)
|
||||
{
|
||||
var attr = type.GetCustomAttribute<TypeIdAttribute>();
|
||||
if (attr != null)
|
||||
return attr.Id;
|
||||
|
||||
var tn = Encoding.UTF8.GetBytes(GetTypeName(type));
|
||||
var hash = SHA256.Create().ComputeHash(tn).Clip(0, 16);
|
||||
hash[6] = (byte)((hash[6] & 0xF) | 0x80);
|
||||
hash[8] = (byte)((hash[8] & 0xF) | 0x80);
|
||||
|
||||
var rt = new Uuid(hash);
|
||||
return rt;
|
||||
}
|
||||
|
||||
static Type[] GetDistributedTypes(Type type)
|
||||
{
|
||||
if (type.IsArray)
|
||||
return GetDistributedTypes(type.GetElementType());
|
||||
else if (type.IsEnum)
|
||||
return new Type[] { type };
|
||||
else if (Codec.ImplementsInterface(type, typeof(IRecord))
|
||||
|| Codec.ImplementsInterface(type, typeof(IResource)))
|
||||
{
|
||||
return new Type[] { type };
|
||||
}
|
||||
else if (type.IsGenericType)
|
||||
{
|
||||
var genericType = type.GetGenericTypeDefinition();
|
||||
var genericTypeArgs = type.GetGenericArguments();
|
||||
|
||||
if (genericType == typeof(List<>)
|
||||
|| genericType == typeof(PropertyContext<>)
|
||||
|| genericType == typeof(AsyncReply<>)
|
||||
|| genericType == typeof(ResourceLink<>))
|
||||
{
|
||||
return GetDistributedTypes(genericTypeArgs[0]);
|
||||
}
|
||||
else if (genericType == typeof(Tuple<>)
|
||||
|| genericType == typeof(Map<,>))
|
||||
{
|
||||
var rt = new List<Type>();
|
||||
for (var i = 0; i < genericTypeArgs.Length; i++)
|
||||
{
|
||||
var depTypes = GetDistributedTypes(genericTypeArgs[i]);
|
||||
foreach (var depType in depTypes)
|
||||
if (!rt.Contains(depType))
|
||||
rt.Add(depType);
|
||||
}
|
||||
|
||||
return rt.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new Type[0];
|
||||
}
|
||||
|
||||
|
||||
public static TypeDef[] GetDependencies(TypeDef schema, Warehouse warehouse)
|
||||
{
|
||||
|
||||
var list = new List<TypeDef>();
|
||||
|
||||
// Add self
|
||||
list.Add(schema);
|
||||
|
||||
|
||||
Action<TypeDef, List<TypeDef>> getDependenciesFunc = null;
|
||||
|
||||
getDependenciesFunc = (TypeDef sch, List<TypeDef> bag) =>
|
||||
{
|
||||
if (schema.DefinedType == null)
|
||||
return;
|
||||
|
||||
// Add parents
|
||||
var parentType = sch.ParentDefinedType;
|
||||
|
||||
// Get parents
|
||||
while (parentType != null)
|
||||
{
|
||||
var parentTypeDef = warehouse.GetTypeDefByType(parentType);
|
||||
if (parentTypeDef != null)
|
||||
{
|
||||
list.Add(parentTypeDef);
|
||||
parentType = parentTypeDef.ParentDefinedType;
|
||||
}
|
||||
}
|
||||
|
||||
// functions
|
||||
foreach (var f in sch.functions)
|
||||
{
|
||||
var functionReturnTypes = GetDistributedTypes(f.MethodInfo.ReturnType);
|
||||
|
||||
foreach (var functionReturnType in functionReturnTypes)
|
||||
{
|
||||
var functionReturnTypeDef = warehouse.GetTypeDefByType(functionReturnType);
|
||||
if (functionReturnTypeDef != null)
|
||||
{
|
||||
if (!bag.Contains(functionReturnTypeDef))
|
||||
{
|
||||
list.Add(functionReturnTypeDef);
|
||||
getDependenciesFunc(functionReturnTypeDef, bag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var args = f.MethodInfo.GetParameters();
|
||||
|
||||
for (var i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
var fpTypes = GetDistributedTypes(args[i].ParameterType);
|
||||
|
||||
foreach (var fpType in fpTypes)
|
||||
{
|
||||
var fpt = warehouse.GetTypeDefByType(fpType);
|
||||
if (fpt != null)
|
||||
{
|
||||
if (!bag.Contains(fpt))
|
||||
{
|
||||
bag.Add(fpt);
|
||||
getDependenciesFunc(fpt, bag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip EpConnection argument
|
||||
if (args.Length > 0)
|
||||
{
|
||||
var last = args.Last();
|
||||
if (last.ParameterType != typeof(EpConnection))
|
||||
{
|
||||
|
||||
var fpTypes = GetDistributedTypes(last.ParameterType);
|
||||
|
||||
foreach (var fpType in fpTypes)
|
||||
{
|
||||
var fpt = warehouse.GetTypeDefByType(fpType);
|
||||
if (fpt != null)
|
||||
{
|
||||
if (!bag.Contains(fpt))
|
||||
{
|
||||
bag.Add(fpt);
|
||||
getDependenciesFunc(fpt, bag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// properties
|
||||
foreach (var p in sch.properties)
|
||||
{
|
||||
var propertyTypes = GetDistributedTypes(p.PropertyInfo.PropertyType);
|
||||
|
||||
foreach (var propertyType in propertyTypes)
|
||||
{
|
||||
var propertyTypeDef = warehouse.GetTypeDefByType(propertyType);
|
||||
if (propertyTypeDef != null)
|
||||
{
|
||||
if (!bag.Contains(propertyTypeDef))
|
||||
{
|
||||
bag.Add(propertyTypeDef);
|
||||
getDependenciesFunc(propertyTypeDef, bag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// events
|
||||
foreach (var e in sch.events)
|
||||
{
|
||||
var eventTypes = GetDistributedTypes(e.EventInfo.EventHandlerType.GenericTypeArguments[0]);
|
||||
|
||||
foreach (var eventType in eventTypes)
|
||||
{
|
||||
var eventTypeDef = warehouse.GetTypeDefByType(eventType);
|
||||
|
||||
if (eventTypeDef != null)
|
||||
{
|
||||
if (!bag.Contains(eventTypeDef))
|
||||
{
|
||||
bag.Add(eventTypeDef);
|
||||
getDependenciesFunc(eventTypeDef, bag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getDependenciesFunc(schema, list);
|
||||
return list.Distinct().ToArray();
|
||||
}
|
||||
|
||||
|
||||
public static string GetTypeName(Type type, char separator = '.')
|
||||
{
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var index = type.Name.IndexOf("`");
|
||||
var name = $"{type.Namespace}{separator}{((index > -1) ? type.Name.Substring(0, index) : type.Name)}Of";
|
||||
foreach (var t in type.GenericTypeArguments)
|
||||
name += GetTypeName(t, '_');
|
||||
|
||||
return name;
|
||||
}
|
||||
else
|
||||
return $"{type.Namespace?.Replace('.', separator) ?? "Global"}{separator}{type.Name}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public bool IsWrapper { get; private set; }
|
||||
|
||||
public TypeDef(Type type, Warehouse warehouse = null)
|
||||
{
|
||||
if (Codec.ImplementsInterface(type, typeof(IResource)))
|
||||
typeDefKind = TypeDefKind.Resource;
|
||||
else if (Codec.ImplementsInterface(type, typeof(IRecord)))
|
||||
typeDefKind = TypeDefKind.Record;
|
||||
else if (type.IsEnum)
|
||||
typeDefKind = TypeDefKind.Enum;
|
||||
else
|
||||
throw new Exception("Type must implement IResource, IRecord or inherit from DistributedResource.");
|
||||
|
||||
IsWrapper = Codec.InheritsClass(type, typeof(EpResource));
|
||||
|
||||
type = ResourceProxy.GetBaseType(type);
|
||||
|
||||
DefinedType = type;
|
||||
|
||||
typeName = GetTypeName(type);
|
||||
|
||||
// set guid
|
||||
typeId = GetTypeUUID(type);
|
||||
|
||||
if (warehouse != null)
|
||||
warehouse.RegisterTypeDef(this);
|
||||
|
||||
var hierarchy = GetHierarchy(type);
|
||||
|
||||
if (hierarchy.ContainsKey(MemberTypes.Field))
|
||||
{
|
||||
foreach (var cd in hierarchy[MemberTypes.Field])
|
||||
{
|
||||
constants.Add(ConstantDef.MakeConstantDef
|
||||
(type, (FieldInfo)cd.GetMemberInfo(), cd.Index, cd.Name, this));
|
||||
}
|
||||
}
|
||||
|
||||
if (hierarchy.ContainsKey(MemberTypes.Property))
|
||||
{
|
||||
foreach (var pd in hierarchy[MemberTypes.Property])
|
||||
{
|
||||
properties.Add(PropertyDef.MakePropertyDef
|
||||
(type, (PropertyInfo)pd.GetMemberInfo(), pd.Name, pd.Index, pd.PropertyPermission, this));
|
||||
}
|
||||
}
|
||||
|
||||
if (typeDefKind == TypeDefKind.Resource)
|
||||
{
|
||||
if (hierarchy.ContainsKey(MemberTypes.Method))
|
||||
{
|
||||
foreach (var fd in hierarchy[MemberTypes.Method])
|
||||
{
|
||||
functions.Add(FunctionDef.MakeFunctionDef
|
||||
(type, (MethodInfo)fd.GetMemberInfo(), fd.Index, fd.Name, this));
|
||||
}
|
||||
}
|
||||
|
||||
if (hierarchy.ContainsKey(MemberTypes.Event))
|
||||
{
|
||||
foreach (var ed in hierarchy[MemberTypes.Event])
|
||||
{
|
||||
events.Add(EventDef.MakeEventDef
|
||||
(type, (EventInfo)ed.GetMemberInfo(), ed.Index, ed.Name, this));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// add attributes
|
||||
var attrs = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(x => x.GetCustomAttribute<AttributeAttribute>() != null);
|
||||
|
||||
foreach (var attr in attrs)
|
||||
{
|
||||
var attrAttr = attr.GetCustomAttribute<AttributeAttribute>();
|
||||
|
||||
attributes.Add(AttributeDef
|
||||
.MakeAttributeDef(type, attr, 0, attrAttr?.Name ?? attr.Name, this));
|
||||
}
|
||||
|
||||
|
||||
// bake it binarily
|
||||
var b = new BinaryList();
|
||||
|
||||
// find the first parent type that implements IResource
|
||||
|
||||
|
||||
var hasParent = HasParent(type);
|
||||
var classAnnotations = type.GetCustomAttributes<AnnotationAttribute>(false);
|
||||
|
||||
|
||||
var hasClassAnnotation = (classAnnotations != null) && (classAnnotations.Count() > 0);
|
||||
|
||||
var typeNameBytes = DC.ToBytes(typeName);
|
||||
|
||||
b.AddUInt8((byte)((hasParent ? 0x80 : 0) | (hasClassAnnotation ? 0x40 : 0x0) | (byte)typeDefKind))
|
||||
.AddUUID(typeId)
|
||||
.AddUInt8((byte)typeNameBytes.Length)
|
||||
.AddUInt8Array(typeNameBytes);
|
||||
|
||||
if (hasParent)
|
||||
{
|
||||
// find the first parent type that implements IResource
|
||||
ParentDefinedType = ResourceProxy.GetBaseType(type.BaseType);
|
||||
var parentId = GetTypeUUID(ParentDefinedType);
|
||||
b.AddUUID(parentId);
|
||||
}
|
||||
|
||||
if (hasClassAnnotation)
|
||||
{
|
||||
Annotations = new Map<string, string>();
|
||||
|
||||
foreach (var ann in classAnnotations)
|
||||
Annotations.Add(ann.Key, ann.Value);
|
||||
|
||||
var classAnnotationBytes = Codec.Compose (Annotations, null, null);
|
||||
|
||||
b.AddUInt8Array(classAnnotationBytes);
|
||||
|
||||
}
|
||||
|
||||
b.AddInt32(version)
|
||||
.AddUInt16((ushort)(functions.Count + properties.Count + events.Count + constants.Count));
|
||||
|
||||
foreach (var ft in functions)
|
||||
b.AddUInt8Array(ft.Compose());
|
||||
foreach (var pt in properties)
|
||||
b.AddUInt8Array(pt.Compose());
|
||||
foreach (var et in events)
|
||||
b.AddUInt8Array(et.Compose());
|
||||
foreach (var ct in constants)
|
||||
b.AddUInt8Array(ct.Compose());
|
||||
|
||||
content = b.ToArray();
|
||||
|
||||
}
|
||||
|
||||
public static bool HasParent(Type type)
|
||||
{
|
||||
var parent = type.BaseType;
|
||||
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent == typeof(Esiur.Resource.Resource)
|
||||
|| parent == typeof(Record)
|
||||
|| parent == typeof(EntryPoint))
|
||||
return false;
|
||||
|
||||
if (parent.GetInterfaces().Contains(typeof(IResource))
|
||||
|| parent.GetInterfaces().Contains(typeof(IRecord)))
|
||||
return true;
|
||||
|
||||
parent = parent.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Dictionary<MemberTypes, List<MemberData>> GetHierarchy(Type type)
|
||||
{
|
||||
var members = new List<MemberData>();
|
||||
|
||||
var order = 0;
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
var classIsPublic = type.IsEnum || (type.GetCustomAttribute<ExportAttribute>() != null);
|
||||
|
||||
if (classIsPublic)
|
||||
{
|
||||
// get public instance members only.
|
||||
var mis = type.GetMembers(BindingFlags.Public | BindingFlags.Instance
|
||||
| BindingFlags.DeclaredOnly | BindingFlags.Static)
|
||||
.Where(x => x.MemberType == MemberTypes.Property || x.MemberType == MemberTypes.Field
|
||||
|| x.MemberType == MemberTypes.Event || x.MemberType == MemberTypes.Method)
|
||||
.Where(x => !(x is FieldInfo c && !c.IsStatic))
|
||||
.Where(x => x.GetCustomAttribute<IgnoreAttribute>() == null)
|
||||
.Where(x => x.Name != "Instance")
|
||||
.Where(x => x.Name != "Trigger")
|
||||
.Where(x => !(x is MethodInfo m && m.IsSpecialName))
|
||||
.Where(x => !(x is EventInfo e &&
|
||||
!(e.EventHandlerType.IsGenericType &&
|
||||
(e.EventHandlerType.GetGenericTypeDefinition() == typeof(ResourceEventHandler<>)
|
||||
|| e.EventHandlerType.GetGenericTypeDefinition() == typeof(CustomResourceEventHandler<>))
|
||||
)
|
||||
))
|
||||
.Select(x => new MemberData(
|
||||
info: x,
|
||||
order: order
|
||||
))
|
||||
.OrderBy(x => x.Name);
|
||||
|
||||
members.AddRange(mis.ToArray());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// allow private and public members that are marked with [Export] attribute.
|
||||
var mis = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
||||
| BindingFlags.DeclaredOnly | BindingFlags.Static)
|
||||
.Where(x => x.MemberType == MemberTypes.Property || x.MemberType == MemberTypes.Field
|
||||
|| x.MemberType == MemberTypes.Event || x.MemberType == MemberTypes.Method)
|
||||
.Where(x => !(x is FieldInfo c && !c.IsStatic))
|
||||
.Where(x => x.GetCustomAttribute<ExportAttribute>() != null)
|
||||
.Where(x => !(x is MethodInfo m && m.IsSpecialName))
|
||||
.Select(x => new MemberData(
|
||||
info: x,
|
||||
order: order
|
||||
))
|
||||
.OrderBy(x => x.Name);
|
||||
|
||||
members.AddRange(mis.ToArray());
|
||||
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
|
||||
if (type == null
|
||||
|| type == typeof(Esiur.Resource.Resource)
|
||||
|| type == typeof(Record)
|
||||
|| type == typeof(EntryPoint))
|
||||
break;
|
||||
|
||||
if (type.GetInterfaces().Contains(typeof(IResource))
|
||||
|| type.GetInterfaces().Contains(typeof(IRecord)))
|
||||
{
|
||||
order++;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// round 2: check for duplicates
|
||||
for (var i = 0; i < members.Count; i++)
|
||||
{
|
||||
var mi = members[i];
|
||||
for (var j = i + 1; j < members.Count; j++)
|
||||
{
|
||||
var pi = members[j];
|
||||
if (pi.Info.MemberType != mi.Info.MemberType)
|
||||
continue;
|
||||
|
||||
//if (ci.Info.Name == mi.Info.Name && ci.Order == mi.Order)
|
||||
// throw new Exception($"Method overload is not supported. Method '{ci.Info.Name}'.");
|
||||
|
||||
if (pi.Name == mi.Name)
|
||||
{
|
||||
if (pi.Order == mi.Order)
|
||||
throw new Exception($"Duplicate definitions for members public name '{mi.Info.DeclaringType.Name}:{mi.Info.Name}' and '{pi.Info.DeclaringType.Name}:{pi.Info.Name}'.");
|
||||
else
|
||||
{
|
||||
// @TODO: check for return type and parameters they must match
|
||||
if (pi.Info.Name != mi.Info.Name)
|
||||
throw new Exception($"Duplicate definitions for members public name '{mi.Info.DeclaringType.Name}:{mi.Info.Name}' and '{pi.Info.DeclaringType.Name}:{pi.Info.Name}'.");
|
||||
}
|
||||
|
||||
mi.Parent = pi;
|
||||
pi.Child = mi;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// assign indexes
|
||||
var groups = members.Where(x => x.Parent == null)
|
||||
.OrderBy(x => x.Name).OrderByDescending(x => x.Order)
|
||||
.GroupBy(x => x.Info.MemberType);
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
byte index = 0;
|
||||
foreach (var mi in group)
|
||||
{
|
||||
//if (mi.Parent == null)
|
||||
mi.Index = index++;
|
||||
}
|
||||
}
|
||||
|
||||
var rt = groups.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
|
||||
public static TypeDef Parse(byte[] data)
|
||||
{
|
||||
return Parse(data, 0, (uint)data.Length);
|
||||
}
|
||||
|
||||
|
||||
public static TypeDef Parse(byte[] data, uint offset, uint contentLength)
|
||||
{
|
||||
|
||||
uint ends = offset + contentLength;
|
||||
|
||||
uint oOffset = offset;
|
||||
|
||||
// start parsing...
|
||||
|
||||
var od = new TypeDef();
|
||||
od.content = data.Clip(offset, contentLength);
|
||||
|
||||
var hasParent = (data[offset] & 0x80) > 0;
|
||||
var hasClassAnnotation = (data[offset] & 0x40) > 0;
|
||||
|
||||
od.typeDefKind = (TypeDefKind)(data[offset++] & 0xF);
|
||||
|
||||
od.typeId = data.GetUUID(offset);
|
||||
offset += 16;
|
||||
od.typeName = data.GetString(offset + 1, data[offset]);
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
|
||||
if (hasParent)
|
||||
{
|
||||
od.parentId = data.GetUUID(offset);
|
||||
offset += 16;
|
||||
}
|
||||
|
||||
if (hasClassAnnotation)
|
||||
{
|
||||
var (len, anns) = Codec.ParseSync(data, offset, null);
|
||||
|
||||
if (anns is Map<string, string> annotations)
|
||||
od.Annotations = annotations;
|
||||
|
||||
offset += len;
|
||||
}
|
||||
|
||||
od.version = data.GetInt32(offset, Endian.Little);
|
||||
offset += 4;
|
||||
|
||||
ushort methodsCount = data.GetUInt16(offset, Endian.Little);
|
||||
offset += 2;
|
||||
|
||||
byte functionIndex = 0;
|
||||
byte propertyIndex = 0;
|
||||
byte eventIndex = 0;
|
||||
byte constantIndex = 0;
|
||||
|
||||
for (int i = 0; i < methodsCount; i++)
|
||||
{
|
||||
var inherited = (data[offset] & 0x80) > 0;
|
||||
var type = (data[offset] >> 5) & 0x3;
|
||||
|
||||
if (type == 0) // function
|
||||
{
|
||||
var (len, ft) = FunctionDef.Parse(data, offset, functionIndex++, inherited);
|
||||
offset += len;
|
||||
od.functions.Add(ft);
|
||||
}
|
||||
else if (type == 1) // property
|
||||
{
|
||||
var (len, pt) = PropertyDef.Parse(data, offset, propertyIndex++, inherited);
|
||||
offset += len;
|
||||
od.properties.Add(pt);
|
||||
|
||||
}
|
||||
else if (type == 2) // Event
|
||||
{
|
||||
var (len, et) = EventDef.Parse(data, offset, eventIndex++, inherited);
|
||||
offset += len;
|
||||
od.events.Add(et);
|
||||
}
|
||||
// constant
|
||||
else if (type == 3)
|
||||
{
|
||||
var (len, ct) = ConstantDef.Parse(data, offset, constantIndex++, inherited);
|
||||
offset += len;
|
||||
od.constants.Add(ct);
|
||||
}
|
||||
}
|
||||
|
||||
return od;
|
||||
}
|
||||
|
||||
public Map<byte, object> CastProperties(Map<string, object> properties)
|
||||
{
|
||||
var rt = new Map<byte, object>();
|
||||
foreach (var kv in properties)
|
||||
{
|
||||
var pt = GetPropertyDefByName(kv.Key);
|
||||
if (pt == null) continue;
|
||||
rt.Add(pt.Index, kv.Value);
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
13
Libraries/Esiur/Data/Types/TypeDefKind.cs
Normal file
13
Libraries/Esiur/Data/Types/TypeDefKind.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data.Types;
|
||||
|
||||
public enum TypeDefKind : byte
|
||||
{
|
||||
Resource,
|
||||
Record,
|
||||
Enum,
|
||||
Function
|
||||
}
|
||||
18
Libraries/Esiur/Data/UInt128.cs
Normal file
18
Libraries/Esiur/Data/UInt128.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public struct UInt128
|
||||
{
|
||||
public UInt128(ulong lsb, ulong msb)
|
||||
{
|
||||
this.MSB = msb;
|
||||
this.LSB = lsb;
|
||||
}
|
||||
|
||||
public ulong MSB { get;set; }
|
||||
public ulong LSB { get;set; }
|
||||
}
|
||||
}
|
||||
144
Libraries/Esiur/Data/Uuid.cs
Normal file
144
Libraries/Esiur/Data/Uuid.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.FlowAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
//[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct Uuid
|
||||
{
|
||||
//4e7db2d8-a785-1b99-1854-4b4018bc5677
|
||||
//byte a1;
|
||||
//byte a2;
|
||||
//byte a3;
|
||||
//byte a4;
|
||||
//byte b1;
|
||||
//byte b2;
|
||||
//byte c1;
|
||||
//byte c2;
|
||||
//byte d1;
|
||||
//byte d2;
|
||||
//byte e1;
|
||||
//byte e2;
|
||||
//byte e3;
|
||||
//byte e4;
|
||||
//byte e5;
|
||||
//byte e6;
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
|
||||
public Uuid(byte[] data, uint offset)
|
||||
{
|
||||
if (offset + 16 > data.Length)
|
||||
throw new Exception("UUID data size must be at least 16 bytes");
|
||||
|
||||
Data = DC.Clip(data, offset, 16);
|
||||
|
||||
//a1 = data[offset++];
|
||||
//a2 = data[offset++];
|
||||
//a3 = data[offset++];
|
||||
//a4 = data[offset++];
|
||||
//b1 = data[offset++];
|
||||
//b2 = data[offset++];
|
||||
//c1 = data[offset++];
|
||||
//c2 = data[offset++];
|
||||
//d1 = data[offset++];
|
||||
//d2 = data[offset++];
|
||||
//e1 = data[offset++];
|
||||
//e2 = data[offset++];
|
||||
//e3 = data[offset++];
|
||||
//e4 = data[offset++];
|
||||
//e5 = data[offset++];
|
||||
//e6 = data[offset++];
|
||||
}
|
||||
|
||||
public unsafe override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
fixed (byte* p = Data)
|
||||
{
|
||||
ulong u0 = *(ulong*)p;
|
||||
ulong u1 = *(ulong*)(p + 8);
|
||||
|
||||
// simple mixing of two 64-bit halves
|
||||
return ((int)u0 ^ (int)(u0 >> 32)) ^
|
||||
((int)u1 ^ (int)(u1 >> 32));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Uuid(byte[] data) {
|
||||
|
||||
if (data.Length != 16)
|
||||
throw new Exception("UUID data size must be 16 bytes");
|
||||
|
||||
Data = data;
|
||||
//a1 = data[0];
|
||||
//a2 = data[1];
|
||||
//a3 = data[2];
|
||||
//a4 = data[3];
|
||||
//b1 = data[4];
|
||||
//b2 = data[5];
|
||||
//c1 = data[6];
|
||||
//c2 = data[7];
|
||||
//d1 = data[8];
|
||||
//d2 = data[9];
|
||||
//e1 = data[10];
|
||||
//e2 = data[11];
|
||||
//e3 = data[12];
|
||||
//e4 = data[13];
|
||||
//e5 = data[14];
|
||||
//e6 = data[15];
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
return $"{DC.ToHex(Data, 0, 4, null)}-{DC.ToHex(Data, 4, 2, null)}-{DC.ToHex(Data, 6, 2, null)}-{DC.ToHex(Data, 8, 2, null)}-{DC.ToHex(Data, 10, 6, null)}";
|
||||
|
||||
//return $"{a1.ToString("x2")}{a2.ToString("x2")}{a3.ToString("x2")}{a4.ToString("x2")}-{b1.ToString("x2")}{b2.ToString("x2")}-{c1.ToString("x2")}{c2.ToString("x2")}-{d1.ToString("x2")}{d2.ToString("x2")}-{e1.ToString("x2")}{e2.ToString("x2")}{e3.ToString("x2")}{e4.ToString("x2")}{e5.ToString("x2")}{e6.ToString("x2")}";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Uuid b)
|
||||
return Data.SequenceEqual(b.Data);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool operator == (Uuid a, Uuid b)
|
||||
{
|
||||
return a.Data.SequenceEqual(b.Data);
|
||||
|
||||
//return a.a1 == b.a1
|
||||
// && a.a2 == b.a2
|
||||
// && a.a3 == b.a3
|
||||
// && a.a4 == b.a4
|
||||
// && a.b1 == b.b1
|
||||
// && a.b2 == b.b2
|
||||
// && a.c1 == b.c1
|
||||
// && a.c2 == b.c2
|
||||
// && a.d1 == b.d1
|
||||
// && a.d2 == b.d2
|
||||
// && a.e1 == b.e1
|
||||
// && a.e2 == b.e2
|
||||
// && a.e3 == b.e3
|
||||
// && a.e4 == b.e4
|
||||
// && a.e5 == b.e5
|
||||
// && a.e6 == b.e6;
|
||||
}
|
||||
|
||||
public static bool operator !=(Uuid a, Uuid b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
20
Libraries/Esiur/Data/VarInfo.cs
Normal file
20
Libraries/Esiur/Data/VarInfo.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
108
Libraries/Esiur/Data/VarList.cs
Normal file
108
Libraries/Esiur/Data/VarList.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Esiur.Core;
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Data
|
||||
{
|
||||
public class VarList<T> : IEnumerable<T>, ICollection, ICollection<T>
|
||||
{
|
||||
string propertyName;
|
||||
IResource resource;
|
||||
|
||||
List<T> list = new List<T>();
|
||||
|
||||
public VarList(IResource resource, [CallerMemberName] string propertyName = "")
|
||||
{
|
||||
this.resource = resource;
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
public VarList()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public int Count => list.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public bool IsSynchronized => true;
|
||||
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
list.Add(item);
|
||||
|
||||
resource?.Instance?.Modified(propertyName);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
list.Clear();
|
||||
resource?.Instance?.Modified(propertyName);
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return list.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
(list as ICollection).CopyTo(array, index);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if ( list.Remove(item))
|
||||
{
|
||||
resource?.Instance?.Modified(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return list[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
//var oldValue = list[index];
|
||||
|
||||
lock (SyncRoot)
|
||||
list[index] = value;
|
||||
|
||||
resource?.Instance?.Modified(propertyName);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Libraries/Esiur/Esiur.csproj
Normal file
92
Libraries/Esiur/Esiur.csproj
Normal file
@@ -0,0 +1,92 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Distributed Resources Platform</Description>
|
||||
<Copyright>Ahmed Kh. Zamil</Copyright>
|
||||
<PackageProjectUrl>http://www.esiur.com</PackageProjectUrl>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Version>3.0.0</Version>
|
||||
<RepositoryUrl>https://github.com/esiur/esiur-dotnet</RepositoryUrl>
|
||||
<Authors>Ahmed Kh. Zamil</Authors>
|
||||
<AssemblyVersion></AssemblyVersion>
|
||||
<Company>Esiur Foundation</Company>
|
||||
<FileVersion></FileVersion>
|
||||
<AssemblyName>Esiur</AssemblyName>
|
||||
<RootNamespace>Esiur</RootNamespace>
|
||||
<PackageId>Esiur</PackageId>
|
||||
<Product>Esiur</Product>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="obj\**" />
|
||||
<EmbeddedResource Remove="obj\**" />
|
||||
<None Remove="obj\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" PrivateAssets="all" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.8" GeneratePathProperty="true" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<!-- Package the Json dependency alongside the generator assembly -->
|
||||
<None Include="$(PkgSystem_Text_Json)\lib\netstandard2.0\*.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<None Include="Tools/*" Pack="true" PackagePath="tools/" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Data\NullabilityInfo.cs" />
|
||||
<Compile Remove="Data\NullabilityInfoContext.cs" />
|
||||
<Compile Remove="Net\Packets\EpAuthPacketAuthMode.cs" />
|
||||
<Compile Remove="Protocol\Authentication\HashAnonymousAuthenticator.cs" />
|
||||
<Compile Remove="Security\Authority\Authentication.cs" />
|
||||
<Compile Remove="Security\Authority\AuthenticationMethod.cs" />
|
||||
<Compile Remove="Security\Authority\IAuthenticationInitiator.cs" />
|
||||
<Compile Remove="Security\Authority\IAuthenticationResponder.cs" />
|
||||
<Compile Remove="Security\Membership\SimpleMembership.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Data\NullabilityInfo.cs" />
|
||||
<None Include="Data\NullabilityInfoContext.cs" />
|
||||
<None Include="Data\Types\ArgumentDef.cs" />
|
||||
<None Include="Data\Types\AttributeDef.cs" />
|
||||
<None Include="LICENSE" Pack="true" PackagePath=""></None>
|
||||
<None Include="Net\Packets\EpAuthPacketAuthMode.cs" />
|
||||
<None Include="Protocol\Authentication\HashAnonymousAuthenticator.cs" />
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
<None Include="Security\Authority\Authentication.cs" />
|
||||
<None Include="Security\Authority\AuthenticationMethod.cs" />
|
||||
<None Include="Security\Authority\IAuthenticationInitiator.cs" />
|
||||
<None Include="Security\Authority\IAuthenticationResponder.cs" />
|
||||
<None Include="Security\Membership\SimpleMembership.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
21
Libraries/Esiur/LICENSE
Normal file
21
Libraries/Esiur/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2025 Esiur Foundation, Ahmed Kh. Zamil.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
389
Libraries/Esiur/Misc/Global.cs
Normal file
389
Libraries/Esiur/Misc/Global.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Xml;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using Esiur.Data;
|
||||
using System.Collections.Generic;
|
||||
//using Esiur.Net.Packets;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Linq;
|
||||
using Esiur.Core;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using Esiur.Resource;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Esiur.Misc;
|
||||
public static class Global
|
||||
{
|
||||
private static KeyList<string, object> variables = new KeyList<string, object>();
|
||||
|
||||
private static Random rand = new Random();// System.Environment.TickCount);
|
||||
|
||||
|
||||
|
||||
public static KeyList<string, long> Counters = new KeyList<string, long>();
|
||||
|
||||
public delegate void LogEvent(string service, LogType type, string message);
|
||||
|
||||
public static event LogEvent SystemLog;
|
||||
|
||||
|
||||
public static string ToJson(this IResource resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Serialize(resource, Global.SerializeOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonSerializerOptions SerializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
ReferenceHandler = ReferenceHandler.Preserve,
|
||||
WriteIndented = true,
|
||||
Converters =
|
||||
{
|
||||
new ResourceJsonConverter(),
|
||||
new DoubleJsonConverter()
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
public static string Version { get; } = typeof(Global).Assembly.GetName().Version.ToString(); //FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
|
||||
|
||||
|
||||
public static void Log(Exception ex, params object[] arguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
var stack = new StackTrace(ex, true);
|
||||
var frames = stack.GetFrames();
|
||||
var frame = frames?.FirstOrDefault();
|
||||
|
||||
MethodBase? method = null;
|
||||
ParameterInfo[] parameters = Array.Empty<ParameterInfo>();
|
||||
string service = "Unknown";
|
||||
|
||||
if (frame != null)
|
||||
{
|
||||
method = frame.GetMethod();
|
||||
}
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
// Fallback to TargetSite if available
|
||||
method = ex.TargetSite;
|
||||
}
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
parameters = method.GetParameters();
|
||||
service = method.DeclaringType != null ? method.DeclaringType.Name : method.Name;
|
||||
}
|
||||
var message = "";
|
||||
|
||||
if (arguments.Length > 0 && parameters.Length > 0)
|
||||
{
|
||||
message = "Arguments ( ";
|
||||
|
||||
for (int i = 0; i < parameters.Length && i < arguments.Length; i++)
|
||||
{
|
||||
message += parameters[i].Name + ": " + arguments[i].ToString() + " ";
|
||||
}
|
||||
|
||||
message += ")" + Environment.NewLine + "------------------------------------------------";
|
||||
}
|
||||
|
||||
message += ex.ToString();
|
||||
|
||||
Log(service, LogType.Error, message);
|
||||
|
||||
|
||||
|
||||
Log(service, LogType.Error, ex.ToString());
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void Log(string service, LogType type, string message, bool appendHeader = true)
|
||||
{
|
||||
//if (type != LogType.Debug)
|
||||
Console.WriteLine(service + " " + message);
|
||||
|
||||
SystemLog?.Invoke(service, type, message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static string RemoveControlCharacters(string inString)
|
||||
{
|
||||
if (inString == null) return null;
|
||||
|
||||
StringBuilder newString = new StringBuilder();
|
||||
char ch;
|
||||
|
||||
for (int i = 0; i < inString.Length; i++)
|
||||
{
|
||||
|
||||
ch = inString[i];
|
||||
|
||||
if (!char.IsControl(ch))
|
||||
{
|
||||
newString.Append(ch);
|
||||
}
|
||||
}
|
||||
return newString.ToString();
|
||||
}
|
||||
|
||||
public static void PrintCounters()
|
||||
{
|
||||
string[] keys = new string[Counters.Keys.Count];
|
||||
Counters.Keys.CopyTo(keys, 0);
|
||||
|
||||
foreach (string k in keys)
|
||||
{
|
||||
Console.WriteLine(k + ":" + Counters[k]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static KeyList<string, object> Variables
|
||||
{
|
||||
get
|
||||
{
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static uint CurrentUnixTime()
|
||||
{
|
||||
return (uint)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
}
|
||||
|
||||
public static void SetConsoleColors(ConsoleColor ForegroundColor, ConsoleColor BackgroundColor)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
switch (ForegroundColor)
|
||||
{
|
||||
case ConsoleColor.Black:
|
||||
Console.Write("\u001B[30m");
|
||||
break;
|
||||
case ConsoleColor.Blue:
|
||||
Console.Write("\u001B[1;34m");
|
||||
break;
|
||||
case ConsoleColor.Cyan:
|
||||
Console.Write("\u001B[1;36m");
|
||||
break;
|
||||
case ConsoleColor.Gray:
|
||||
case ConsoleColor.DarkGray:
|
||||
Console.Write("\u001B[1;30m");
|
||||
break;
|
||||
case ConsoleColor.Green:
|
||||
Console.Write("\u001B[1;32m");
|
||||
break;
|
||||
case ConsoleColor.Magenta:
|
||||
Console.Write("\u001B[1;35m");
|
||||
break;
|
||||
case ConsoleColor.Red:
|
||||
Console.Write("\u001B[1;31m");
|
||||
break;
|
||||
case ConsoleColor.White:
|
||||
Console.Write("\u001B[1;37m");
|
||||
break;
|
||||
case ConsoleColor.Yellow:
|
||||
Console.Write("\u001B[1;33m");
|
||||
break;
|
||||
|
||||
case ConsoleColor.DarkBlue:
|
||||
Console.Write("\u001B[34m");
|
||||
break;
|
||||
case ConsoleColor.DarkCyan:
|
||||
Console.Write("\u001B[36m");
|
||||
break;
|
||||
case ConsoleColor.DarkGreen:
|
||||
Console.Write("\u001B[32m");
|
||||
break;
|
||||
case ConsoleColor.DarkMagenta:
|
||||
Console.Write("\u001B[35m");
|
||||
break;
|
||||
case ConsoleColor.DarkRed:
|
||||
Console.Write("\u001B[31m");
|
||||
break;
|
||||
case ConsoleColor.DarkYellow:
|
||||
Console.Write("\u001B[33m");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
switch (BackgroundColor)
|
||||
{
|
||||
case ConsoleColor.Black:
|
||||
Console.Write("\u001B[40m");
|
||||
break;
|
||||
case ConsoleColor.Blue:
|
||||
Console.Write("\u001B[1;44m");
|
||||
break;
|
||||
case ConsoleColor.Cyan:
|
||||
Console.Write("\u001B[1;46m");
|
||||
break;
|
||||
case ConsoleColor.Gray:
|
||||
case ConsoleColor.DarkGray:
|
||||
Console.Write("\u001B[1;40m");
|
||||
break;
|
||||
case ConsoleColor.Green:
|
||||
Console.Write("\u001B[1;42m");
|
||||
break;
|
||||
case ConsoleColor.Magenta:
|
||||
Console.Write("\u001B[1;45m");
|
||||
break;
|
||||
case ConsoleColor.Red:
|
||||
Console.Write("\u001B[1;41m");
|
||||
break;
|
||||
case ConsoleColor.White:
|
||||
Console.Write("\u001B[1;47m");
|
||||
break;
|
||||
case ConsoleColor.Yellow:
|
||||
Console.Write("\u001B[1;43m");
|
||||
break;
|
||||
|
||||
case ConsoleColor.DarkBlue:
|
||||
Console.Write("\u001B[44m");
|
||||
break;
|
||||
case ConsoleColor.DarkCyan:
|
||||
Console.Write("\u001B[46m");
|
||||
break;
|
||||
case ConsoleColor.DarkGreen:
|
||||
Console.Write("\u001B[42m");
|
||||
break;
|
||||
case ConsoleColor.DarkMagenta:
|
||||
Console.Write("\u001B[45m");
|
||||
break;
|
||||
case ConsoleColor.DarkRed:
|
||||
Console.Write("\u001B[41m");
|
||||
break;
|
||||
case ConsoleColor.DarkYellow:
|
||||
Console.Write("\u001B[43m");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ForegroundColor;
|
||||
Console.BackgroundColor = BackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
/*
|
||||
public static bool IsUnix()
|
||||
{
|
||||
// Linux OSs under Mono 1.2 uses unknown integer numbers so this should identify all non windows as unix
|
||||
return (Environment.OSVersion.Platform != PlatformID.Win32NT
|
||||
&& Environment.OSVersion.Platform != PlatformID.Win32Windows); // || Environment.OSVersion.Platform == PlatformID.Linux;
|
||||
}
|
||||
*/
|
||||
|
||||
public static string GenerateCode()
|
||||
{
|
||||
return GenerateCode(16);
|
||||
}
|
||||
|
||||
|
||||
public static byte[] GenerateBytes(int length)
|
||||
{
|
||||
var b = new byte[length];
|
||||
rand.NextBytes(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static string GenerateCode(int length)
|
||||
{
|
||||
return GenerateCode(length, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
}
|
||||
|
||||
public static string GenerateCode(int length, string chars)
|
||||
{
|
||||
var result = new string(
|
||||
Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[rand.Next(s.Length)])
|
||||
.ToArray());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static Regex GetRouteRegex(string url)
|
||||
{
|
||||
var sc = Regex.Match(url, @"([^\{]*)\{([^\}]*)\}([^\{]*)");
|
||||
|
||||
List<VarInfo> vars = new List<VarInfo>();
|
||||
|
||||
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) + "$");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
57
Libraries/Esiur/Net/DataLink/PacketFilter.cs
Normal file
57
Libraries/Esiur/Net/DataLink/PacketFilter.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Net.Packets;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Net.DataLink;
|
||||
public abstract class PacketFilter : IResource
|
||||
{
|
||||
|
||||
public Instance Instance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public event DestroyedEvent OnDestroy;
|
||||
|
||||
|
||||
public abstract AsyncReply<bool> Trigger(ResourceTrigger trigger);
|
||||
|
||||
public abstract bool Execute(Packet packet);
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
121
Libraries/Esiur/Net/DataLink/PacketServer.cs
Normal file
121
Libraries/Esiur/Net/DataLink/PacketServer.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using System.Runtime.InteropServices;
|
||||
using Esiur.Net.Packets;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Net.DataLink;
|
||||
public class PacketServer : IResource
|
||||
{
|
||||
List<PacketSource> sources = new List<PacketSource>();
|
||||
List<PacketFilter> filters = new List<PacketFilter>();
|
||||
|
||||
|
||||
[Storable]
|
||||
public string Mode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Instance Instance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public List<PacketSource> Sources
|
||||
{
|
||||
get
|
||||
{
|
||||
return sources;
|
||||
}
|
||||
}
|
||||
|
||||
public event DestroyedEvent OnDestroy;
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Trigger(ResourceTrigger trigger)
|
||||
{
|
||||
if (trigger == ResourceTrigger.Initialize)
|
||||
{
|
||||
/*
|
||||
foreach (var resource in Instance.Children<IResource>())
|
||||
{
|
||||
|
||||
if (resource is PacketFilter)
|
||||
{
|
||||
filters.Add(resource as PacketFilter);
|
||||
}
|
||||
else if (resource is PacketSource)
|
||||
{
|
||||
sources.Add(resource as PacketSource);
|
||||
}
|
||||
}
|
||||
*/
|
||||
foreach (var src in sources)
|
||||
{
|
||||
src.OnNewPacket += PacketReceived;
|
||||
src.Open();
|
||||
}
|
||||
}
|
||||
else if (trigger == ResourceTrigger.Terminate)
|
||||
{
|
||||
// foreach (var src in sources)
|
||||
// src.Close();
|
||||
}
|
||||
else if (trigger == ResourceTrigger.SystemReload)
|
||||
{
|
||||
foreach (var src in sources)
|
||||
{
|
||||
src.Close();
|
||||
src.Open();
|
||||
}
|
||||
}
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
void PacketReceived(Packet Packet)
|
||||
{
|
||||
foreach (var f in filters)
|
||||
{
|
||||
if (f.Execute(Packet))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Libraries/Esiur/Net/DataLink/PacketSource.cs
Normal file
93
Libraries/Esiur/Net/DataLink/PacketSource.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Net.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Esiur.Core;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Net.DataLink;
|
||||
public abstract class PacketSource : IResource
|
||||
{
|
||||
public delegate void NewPacket(Packet Packet);
|
||||
public abstract event NewPacket OnNewPacket;
|
||||
public event DestroyedEvent OnDestroy;
|
||||
|
||||
public Instance Instance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public abstract AsyncReply<bool> Trigger(ResourceTrigger trigger);
|
||||
|
||||
|
||||
public abstract bool RawMode
|
||||
{
|
||||
set;
|
||||
get;
|
||||
}
|
||||
|
||||
//public PacketSource(PacketServer Server, bool RawMode)
|
||||
//{
|
||||
// this.RawMode = RawMode;
|
||||
//}
|
||||
|
||||
|
||||
public abstract bool Open();
|
||||
|
||||
public abstract bool Close();
|
||||
|
||||
|
||||
public abstract bool Write(Packet packet);
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
|
||||
/*
|
||||
public virtual string TypeName
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Raw";
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public abstract byte[] Address
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract string DeviceId
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
37
Libraries/Esiur/Net/Http/EpOverHttp.cs
Normal file
37
Libraries/Esiur/Net/Http/EpOverHttp.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Esiur.Core;
|
||||
using Esiur.Net.Packets;
|
||||
using Esiur.Protocol;
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Http;
|
||||
public class EpOverHttp : HttpFilter
|
||||
{
|
||||
[Attribute]
|
||||
EntryPoint EntryPoint { get; set; }
|
||||
|
||||
public override AsyncReply<bool> Execute(HttpConnection sender)
|
||||
{
|
||||
if (sender.Request.URL != "EP")
|
||||
return new AsyncReply<bool>(false);
|
||||
|
||||
EpPacketRequest action = (EpPacketRequest)Convert.ToByte(sender.Request.Query["a"]);
|
||||
|
||||
if (action == EpPacketRequest.Query)
|
||||
{
|
||||
EntryPoint.Query(sender.Request.Query["l"], null).Then(x =>
|
||||
{
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
public override AsyncReply<bool> Trigger(ResourceTrigger trigger)
|
||||
{
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
}
|
||||
80
Libraries/Esiur/Net/Http/EpOvwerWebsocket.cs
Normal file
80
Libraries/Esiur/Net/Http/EpOvwerWebsocket.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Esiur.Resource;
|
||||
using Esiur.Net.Sockets;
|
||||
using Esiur.Core;
|
||||
using Esiur.Protocol;
|
||||
|
||||
namespace Esiur.Net.Http;
|
||||
public class EpOvwerWebsocket : HttpFilter
|
||||
{
|
||||
[Attribute]
|
||||
public EpServer Server
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override AsyncReply<bool> Execute(HttpConnection sender)
|
||||
{
|
||||
|
||||
if (sender.IsWebsocketRequest())
|
||||
{
|
||||
if (Server == null)
|
||||
return new AsyncReply<bool>(false);
|
||||
|
||||
var tcpSocket = sender.Unassign();
|
||||
|
||||
if (tcpSocket == null)
|
||||
return new AsyncReply<bool>(false);
|
||||
|
||||
var httpServer = sender.Parent;
|
||||
var wsSocket = new WSocket(tcpSocket);
|
||||
httpServer.Remove(sender);
|
||||
|
||||
var EPConnection = new EpConnection();
|
||||
|
||||
Server.Add(EPConnection);
|
||||
EPConnection.Assign(wsSocket);
|
||||
wsSocket.Begin();
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
return new AsyncReply<bool>(false);
|
||||
|
||||
}
|
||||
|
||||
public override AsyncReply<bool> Trigger(ResourceTrigger trigger)
|
||||
{
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
}
|
||||
|
||||
452
Libraries/Esiur/Net/Http/HttpConnection.cs
Normal file
452
Libraries/Esiur/Net/Http/HttpConnection.cs
Normal file
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Net.Sockets;
|
||||
using Esiur.Data;
|
||||
using Esiur.Misc;
|
||||
using System.Security.Cryptography;
|
||||
using Esiur.Core;
|
||||
using Esiur.Net.Packets.WebSocket;
|
||||
using Esiur.Net.Packets.Http;
|
||||
|
||||
namespace Esiur.Net.Http;
|
||||
public class HttpConnection : NetworkConnection
|
||||
{
|
||||
|
||||
|
||||
|
||||
public bool WSMode { get; internal set; }
|
||||
public HttpServer Server { get; internal set; }
|
||||
|
||||
public WebsocketPacket WSRequest { get; set; }
|
||||
public HttpRequestPacket Request { get; set; }
|
||||
public HttpResponsePacket Response { get; } = new HttpResponsePacket();
|
||||
|
||||
HttpSession session;
|
||||
|
||||
public KeyList<string, object> Variables { get; } = new KeyList<string, object>();
|
||||
|
||||
|
||||
|
||||
internal long Parse(byte[] data)
|
||||
{
|
||||
if (WSMode)
|
||||
{
|
||||
// now parse WS protocol
|
||||
WebsocketPacket ws = new WebsocketPacket();
|
||||
|
||||
var pSize = ws.Parse(data, 0, (uint)data.Length);
|
||||
|
||||
|
||||
if (pSize > 0)
|
||||
{
|
||||
WSRequest = ws;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return pSize;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var rp = new HttpRequestPacket();
|
||||
var pSize = rp.Parse(data, 0, (uint)data.Length);
|
||||
if (pSize > 0)
|
||||
{
|
||||
Request = rp;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return pSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
// close the connection
|
||||
if (Request.Headers["connection"].ToLower() != "keep-alive" & IsConnected)
|
||||
Close();
|
||||
}
|
||||
|
||||
public bool Upgrade()
|
||||
{
|
||||
var ok = Upgrade(Request, Response);
|
||||
|
||||
if (ok)
|
||||
{
|
||||
WSMode = true;
|
||||
Send();
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
public static bool Upgrade(HttpRequestPacket request, HttpResponsePacket response)
|
||||
{
|
||||
if (IsWebsocketRequest(request))
|
||||
{
|
||||
string magicString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
string ret = request.Headers["Sec-WebSocket-Key"] + magicString;
|
||||
// Compute the SHA1 hash
|
||||
SHA1 sha = SHA1.Create();
|
||||
byte[] sha1Hash = sha.ComputeHash(Encoding.UTF8.GetBytes(ret));
|
||||
response.Headers["Upgrade"] = request.Headers["Upgrade"];
|
||||
response.Headers["Connection"] = request.Headers["Connection"];// "Upgrade";
|
||||
response.Headers["Sec-WebSocket-Accept"] = Convert.ToBase64String(sha1Hash);
|
||||
|
||||
if (request.Headers.ContainsKey("Sec-WebSocket-Protocol"))
|
||||
response.Headers["Sec-WebSocket-Protocol"] = request.Headers["Sec-WebSocket-Protocol"];
|
||||
|
||||
|
||||
response.Number = HttpResponseCode.Switching;
|
||||
response.Text = "Switching Protocols";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public HttpServer Parent
|
||||
{
|
||||
get
|
||||
{
|
||||
return Server;
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(WebsocketPacket packet)
|
||||
{
|
||||
if (packet.Data != null)
|
||||
base.Send(packet.Data);
|
||||
}
|
||||
|
||||
public override void Send(string data)
|
||||
{
|
||||
Response.Message = Encoding.UTF8.GetBytes(data);
|
||||
Send();
|
||||
}
|
||||
|
||||
public override void Send(byte[] msg, int offset, int length)
|
||||
{
|
||||
Response.Message = DC.Clip(msg, (uint)offset, (uint)length);
|
||||
Send();
|
||||
}
|
||||
|
||||
public override void Send(byte[] message)
|
||||
{
|
||||
Response.Message = message;
|
||||
Send();
|
||||
}
|
||||
|
||||
public void Send(HttpComposeOption Options = HttpComposeOption.AllCalculateLength)
|
||||
{
|
||||
if (Response.Handled)
|
||||
return;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
Response.Compose(Options);
|
||||
base.Send(Response.Data);
|
||||
|
||||
// Refresh the current session
|
||||
if (session != null)
|
||||
session.Refresh();
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
Close();
|
||||
}
|
||||
finally { }
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void CreateNewSession()
|
||||
{
|
||||
if (session == null)
|
||||
{
|
||||
// Create a new one
|
||||
session = Server.CreateSession(Global.GenerateCode(12), 60 * 20);
|
||||
|
||||
HttpCookie cookie = new HttpCookie("SID", session.Id);
|
||||
cookie.Expires = DateTime.MaxValue;
|
||||
cookie.Path = "/";
|
||||
cookie.HttpOnly = true;
|
||||
|
||||
Response.Cookies.Add(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsWebsocketRequest()
|
||||
{
|
||||
return IsWebsocketRequest(this.Request);
|
||||
}
|
||||
|
||||
public static bool IsWebsocketRequest(HttpRequestPacket request)
|
||||
{
|
||||
if (request.Headers.ContainsKey("connection")
|
||||
&& request.Headers["connection"].ToLower().Contains("upgrade")
|
||||
&& request.Headers.ContainsKey("upgrade")
|
||||
&& request.Headers["upgrade"].ToLower() == "websocket"
|
||||
&& request.Headers.ContainsKey("Sec-WebSocket-Version")
|
||||
&& request.Headers["Sec-WebSocket-Version"] == "13"
|
||||
&& request.Headers.ContainsKey("Sec-WebSocket-Key"))
|
||||
//&& Request.Headers.ContainsKey("Sec-WebSocket-Protocol"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DataReceived(NetworkBuffer data)
|
||||
{
|
||||
|
||||
byte[] msg = data.Read();
|
||||
|
||||
var BL = Parse(msg);
|
||||
|
||||
if (BL == 0)
|
||||
{
|
||||
if (Request.Method == Packets.Http.HttpMethod.UNKNOWN)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
if (Request.URL == "")
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (BL == -1)
|
||||
{
|
||||
data.HoldForNextWrite(msg);
|
||||
return;
|
||||
}
|
||||
else if (BL < 0)
|
||||
{
|
||||
data.HoldFor(msg, (uint)(msg.Length - BL));
|
||||
return;
|
||||
}
|
||||
else if (BL > 0)
|
||||
{
|
||||
if (BL > Server.MaxPost)
|
||||
{
|
||||
Send(
|
||||
"<html><body>POST method content is larger than "
|
||||
+ Server.MaxPost
|
||||
+ " bytes.</body></html>");
|
||||
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
data.HoldFor(msg, (uint)(msg.Length + BL));
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (BL < 0) // for security
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (IsWebsocketRequest(Request) & !WSMode)
|
||||
{
|
||||
Upgrade();
|
||||
//return;
|
||||
}
|
||||
|
||||
|
||||
//return;
|
||||
|
||||
try
|
||||
{
|
||||
if (!Server.Execute(this))
|
||||
{
|
||||
Response.Number = HttpResponseCode.InternalServerError;
|
||||
Send("Bad Request");
|
||||
Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message != "Thread was being aborted.")
|
||||
{
|
||||
|
||||
Global.Log("HTTPServer", LogType.Error, ex.ToString());
|
||||
|
||||
//Console.WriteLine(ex.ToString());
|
||||
//EventLog.WriteEntry("HttpServer", ex.ToString(), EventLogEntryType.Error);
|
||||
Send(Error500(ex.Message));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private string Error500(string msg)
|
||||
{
|
||||
return "<html><head><title>500 Internal Server Error</title></head><br>\r\n"
|
||||
+ "<body><br>\r\n"
|
||||
+ "<b>500</b> Internal Server Error<br>" + msg + "\r\n"
|
||||
+ "</body><br>\r\n"
|
||||
+ "</html><br>\r\n";
|
||||
}
|
||||
|
||||
public async AsyncReply<bool> SendFile(string filename)
|
||||
{
|
||||
if (Response.Handled == true)
|
||||
return false;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
//HTTP/1.1 200 OK
|
||||
//Server: Microsoft-IIS/5.0
|
||||
//Content-Location: http://127.0.0.1/index.html
|
||||
//Date: Wed, 10 Dec 2003 19:10:25 GMT
|
||||
//Content-Type: text/html
|
||||
//Accept-Ranges: bytes
|
||||
//Last-Modified: Mon, 22 Sep 2003 22:36:56 GMT
|
||||
//Content-Length: 1957
|
||||
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
Response.Number = HttpResponseCode.NotFound;
|
||||
Send("File Not Found");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
var fileEditTime = File.GetLastWriteTime(filename).ToUniversalTime();
|
||||
if (Request.Headers.ContainsKey("if-modified-since"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var ims = DateTime.Parse(Request.Headers["if-modified-since"]);
|
||||
if ((fileEditTime - ims).TotalSeconds < 2)
|
||||
{
|
||||
Response.Number = HttpResponseCode.NotModified;
|
||||
Response.Headers.Clear();
|
||||
//Response.Text = "Not Modified";
|
||||
Send(HttpComposeOption.SpecifiedHeadersOnly);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Response.Number = HttpResponseCode.OK;
|
||||
// Fri, 30 Oct 2007 14:19:41 GMT
|
||||
Response.Headers["Last-Modified"] = fileEditTime.ToString("ddd, dd MMM yyyy HH:mm:ss");
|
||||
FileInfo fi = new FileInfo(filename);
|
||||
Response.Headers["Content-Length"] = fi.Length.ToString();
|
||||
Send(HttpComposeOption.SpecifiedHeadersOnly);
|
||||
|
||||
//var fd = File.ReadAllBytes(filename);
|
||||
|
||||
//base.Send(fd);
|
||||
|
||||
|
||||
using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
|
||||
var buffer = new byte[60000];
|
||||
|
||||
|
||||
while (true)
|
||||
{
|
||||
var n = fs.Read(buffer, 0, 60000);
|
||||
|
||||
if (n <= 0)
|
||||
break;
|
||||
|
||||
//Thread.Sleep(50);
|
||||
await base.SendAsync(buffer, 0, n);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
Close();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Connected()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
protected override void Disconnected()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
79
Libraries/Esiur/Net/Http/HttpFilter.cs
Normal file
79
Libraries/Esiur/Net/Http/HttpFilter.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Data;
|
||||
using Esiur.Core;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Net.Http;
|
||||
|
||||
public abstract class HttpFilter : IResource
|
||||
{
|
||||
public Instance Instance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public event DestroyedEvent OnDestroy;
|
||||
public abstract AsyncReply<bool> Trigger(ResourceTrigger trigger);
|
||||
|
||||
/*
|
||||
public virtual void SessionModified(HTTPSession session, string key, object oldValue, object newValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void SessionExpired(HTTPSession session)
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
public abstract AsyncReply<bool> Execute(HttpConnection sender);
|
||||
|
||||
public virtual void ClientConnected(HttpConnection HTTP)
|
||||
{
|
||||
//return false;
|
||||
}
|
||||
|
||||
public virtual void ClientDisconnected(HttpConnection HTTP)
|
||||
{
|
||||
//return false;
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
}
|
||||
394
Libraries/Esiur/Net/Http/HttpServer.cs
Normal file
394
Libraries/Esiur/Net/Http/HttpServer.cs
Normal file
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Net.Sockets;
|
||||
using Esiur.Data;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Core;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Esiur.Resource;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Esiur.Net.Packets.Http;
|
||||
|
||||
namespace Esiur.Net.Http;
|
||||
public class HttpServer : NetworkServer<HttpConnection>, IResource
|
||||
{
|
||||
Dictionary<string, HttpSession> sessions = new Dictionary<string, HttpSession>();
|
||||
HttpFilter[] filters = new HttpFilter[0];
|
||||
|
||||
Dictionary<Packets.Http.HttpMethod, List<RouteInfo>> routes = new()
|
||||
{
|
||||
[Packets.Http.HttpMethod.GET] = new List<RouteInfo>(),
|
||||
[Packets.Http.HttpMethod.POST] = new List<RouteInfo>(),
|
||||
[Packets.Http.HttpMethod.HEAD] = new List<RouteInfo>(),
|
||||
[Packets.Http.HttpMethod.OPTIONS] = new List<RouteInfo>(),
|
||||
[Packets.Http.HttpMethod.UNKNOWN] = new List<RouteInfo>(),
|
||||
[Packets.Http.HttpMethod.DELETE] = new List<RouteInfo>(),
|
||||
[Packets.Http.HttpMethod.TRACE] = new List<RouteInfo>(),
|
||||
[Packets.Http.HttpMethod.CONNECT] = new List<RouteInfo>(),
|
||||
[Packets.Http.HttpMethod.PUT] = new List<RouteInfo>()
|
||||
};
|
||||
|
||||
//List<RouteInfo> GetRoutes = new List<RouteInfo>();
|
||||
//List<RouteInfo> PostRoutes = new List<RouteInfo>();
|
||||
|
||||
|
||||
class RouteInfo
|
||||
{
|
||||
public Delegate Handler;
|
||||
public Regex Pattern;
|
||||
Dictionary<string, ParameterInfo> ParameterIndex = new();
|
||||
int? SenderIndex;
|
||||
//bool HasSender;
|
||||
int ArgumentsCount;
|
||||
|
||||
public RouteInfo(Delegate handler, Regex pattern)
|
||||
{
|
||||
|
||||
Pattern = pattern;
|
||||
Handler = handler;
|
||||
|
||||
var ps = handler.Method.GetParameters();
|
||||
|
||||
ArgumentsCount = ps.Length;
|
||||
|
||||
var last = ps.LastOrDefault();
|
||||
|
||||
if (last != null && last.ParameterType == typeof(HttpConnection))
|
||||
{
|
||||
|
||||
SenderIndex = ps.Length - 1;
|
||||
for (var i = 0; i < ps.Length - 1; i++)
|
||||
ParameterIndex[ps[i].Name] = ps[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < ps.Length; i++)
|
||||
ParameterIndex[ps[i].Name] = ps[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool Invoke(HttpConnection sender)
|
||||
{
|
||||
var match = Pattern.Match(sender.Request.URL);
|
||||
|
||||
if (!match.Success)
|
||||
return false;
|
||||
|
||||
var args = new object[ArgumentsCount];
|
||||
|
||||
foreach (var kv in ParameterIndex)
|
||||
{
|
||||
var g = match.Groups[kv.Key];
|
||||
args[kv.Value.Position] = RuntimeCaster.Cast(g.Value, kv.Value.ParameterType);
|
||||
}
|
||||
|
||||
if (SenderIndex != null)
|
||||
args[(int)SenderIndex] = sender;
|
||||
|
||||
var rt = Handler.DynamicInvoke(args);
|
||||
|
||||
if (rt is bool)
|
||||
return (bool)rt;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public Instance Instance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Attribute]
|
||||
public virtual string IP
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Attribute]
|
||||
public virtual ushort Port
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Attribute]
|
||||
public virtual uint MaxPost
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Attribute]
|
||||
public virtual bool SSL
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Attribute]
|
||||
public virtual string Certificate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public HttpSession CreateSession(string id, int timeout)
|
||||
{
|
||||
var s = new HttpSession();
|
||||
|
||||
s.Set(id, timeout);
|
||||
|
||||
|
||||
sessions.Add(id, s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static string MakeCookie(string Item, string Value, DateTime Expires, string Domain, string Path, bool HttpOnly)
|
||||
{
|
||||
|
||||
//Set-Cookie: ckGeneric=CookieBody; expires=Sun, 30-Dec-2001 21:00:00 GMT; domain=.com.au; path=/
|
||||
//Set-Cookie: SessionID=another; expires=Fri, 29 Jun 2006 20:47:11 UTC; path=/
|
||||
string Cookie = Item + "=" + Value;
|
||||
|
||||
if (Expires.Ticks != 0)
|
||||
{
|
||||
Cookie += "; expires=" + Expires.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss") + " GMT";
|
||||
}
|
||||
if (Domain != null)
|
||||
{
|
||||
Cookie += "; domain=" + Domain;
|
||||
}
|
||||
if (Path != null)
|
||||
{
|
||||
Cookie += "; path=" + Path;
|
||||
}
|
||||
if (HttpOnly)
|
||||
{
|
||||
Cookie += "; HttpOnly";
|
||||
}
|
||||
return Cookie;
|
||||
}
|
||||
|
||||
protected override void ClientDisconnected(HttpConnection connection)
|
||||
{
|
||||
foreach (var filter in filters)
|
||||
filter.ClientDisconnected(connection);
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal bool Execute(HttpConnection sender)
|
||||
{
|
||||
if (!sender.WSMode)
|
||||
foreach (var route in routes[sender.Request.Method])
|
||||
if (route.Invoke(sender))
|
||||
return true;
|
||||
|
||||
|
||||
foreach (var resource in filters)
|
||||
if (resource.Execute(sender).Wait(30000))
|
||||
return true;
|
||||
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void MapGet(string pattern, Delegate handler)
|
||||
{
|
||||
var regex = Global.GetRouteRegex(pattern);
|
||||
var list = routes[Packets.Http.HttpMethod.GET];
|
||||
list.Add(new RouteInfo(handler, regex));
|
||||
}
|
||||
|
||||
public void MapPost(string pattern, Delegate handler)
|
||||
{
|
||||
var regex = Global.GetRouteRegex(pattern);
|
||||
var list = routes[Packets.Http.HttpMethod.POST];
|
||||
list.Add(new RouteInfo(handler, regex));
|
||||
}
|
||||
|
||||
/*
|
||||
protected override void SessionEnded(NetworkSession session)
|
||||
{
|
||||
// verify wether there are no active connections related to the session
|
||||
|
||||
foreach (HTTPConnection c in Connections)//.Values)
|
||||
{
|
||||
if (c.Session == session)
|
||||
{
|
||||
session.Refresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Instance instance in Instance.Children)
|
||||
{
|
||||
var f = (HTTPFilter)instance.Resource;
|
||||
f.SessionExpired((HTTPSession)session);
|
||||
}
|
||||
|
||||
base.SessionEnded((HTTPSession)session);
|
||||
//Sessions.Remove(Session.ID);
|
||||
//Session.Dispose();
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public int TTL
|
||||
{
|
||||
get
|
||||
{
|
||||
return Timeout;// mTimeout;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
public async AsyncReply<bool> Trigger(ResourceTrigger trigger)
|
||||
{
|
||||
|
||||
if (trigger == ResourceTrigger.Initialize)
|
||||
{
|
||||
//var ip = (IPAddress)Instance.Attributes["ip"];
|
||||
//var port = (int)Instance.Attributes["port"];
|
||||
//var ssl = (bool)Instance.Attributes["ssl"];
|
||||
//var cert = (string)Instance.Attributes["certificate"];
|
||||
|
||||
//if (ip == null) ip = IPAddress.Any;
|
||||
|
||||
Sockets.ISocket listener;
|
||||
IPAddress ipAdd;
|
||||
|
||||
if (IP == null)
|
||||
ipAdd = IPAddress.Any;
|
||||
else
|
||||
ipAdd = IPAddress.Parse(IP);
|
||||
|
||||
if (SSL)
|
||||
listener = new SSLSocket(new IPEndPoint(ipAdd, Port), new X509Certificate2(Certificate));
|
||||
else
|
||||
listener = new TCPSocket(new IPEndPoint(ipAdd, Port));
|
||||
|
||||
Start(listener);
|
||||
}
|
||||
else if (trigger == ResourceTrigger.Terminate)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
else if (trigger == ResourceTrigger.SystemReload)
|
||||
{
|
||||
await Trigger(ResourceTrigger.Terminate);
|
||||
await Trigger(ResourceTrigger.Initialize);
|
||||
}
|
||||
else if (trigger == ResourceTrigger.SystemInitialized)
|
||||
{
|
||||
filters = await Instance.Children<HttpFilter>();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public override void Add(HttpConnection connection)
|
||||
{
|
||||
connection.Server = this;
|
||||
base.Add(connection);
|
||||
}
|
||||
|
||||
public override void Remove(HttpConnection connection)
|
||||
{
|
||||
connection.Server = null;
|
||||
base.Remove(connection);
|
||||
}
|
||||
|
||||
protected override void ClientConnected(HttpConnection connection)
|
||||
{
|
||||
//if (filters.Length == 0 && routes.)
|
||||
//{
|
||||
// connection.Close();
|
||||
// return;
|
||||
//}
|
||||
|
||||
foreach (var resource in filters)
|
||||
{
|
||||
resource.ClientConnected(connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public int LocalPort
|
||||
{
|
||||
get
|
||||
{
|
||||
return cServer.LocalPort;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public HTTPServer(int Port)
|
||||
{
|
||||
cServer = new TServer();
|
||||
cServer.LocalPort = Port;
|
||||
cServer.StartServer();
|
||||
cServer.ClientConnected += new TServer.eClientConnected(ClientConnected);
|
||||
cServer.ClientDisConnected += new TServer.eClientDisConnected(ClientDisConnected);
|
||||
cServer.ClientIsSwitching += new TServer.eClientIsSwitching(ClientIsSwitching);
|
||||
cServer.DataReceived += new TServer.eDataReceived(DataReceived);
|
||||
|
||||
}*/
|
||||
|
||||
//~HTTPServer()
|
||||
//{
|
||||
// cServer.StopServer();
|
||||
//}
|
||||
}
|
||||
128
Libraries/Esiur/Net/Http/HttpSession.cs
Normal file
128
Libraries/Esiur/Net/Http/HttpSession.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Data;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Core;
|
||||
|
||||
namespace Esiur.Net.Http;
|
||||
public class HttpSession : IDestructible //<T> where T : TClient
|
||||
{
|
||||
public delegate void SessionModifiedEvent(HttpSession session, string key, object oldValue, object newValue);
|
||||
public delegate void SessionEndedEvent(HttpSession session);
|
||||
|
||||
private string id;
|
||||
private Timer timer;
|
||||
private int timeout;
|
||||
DateTime creation;
|
||||
DateTime lastAction;
|
||||
|
||||
private KeyList<string, object> variables;
|
||||
|
||||
public event SessionEndedEvent OnEnd;
|
||||
public event SessionModifiedEvent OnModify;
|
||||
public event DestroyedEvent OnDestroy;
|
||||
|
||||
public KeyList<string, object> Variables
|
||||
{
|
||||
get { return variables; }
|
||||
}
|
||||
|
||||
public HttpSession()
|
||||
{
|
||||
variables = new KeyList<string, object>();
|
||||
variables.OnModified += new KeyList<string, object>.Modified(VariablesModified);
|
||||
creation = DateTime.Now;
|
||||
}
|
||||
|
||||
internal void Set(string id, int timeout)
|
||||
{
|
||||
//modified = sessionModifiedEvent;
|
||||
//ended = sessionEndEvent;
|
||||
this.id = id;
|
||||
|
||||
if (this.timeout != 0)
|
||||
{
|
||||
this.timeout = timeout;
|
||||
timer = new Timer(OnSessionEndTimerCallback, null, TimeSpan.FromSeconds(timeout), TimeSpan.FromSeconds(0));
|
||||
creation = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSessionEndTimerCallback(object o)
|
||||
{
|
||||
OnEnd?.Invoke(this);
|
||||
}
|
||||
|
||||
void VariablesModified(string key, object oldValue, object newValue, KeyList<string, object> sender)
|
||||
{
|
||||
OnModify?.Invoke(this, key, oldValue, newValue);
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
timer.Dispose();
|
||||
timer = null;
|
||||
}
|
||||
|
||||
internal void Refresh()
|
||||
{
|
||||
lastAction = DateTime.Now;
|
||||
timer.Change(TimeSpan.FromSeconds(timeout), TimeSpan.FromSeconds(0));
|
||||
}
|
||||
|
||||
public int Timeout // Seconds
|
||||
{
|
||||
get
|
||||
{
|
||||
return timeout;
|
||||
}
|
||||
set
|
||||
{
|
||||
timeout = value;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public string Id
|
||||
{
|
||||
get { return id; }
|
||||
}
|
||||
|
||||
public DateTime LastAction
|
||||
{
|
||||
get { return lastAction; }
|
||||
}
|
||||
}
|
||||
|
||||
14
Libraries/Esiur/Net/INetworkReceiver.cs
Normal file
14
Libraries/Esiur/Net/INetworkReceiver.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net;
|
||||
|
||||
public interface INetworkReceiver<T>
|
||||
{
|
||||
void NetworkClose(T sender);
|
||||
void NetworkReceive(T sender, NetworkBuffer buffer);
|
||||
//void NetworkError(T sender);
|
||||
|
||||
void NetworkConnect(T sender);
|
||||
}
|
||||
206
Libraries/Esiur/Net/NetworkBuffer.cs
Normal file
206
Libraries/Esiur/Net/NetworkBuffer.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Esiur.Data;
|
||||
using Esiur.Misc;
|
||||
|
||||
namespace Esiur.Net;
|
||||
public class NetworkBuffer
|
||||
{
|
||||
byte[] data;
|
||||
|
||||
uint neededDataLength = 0;
|
||||
//bool trim;
|
||||
|
||||
object syncLock = new object();
|
||||
|
||||
//public object SyncLock
|
||||
//{
|
||||
// get { return syncLock; }
|
||||
//}
|
||||
|
||||
public NetworkBuffer()
|
||||
{
|
||||
data = new byte[0];
|
||||
}
|
||||
|
||||
public bool Protected
|
||||
{
|
||||
get
|
||||
{
|
||||
return neededDataLength > data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public uint Available
|
||||
{
|
||||
get
|
||||
{
|
||||
return (uint)data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//public void HoldForAtLeast(byte[] src, uint offset, uint size, uint needed)
|
||||
//{
|
||||
// HoldFor(src, offset, size, needed);
|
||||
// //trim = false;
|
||||
//}
|
||||
|
||||
//public void HoldForAtLeast(byte[] src, uint needed)
|
||||
//{
|
||||
// HoldForAtLeast(src, 0, (uint)src.Length, needed);
|
||||
//}
|
||||
|
||||
public void HoldForNextWrite(byte[] src)
|
||||
{
|
||||
//HoldForAtLeast(src, (uint)src.Length + 1);
|
||||
HoldFor(src, (uint)src.Length + 1);
|
||||
}
|
||||
|
||||
public void HoldForNextWrite(byte[] src, uint offset, uint size)
|
||||
{
|
||||
//HoldForAtLeast(src, offset, size, size + 1);
|
||||
HoldFor(src, offset, size, size + 1);
|
||||
}
|
||||
|
||||
|
||||
public void HoldFor(byte[] src, uint offset, uint size, uint needed)
|
||||
{
|
||||
lock (syncLock)
|
||||
{
|
||||
if (size >= needed)
|
||||
throw new Exception("Size >= Needed !");
|
||||
|
||||
//trim = true;
|
||||
data = DC.Combine(src, offset, size, data, 0, (uint)data.Length);
|
||||
neededDataLength = needed;
|
||||
|
||||
// Console.WriteLine("Hold StackTrace: '{0}'", Environment.StackTrace);
|
||||
|
||||
//Console.WriteLine("Holded {0} {1} {2} {3} - {4}", offset, size, needed, data.Length, GetHashCode());
|
||||
}
|
||||
}
|
||||
|
||||
public void HoldFor(byte[] src, uint needed)
|
||||
{
|
||||
HoldFor(src, 0, (uint)src.Length, needed);
|
||||
}
|
||||
|
||||
public bool Protect(byte[] data, uint offset, uint needed)//, bool exact = false)
|
||||
{
|
||||
uint dataLength = (uint)data.Length - offset;
|
||||
|
||||
// protection
|
||||
if (dataLength < needed)
|
||||
{
|
||||
//if (exact)
|
||||
// HoldFor(data, offset, dataLength, needed);
|
||||
//else
|
||||
//HoldForAtLeast(data, offset, dataLength, needed);
|
||||
HoldFor(data, offset, dataLength, needed);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Write(byte[] src)
|
||||
{
|
||||
Write(src, 0, (uint)src.Length);
|
||||
}
|
||||
|
||||
public void Write(byte[] src, uint offset, uint length)
|
||||
{
|
||||
//if (data.Length > 0)
|
||||
// Console.WriteLine();
|
||||
|
||||
lock (syncLock)
|
||||
DC.Append(ref data, src, offset, length);
|
||||
}
|
||||
|
||||
public bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
if (data.Length == 0)
|
||||
return false;
|
||||
if (data.Length < neededDataLength)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Read()
|
||||
{
|
||||
lock (syncLock)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
return null;
|
||||
|
||||
byte[] rt = null;
|
||||
|
||||
if (neededDataLength == 0)
|
||||
{
|
||||
rt = data;
|
||||
data = new byte[0];
|
||||
return rt;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Console.WriteLine("P STATE:" + data.Length + " " + neededDataLength);
|
||||
|
||||
if (data.Length >= neededDataLength)
|
||||
{
|
||||
//Console.WriteLine("data.Length >= neededDataLength " + data.Length + " >= " + neededDataLength + " " + trim);
|
||||
|
||||
//if (trim)
|
||||
//{
|
||||
// rt = DC.Clip(data, 0, neededDataLength);
|
||||
// data = DC.Clip(data, neededDataLength, (uint)data.Length - neededDataLength);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// return all data
|
||||
rt = data;
|
||||
data = new byte[0];
|
||||
//}
|
||||
|
||||
neededDataLength = 0;
|
||||
return rt;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
365
Libraries/Esiur/Net/NetworkConnection.cs
Normal file
365
Libraries/Esiur/Net/NetworkConnection.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Net.Sockets;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Net;
|
||||
public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocket>// <TS>: IResource where TS : NetworkSession
|
||||
{
|
||||
private Sockets.ISocket sock;
|
||||
// private bool connected;
|
||||
|
||||
private DateTime lastAction;
|
||||
|
||||
//public delegate void DataReceivedEvent(NetworkConnection sender, NetworkBuffer data);
|
||||
//public delegate void ConnectionClosedEvent(NetworkConnection sender);
|
||||
public delegate void NetworkConnectionEvent(NetworkConnection connection);
|
||||
|
||||
public event NetworkConnectionEvent OnConnect;
|
||||
//public event DataReceivedEvent OnDataReceived;
|
||||
public event NetworkConnectionEvent OnClose;
|
||||
|
||||
|
||||
public event DestroyedEvent OnDestroy;
|
||||
//object receivingLock = new object();
|
||||
|
||||
//object sendLock = new object();
|
||||
|
||||
bool processing = false;
|
||||
|
||||
// public INetworkReceiver<NetworkConnection> Receiver { get; set; }
|
||||
|
||||
public virtual void Destroy()
|
||||
{
|
||||
// remove references
|
||||
//sock.OnClose -= Socket_OnClose;
|
||||
//sock.OnConnect -= Socket_OnConnect;
|
||||
//sock.OnReceive -= Socket_OnReceive;
|
||||
sock?.Destroy();
|
||||
//Receiver = null;
|
||||
Close();
|
||||
sock = null;
|
||||
|
||||
OnClose = null;
|
||||
OnConnect = null;
|
||||
//OnDataReceived = null;
|
||||
OnDestroy?.Invoke(this);
|
||||
OnDestroy = null;
|
||||
}
|
||||
|
||||
public ISocket Socket
|
||||
{
|
||||
get
|
||||
{
|
||||
return sock;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Assign(ISocket socket)
|
||||
{
|
||||
lastAction = DateTime.Now;
|
||||
sock = socket;
|
||||
sock.Receiver = this;
|
||||
|
||||
//socket.OnReceive += Socket_OnReceive;
|
||||
//socket.OnClose += Socket_OnClose;
|
||||
//socket.OnConnect += Socket_OnConnect;
|
||||
}
|
||||
|
||||
//private void Socket_OnConnect()
|
||||
//{
|
||||
// OnConnect?.Invoke(this);
|
||||
//}
|
||||
|
||||
//private void Socket_OnClose()
|
||||
//{
|
||||
// ConnectionClosed();
|
||||
// OnClose?.Invoke(this);
|
||||
//}
|
||||
|
||||
//protected virtual void ConnectionClosed()
|
||||
//{
|
||||
|
||||
//}
|
||||
|
||||
//private void Socket_OnReceive(NetworkBuffer buffer)
|
||||
//{
|
||||
//}
|
||||
|
||||
public ISocket Unassign()
|
||||
{
|
||||
if (sock != null)
|
||||
{
|
||||
// connected = false;
|
||||
//sock.OnClose -= Socket_OnClose;
|
||||
//sock.OnConnect -= Socket_OnConnect;
|
||||
//sock.OnReceive -= Socket_OnReceive;
|
||||
sock.Receiver = null;
|
||||
|
||||
var rt = sock;
|
||||
sock = null;
|
||||
|
||||
return rt;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
//protected virtual void DataReceived(NetworkBuffer data)
|
||||
//{
|
||||
// if (OnDataReceived != null)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// OnDataReceived?.Invoke(this, data);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Global.Log("NetworkConenction:DataReceived", LogType.Error, ex.ToString());
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
//if (!connected)
|
||||
// return;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (sock != null)
|
||||
sock.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Global.Log("NetworkConenction:Close", LogType.Error, ex.ToString());
|
||||
|
||||
}
|
||||
|
||||
//finally
|
||||
//{
|
||||
//connected = false;
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
public DateTime LastAction
|
||||
{
|
||||
get { return lastAction; }
|
||||
}
|
||||
|
||||
public IPEndPoint RemoteEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
if (sock != null)
|
||||
return (IPEndPoint)sock.RemoteEndPoint;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public IPEndPoint LocalEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
if (sock != null)
|
||||
return (IPEndPoint)sock.LocalEndPoint;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
return sock == null ? false : sock.State == SocketState.Established;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public void CloseAndWait()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!connected)
|
||||
return;
|
||||
|
||||
if (sock != null)
|
||||
sock.Close();
|
||||
|
||||
while (connected)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public virtual AsyncReply<bool> SendAsync(byte[] message, int offset, int length)
|
||||
{
|
||||
try
|
||||
{
|
||||
lastAction = DateTime.Now;
|
||||
return sock.SendAsync(message, offset, length);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new AsyncReply<bool>(false);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Send(byte[] msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
sock?.Send(msg);
|
||||
lastAction = DateTime.Now;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Send(byte[] msg, int offset, int length)
|
||||
{
|
||||
try
|
||||
{
|
||||
sock.Send(msg, offset, length);
|
||||
lastAction = DateTime.Now;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Send(string data)
|
||||
{
|
||||
Send(Encoding.UTF8.GetBytes(data));
|
||||
}
|
||||
|
||||
public void NetworkClose(ISocket socket)
|
||||
{
|
||||
Disconnected();
|
||||
OnClose?.Invoke(this);
|
||||
}
|
||||
|
||||
public void NetworkConnect(ISocket socket)
|
||||
{
|
||||
Connected();
|
||||
OnConnect?.Invoke(this);
|
||||
}
|
||||
|
||||
//{
|
||||
//ConnectionClosed();
|
||||
//OnClose?.Invoke(this);
|
||||
|
||||
//Receiver?.NetworkClose(this);
|
||||
//}
|
||||
|
||||
//public void NetworkConenct(ISocket sender)
|
||||
//{
|
||||
// OnConnect?.Invoke(this);
|
||||
//}
|
||||
|
||||
protected abstract void DataReceived(NetworkBuffer buffer);
|
||||
protected abstract void Connected();
|
||||
protected abstract void Disconnected();
|
||||
|
||||
public void NetworkReceive(ISocket sender, NetworkBuffer buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Unassigned ?
|
||||
if (sock == null)
|
||||
return;
|
||||
|
||||
// Closed ?
|
||||
if (sock.State == SocketState.Closed)// || sock.State == SocketState.Terminated) // || !connected)
|
||||
return;
|
||||
|
||||
lastAction = DateTime.Now;
|
||||
|
||||
if (!processing)
|
||||
{
|
||||
processing = true;
|
||||
|
||||
try
|
||||
{
|
||||
//lock(buffer.SyncLock)
|
||||
while (buffer.Available > 0 && !buffer.Protected)
|
||||
{
|
||||
//Receiver?.NetworkReceive(this, buffer);
|
||||
DataReceived(buffer);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
processing = false;
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Global.Log("NetworkConnection", LogType.Warning, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//{
|
||||
// Receiver?.NetworkError(this);
|
||||
//throw new NotImplementedException();
|
||||
//}
|
||||
|
||||
//public void NetworkConnect(ISocket sender)
|
||||
//{
|
||||
// Receiver?.NetworkConnect(this);
|
||||
//throw new NotImplementedException();
|
||||
//}
|
||||
}
|
||||
297
Libraries/Esiur/Net/NetworkServer.cs
Normal file
297
Libraries/Esiur/Net/NetworkServer.cs
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Data;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Core;
|
||||
using Esiur.Net.Sockets;
|
||||
using Esiur.Resource;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Esiur.Net;
|
||||
|
||||
public abstract class NetworkServer<TConnection> : IDestructible where TConnection : NetworkConnection, new()
|
||||
{
|
||||
//private bool isRunning;
|
||||
private Sockets.ISocket listener;
|
||||
public AutoList<TConnection, NetworkServer<TConnection>> Connections { get; internal set; }
|
||||
|
||||
private Thread thread;
|
||||
|
||||
//protected abstract void DataReceived(TConnection sender, NetworkBuffer data);
|
||||
//protected abstract void ClientConnected(TConnection sender);
|
||||
//protected abstract void ClientDisconnected(TConnection sender);
|
||||
|
||||
|
||||
private Timer timer;
|
||||
//public KeyList<string, TSession> Sessions = new KeyList<string, TSession>();
|
||||
|
||||
public event DestroyedEvent OnDestroy;
|
||||
|
||||
//public AutoList<TConnection, NetworkServer<TConnection>> Connections => connections;
|
||||
|
||||
private void MinuteThread(object state)
|
||||
{
|
||||
List<TConnection> ToBeClosed = null;
|
||||
|
||||
|
||||
lock (Connections.SyncRoot)
|
||||
{
|
||||
foreach (TConnection c in Connections)
|
||||
{
|
||||
if (DateTime.Now.Subtract(c.LastAction).TotalSeconds >= Timeout)
|
||||
{
|
||||
if (ToBeClosed == null)
|
||||
ToBeClosed = new List<TConnection>();
|
||||
ToBeClosed.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ToBeClosed != null)
|
||||
{
|
||||
//Console.WriteLine("Term: " + ToBeClosed.Count + " " + this.listener.LocalEndPoint.ToString());
|
||||
foreach (TConnection c in ToBeClosed)
|
||||
c.Close();// CloseAndWait();
|
||||
|
||||
ToBeClosed.Clear();
|
||||
ToBeClosed = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start(Sockets.ISocket socket)//, uint timeout, uint clock)
|
||||
{
|
||||
if (listener != null)
|
||||
return;
|
||||
|
||||
|
||||
Connections = new AutoList<TConnection, NetworkServer<TConnection>>(this);
|
||||
|
||||
|
||||
if (Timeout > 0 & Clock > 0)
|
||||
{
|
||||
timer = new Timer(MinuteThread, null, TimeSpan.FromMinutes(0), TimeSpan.FromSeconds(Clock));
|
||||
}
|
||||
|
||||
|
||||
listener = socket;
|
||||
|
||||
// Start accepting
|
||||
//var r = listener.Accept();
|
||||
//r.Then(NewConnection);
|
||||
//r.timeout?.Dispose();
|
||||
|
||||
//var rt = listener.Accept().Then()
|
||||
thread = new Thread(new ThreadStart(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = listener.Accept();
|
||||
|
||||
if (s == null)
|
||||
{
|
||||
Global.Log("NetworkServer", LogType.Error, "sock == null");
|
||||
return;
|
||||
}
|
||||
|
||||
//Console.WriteLine("New Socket ... " + DateTime.Now);
|
||||
|
||||
var c = new TConnection();
|
||||
|
||||
c.Assign(s);
|
||||
Add(c);
|
||||
|
||||
try
|
||||
{
|
||||
ClientConnected(c);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// something wrong with the child.
|
||||
}
|
||||
|
||||
s.Begin();
|
||||
|
||||
// Accept more
|
||||
//listener.Accept().Then(NewConnection);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Global.Log(ex);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
thread.Start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Attribute]
|
||||
public uint Timeout
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Attribute]
|
||||
public uint Clock
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
var port = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (listener != null)
|
||||
{
|
||||
port = listener.LocalEndPoint.Port;
|
||||
listener.Close();
|
||||
}
|
||||
|
||||
// wait until the listener stops
|
||||
//while (isRunning)
|
||||
//{
|
||||
// Thread.Sleep(100);
|
||||
//}
|
||||
|
||||
//Console.WriteLine("Listener stopped");
|
||||
|
||||
var cons = Connections.ToArray();
|
||||
|
||||
//lock (connections.SyncRoot)
|
||||
//{
|
||||
foreach (TConnection con in cons)
|
||||
con.Close();
|
||||
//}
|
||||
|
||||
//Console.WriteLine("Sockets Closed");
|
||||
|
||||
//while (connections.Count > 0)
|
||||
//{
|
||||
// Console.WriteLine("Waiting... " + connections.Count);
|
||||
// Thread.Sleep(1000);
|
||||
//}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
Global.Log("NetworkServer", LogType.Warning, $"Server@{port} is down.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void Remove(TConnection connection)
|
||||
{
|
||||
connection.OnClose -= ClientDisconnectedEventReceiver;
|
||||
Connections.Remove(connection);
|
||||
}
|
||||
|
||||
public virtual void Add(TConnection connection)
|
||||
{
|
||||
connection.OnClose += ClientDisconnectedEventReceiver;
|
||||
Connections.Add(connection);
|
||||
}
|
||||
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
return listener.State == SocketState.Listening;
|
||||
//isRunning;
|
||||
}
|
||||
}
|
||||
|
||||
//public void OnDataReceived(ISocket sender, NetworkBuffer data)
|
||||
//{
|
||||
// DataReceived((TConnection)sender, data);
|
||||
//}
|
||||
|
||||
//public void OnClientConnect(ISocket sender)
|
||||
//{
|
||||
// if (sender == null)
|
||||
// return;
|
||||
|
||||
// if (sender.RemoteEndPoint == null || sender.LocalEndPoint == null)
|
||||
// { }
|
||||
// //Console.WriteLine("NULL");
|
||||
// else
|
||||
// Global.Log("Connections", LogType.Debug, sender.RemoteEndPoint.Address.ToString()
|
||||
// + "->" + sender.LocalEndPoint.Port + " at " + DateTime.UtcNow.ToString("d")
|
||||
// + " " + DateTime.UtcNow.ToString("d"), false);
|
||||
|
||||
// // Console.WriteLine("Connected " + sender.RemoteEndPoint.ToString());
|
||||
// ClientConnected((TConnection)sender);
|
||||
//}
|
||||
|
||||
//public void OnClientClose(ISocket sender)
|
||||
//{
|
||||
//}
|
||||
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
Stop();
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
|
||||
private void ClientDisconnectedEventReceiver(NetworkConnection connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
var con = connection as TConnection;
|
||||
con.Destroy();
|
||||
Remove(con);
|
||||
ClientDisconnected(con);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Global.Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void ClientDisconnected(TConnection connection);
|
||||
protected abstract void ClientConnected(TConnection connection);
|
||||
|
||||
~NetworkServer()
|
||||
{
|
||||
Stop();
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
128
Libraries/Esiur/Net/NetworkSession.cs
Normal file
128
Libraries/Esiur/Net/NetworkSession.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Data;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Core;
|
||||
|
||||
namespace Esiur.Net;
|
||||
public class NetworkSession : IDestructible //<T> where T : TClient
|
||||
{
|
||||
public delegate void SessionModifiedEvent(NetworkSession session, string key, object oldValue, object newValue);
|
||||
public delegate void SessionEndedEvent(NetworkSession session);
|
||||
|
||||
private string id;
|
||||
private Timer timer;
|
||||
private int timeout;
|
||||
DateTime creation;
|
||||
DateTime lastAction;
|
||||
|
||||
private KeyList<string, object> variables;
|
||||
|
||||
public event SessionEndedEvent OnEnd;
|
||||
public event SessionModifiedEvent OnModify;
|
||||
public event DestroyedEvent OnDestroy;
|
||||
|
||||
public KeyList<string, object> Variables
|
||||
{
|
||||
get { return variables; }
|
||||
}
|
||||
|
||||
public NetworkSession()
|
||||
{
|
||||
variables = new KeyList<string, object>();
|
||||
variables.OnModified += new KeyList<string, object>.Modified(VariablesModified);
|
||||
creation = DateTime.Now;
|
||||
}
|
||||
|
||||
internal void Set(string id, int timeout)
|
||||
{
|
||||
//modified = sessionModifiedEvent;
|
||||
//ended = sessionEndEvent;
|
||||
this.id = id;
|
||||
|
||||
if (this.timeout != 0)
|
||||
{
|
||||
this.timeout = timeout;
|
||||
timer = new Timer(OnSessionEndTimerCallback, null, TimeSpan.FromSeconds(timeout), TimeSpan.FromSeconds(0));
|
||||
creation = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSessionEndTimerCallback(object o)
|
||||
{
|
||||
OnEnd?.Invoke(this);
|
||||
}
|
||||
|
||||
void VariablesModified(string key, object oldValue, object newValue, KeyList<string, object> sender)
|
||||
{
|
||||
OnModify?.Invoke(this, key, oldValue, newValue);
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
timer.Dispose();
|
||||
timer = null;
|
||||
}
|
||||
|
||||
internal void Refresh()
|
||||
{
|
||||
lastAction = DateTime.Now;
|
||||
timer.Change(TimeSpan.FromSeconds(timeout), TimeSpan.FromSeconds(0));
|
||||
}
|
||||
|
||||
public int Timeout // Seconds
|
||||
{
|
||||
get
|
||||
{
|
||||
return timeout;
|
||||
}
|
||||
set
|
||||
{
|
||||
timeout = value;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public string Id
|
||||
{
|
||||
get { return id; }
|
||||
}
|
||||
|
||||
public DateTime LastAction
|
||||
{
|
||||
get { return lastAction; }
|
||||
}
|
||||
}
|
||||
|
||||
24
Libraries/Esiur/Net/Packets/EpAuthExtensions.cs
Normal file
24
Libraries/Esiur/Net/Packets/EpAuthExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public static class EpAuthExtensions
|
||||
{
|
||||
public static EpAuthPacketIAuthFormat GetIAuthFormat(this object value)
|
||||
{
|
||||
if (value is string)
|
||||
return EpAuthPacketIAuthFormat.Text;
|
||||
else if (value is int || value is uint
|
||||
|| value is byte || value is sbyte
|
||||
|| value is short || value is ushort
|
||||
|| value is long || value is ulong)
|
||||
return EpAuthPacketIAuthFormat.Number;
|
||||
else if (value.GetType().IsArray)
|
||||
return EpAuthPacketIAuthFormat.Choice;
|
||||
|
||||
throw new Exception("Unknown IAuth format");
|
||||
}
|
||||
}
|
||||
}
|
||||
191
Libraries/Esiur/Net/Packets/EpAuthPacket.cs
Normal file
191
Libraries/Esiur/Net/Packets/EpAuthPacket.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2026 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Data;
|
||||
using Esiur.Security.Authority;
|
||||
using Esiur.Security.Cryptography;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace Esiur.Net.Packets;
|
||||
|
||||
public class EpAuthPacket : Packet
|
||||
{
|
||||
public EpAuthPacketCommand Command
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public EpAuthPacketAction Action
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public EpAuthPacketEvent Event
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public EpAuthPacketAcknowledgement Acknowledgement
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public AuthenticationMode AuthMode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public EncryptionMode EncryptionMode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
//public AuthenticationMethod AuthenticationMethod
|
||||
//{
|
||||
// get;
|
||||
// set;
|
||||
//}
|
||||
|
||||
|
||||
public byte ErrorCode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string Message
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public byte[] SessionId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public ParsedTdu? Tdu
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// IAuth Reference
|
||||
public uint Reference
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private uint dataLengthNeeded;
|
||||
|
||||
bool NotEnough(uint offset, uint ends, uint needed)
|
||||
{
|
||||
if (offset + needed > ends)
|
||||
{
|
||||
dataLengthNeeded = needed - (ends - offset);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Command.ToString() + " " + Action.ToString();
|
||||
}
|
||||
|
||||
public override long Parse(byte[] data, uint offset, uint ends)
|
||||
{
|
||||
var oOffset = offset;
|
||||
|
||||
if (NotEnough(offset, ends, 1))
|
||||
return -dataLengthNeeded;
|
||||
|
||||
Command = (EpAuthPacketCommand)(data[offset] >> 6);
|
||||
var hasTdu = (data[offset] & 0x20) != 0;
|
||||
|
||||
if (Command == EpAuthPacketCommand.Initialize)
|
||||
{
|
||||
AuthMode = (AuthenticationMode)(data[offset] >> 3 & 0x7);
|
||||
EncryptionMode = (EncryptionMode)(data[offset++] & 0x7);
|
||||
}
|
||||
else if (Command == EpAuthPacketCommand.Acknowledge)
|
||||
{
|
||||
// remove last two reserved LSBs
|
||||
Acknowledgement = (EpAuthPacketAcknowledgement)(data[offset++]);// & 0xFC);
|
||||
}
|
||||
else if (Command == EpAuthPacketCommand.Action)
|
||||
{
|
||||
// remove last two reserved LSBs
|
||||
Action = (EpAuthPacketAction)(data[offset++]);// & 0xFC);
|
||||
}
|
||||
else if (Command == EpAuthPacketCommand.Event)
|
||||
{
|
||||
// remove last two reserved LSBs
|
||||
Event = (EpAuthPacketEvent)(data[offset++]);// & 0xFC);
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1; // invalid command
|
||||
}
|
||||
|
||||
if (hasTdu)
|
||||
{
|
||||
if (NotEnough(offset, ends, 1))
|
||||
return -dataLengthNeeded;
|
||||
|
||||
Tdu = ParsedTdu.Parse(data, offset, ends);
|
||||
|
||||
if (Tdu.Value.Class == TduClass.Invalid)
|
||||
return -(int)Tdu.Value.TotalLength;
|
||||
|
||||
offset += (uint)Tdu.Value.TotalLength;
|
||||
|
||||
}
|
||||
|
||||
return offset - oOffset;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
18
Libraries/Esiur/Net/Packets/EpAuthPacketAcknowledgement.cs
Normal file
18
Libraries/Esiur/Net/Packets/EpAuthPacketAcknowledgement.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public enum EpAuthPacketAcknowledgement : byte
|
||||
{
|
||||
Denied = 0x40, // no reason, terminate connection
|
||||
NotSupported = 0x41, // auth not supported, terminate connection
|
||||
TrySupported = 0x42, // auth not supported, but other auth methods in the reply are supported. connection is still open
|
||||
Retry = 0x43, // try another auth method, connection is still open
|
||||
ProceedToHandshake = 0x44, // auth method accepted, proceed to handshake, connection is still open
|
||||
ProceedToFinalHandshake = 0x45, // auth method accepted, proceed to final handshake, connection is still open
|
||||
ProceedToEstablishSession = 0x46, // auth method accepted, proceed to establish session, connection is still open
|
||||
SessionEstablished = 0x47, // session established, session Id provided, switch to session mode, connection is still open
|
||||
}
|
||||
}
|
||||
34
Libraries/Esiur/Net/Packets/EpAuthPacketAction.cs
Normal file
34
Libraries/Esiur/Net/Packets/EpAuthPacketAction.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public enum EpAuthPacketAction : byte
|
||||
{
|
||||
Handshake = 0x80,
|
||||
FinalHandshake = 0x81,
|
||||
|
||||
//AuthenticateHash = 0x80,
|
||||
//AuthenticatePublicHash = 0x81,
|
||||
//AuthenticatePrivateHash = 0x82,
|
||||
//AuthenticatePublicPrivateHash = 0x83,
|
||||
|
||||
//AuthenticatePrivateHashCert = 0x88,
|
||||
//AuthenticatePublicPrivateHashCert = 0x89,
|
||||
|
||||
//IAuthPlain = 0x90,
|
||||
//IAuthHashed = 0x91,
|
||||
//IAuthEncrypted = 0x92,
|
||||
|
||||
|
||||
//EstablishNewSession = 0x98,
|
||||
//EstablishResumeSession = 0x99,
|
||||
|
||||
//EncryptKeyExchange = 0xA0,
|
||||
|
||||
//RegisterEndToEndKey = 0xA8,
|
||||
//RegisterHomomorphic = 0xA9,
|
||||
|
||||
}
|
||||
}
|
||||
32
Libraries/Esiur/Net/Packets/EpAuthPacketAuthMode.cs
Normal file
32
Libraries/Esiur/Net/Packets/EpAuthPacketAuthMode.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public enum EpAuthPacketAuthMode : byte
|
||||
{
|
||||
NoAuh = 0x0,
|
||||
InitializerIdentity = 0x1,
|
||||
ResponderIdentity = 0x2,
|
||||
DualIdentity = 0x3,
|
||||
|
||||
|
||||
//NoAuthNoAuth = 0x0, //0b00000000,
|
||||
//NoAuthCredentials = 0x4, //0b00000100,
|
||||
//NoAuthToken = 0x8, //0b00001000,
|
||||
//NoAuthCertificate = 0xC, //0b00001100,
|
||||
//CredentialsNoAuth = 0x10, //0b00010000,
|
||||
//CredentialsCredentials = 0x14, //0b00010100,
|
||||
//CredentialsToken = 0x18, //0b00011000,
|
||||
//CredentialsCertificate = 0x1c, //0b00011100,
|
||||
//TokenNoAuth = 0x20, //0b00100000,
|
||||
//TokenCredentials = 0x24, //0b00100100,
|
||||
//TokenToken = 0x28, //0b00101000,
|
||||
//TokenCertificate = 0x2c, //0b00101100,
|
||||
//CertificateNoAuth = 0x30, //0b00110000,
|
||||
//CertificateCredentials = 0x34,// 0b00110100,
|
||||
//CertificateToken = 0x38, //0b00111000,
|
||||
//CertificateCertificate = 0x3c, //0b00111100,
|
||||
}
|
||||
}
|
||||
14
Libraries/Esiur/Net/Packets/EpAuthPacketCommand.cs
Normal file
14
Libraries/Esiur/Net/Packets/EpAuthPacketCommand.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public enum EpAuthPacketCommand : byte
|
||||
{
|
||||
Initialize = 0x0,
|
||||
Acknowledge = 0x1,
|
||||
Action = 0x2,
|
||||
Event = 0x3,
|
||||
}
|
||||
}
|
||||
13
Libraries/Esiur/Net/Packets/EpAuthPacketEncryptionMode.cs
Normal file
13
Libraries/Esiur/Net/Packets/EpAuthPacketEncryptionMode.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public enum EpAuthPacketEncryptionMode
|
||||
{
|
||||
NoEncryption,
|
||||
EncryptWithSessionKey,
|
||||
EncryptWithSessionKeyAndAddress,
|
||||
}
|
||||
}
|
||||
19
Libraries/Esiur/Net/Packets/EpAuthPacketEvent.cs
Normal file
19
Libraries/Esiur/Net/Packets/EpAuthPacketEvent.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public enum EpAuthPacketEvent : byte
|
||||
{
|
||||
ErrorTerminate = 0xC0,
|
||||
ErrorMustEncrypt = 0xC1,
|
||||
ErrorRetry = 0xC2,
|
||||
|
||||
IndicationEstablished = 0xC8,
|
||||
|
||||
IAuthPlain = 0xD0,
|
||||
IAuthHashed = 0xD1,
|
||||
IAuthEncrypted = 0xD2
|
||||
}
|
||||
}
|
||||
24
Libraries/Esiur/Net/Packets/EpAuthPacketHeader.cs
Normal file
24
Libraries/Esiur/Net/Packets/EpAuthPacketHeader.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public enum EpAuthPacketHeader
|
||||
{
|
||||
Version,
|
||||
Domain,
|
||||
SupportedAuthentications ,
|
||||
SupportedHashAlgorithms,
|
||||
SupportedCiphers,
|
||||
SupportedCompression,
|
||||
SupportedMultiFactorAuthentications,
|
||||
CipherType,
|
||||
CipherKey,
|
||||
SoftwareIdentity,
|
||||
Referrer,
|
||||
Time,
|
||||
IPAddress,
|
||||
AuthenticationData,
|
||||
}
|
||||
}
|
||||
16
Libraries/Esiur/Net/Packets/EpAuthPacketIAuthDestination.cs
Normal file
16
Libraries/Esiur/Net/Packets/EpAuthPacketIAuthDestination.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Net.Packets
|
||||
{
|
||||
public enum EpAuthPacketIAuthDestination
|
||||
{
|
||||
Self = 0,
|
||||
Device = 1, // logged in device
|
||||
Email = 2,
|
||||
SMS = 3,
|
||||
App = 4, // Authenticator app
|
||||
ThirdParty = 5, // usually a second person
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user