From 7ae722ab515382c8829fe05dfcfc1b8915b1f500 Mon Sep 17 00:00:00 2001 From: Ahmed Zamil Date: Fri, 15 Sep 2017 23:40:03 +0300 Subject: [PATCH] Add project files. --- .../Esiur.Stores.MongoDB.csproj | 16 + Esiur.Stores.MongoDB/MongoDBStore.cs | 440 +++++++ Esiur.sln | 34 + Esiur/Data/AutoList.cs | 253 ++++ Esiur/Data/BinaryList.cs | 196 +++ Esiur/Data/Codec.cs | 991 +++++++++++++++ Esiur/Data/DataConverter.cs | 1069 +++++++++++++++++ Esiur/Data/DataType.cs | 95 ++ Esiur/Data/KeyList.cs | 204 ++++ Esiur/Data/NotModified.cs | 13 + Esiur/Data/StringKeyList.cs | 194 +++ Esiur/Data/Structure.cs | 83 ++ Esiur/Engine/AsyncBag.cs | 64 + Esiur/Engine/AsyncQueue.cs | 57 + Esiur/Engine/AsyncReply.cs | 75 ++ Esiur/Engine/AsyncReplyGeneric.cs | 36 + Esiur/Engine/IDestructible.cs | 16 + Esiur/Engine/LogType.cs | 15 + Esiur/Esiur.csproj | 32 + Esiur/Misc/Global.cs | 435 +++++++ Esiur/Net/DataLink/PacketFilter.cs | 32 + Esiur/Net/DataLink/PacketServer.cs | 99 ++ Esiur/Net/DataLink/PacketSource.cs | 71 ++ Esiur/Net/HTTP/HTTPConnection.cs | 346 ++++++ Esiur/Net/HTTP/HTTPFilter.cs | 58 + Esiur/Net/HTTP/HTTPServer.cs | 416 +++++++ Esiur/Net/HTTP/HTTPSession.cs | 106 ++ Esiur/Net/HTTP/IIPoWS.cs | 51 + Esiur/Net/IIP/DistributedConnection.cs | 514 ++++++++ .../Net/IIP/DistributedConnectionProtocol.cs | 802 +++++++++++++ Esiur/Net/IIP/DistributedResource.cs | 374 ++++++ Esiur/Net/IIP/DistributedResourceEvent.cs | 10 + Esiur/Net/IIP/DistributedResourceQueueItem.cs | 49 + Esiur/Net/IIP/DistributedServer.cs | 117 ++ Esiur/Net/IIP/DistributedSession.cs | 14 + Esiur/Net/NetworkBuffer.cs | 167 +++ Esiur/Net/NetworkConnection.cs | 248 ++++ Esiur/Net/NetworkServer.cs | 346 ++++++ Esiur/Net/NetworkSession.cs | 106 ++ Esiur/Net/Packets/HTTPRequestPacket.cs | 287 +++++ Esiur/Net/Packets/HTTPResponsePacket.cs | 284 +++++ Esiur/Net/Packets/IIPAuthPacket.cs | 382 ++++++ Esiur/Net/Packets/IIPPacket.cs | 550 +++++++++ Esiur/Net/Packets/Packet.cs | 367 ++++++ Esiur/Net/Packets/WebsocketPacket.cs | 188 +++ Esiur/Net/Sockets/ISocket.cs | 39 + Esiur/Net/Sockets/SSLSocket.cs | 310 +++++ Esiur/Net/Sockets/SocketState.cs | 18 + Esiur/Net/Sockets/TCPSocket.cs | 291 +++++ Esiur/Net/Sockets/WSSocket.cs | 220 ++++ Esiur/Net/TCP/TCPConnection.cs | 27 + Esiur/Net/TCP/TCPFilter.cs | 42 + Esiur/Net/TCP/TCPServer.cs | 195 +++ Esiur/Net/TCP/TCPSession.cs | 13 + Esiur/Net/UDP/UDPFilter.cs | 33 + Esiur/Net/UDP/UDPServer.cs | 173 +++ Esiur/Resource/IResource.cs | 23 + Esiur/Resource/IStore.cs | 17 + Esiur/Resource/Instance.cs | 427 +++++++ Esiur/Resource/ResourceEvent.cs | 30 + Esiur/Resource/ResourceEventHandler.cs | 13 + Esiur/Resource/ResourceFunction.cs | 28 + Esiur/Resource/ResourceProperty.cs | 39 + Esiur/Resource/ResourceTrigger.cs | 19 + Esiur/Resource/Storable.cs | 48 + Esiur/Resource/Template/EventTemplate.cs | 33 + Esiur/Resource/Template/FunctionTemplate.cs | 42 + Esiur/Resource/Template/MemberTemplate.cs | 29 + Esiur/Resource/Template/PropertyTemplate.cs | 71 ++ Esiur/Resource/Template/ResourceTemplate.cs | 359 ++++++ Esiur/Resource/Warehouse.cs | 258 ++++ .../Security/Authority/AlienAuthentication.cs | 17 + Esiur/Security/Authority/Authentication.cs | 35 + .../Security/Authority/AuthenticationState.cs | 18 + .../Security/Authority/AuthenticationType.cs | 16 + Esiur/Security/Authority/CACertificate.cs | 163 +++ Esiur/Security/Authority/Certificate.cs | 198 +++ Esiur/Security/Authority/CertificateType.cs | 18 + .../Authority/ClientAuthentication.cs | 17 + .../Authority/CoHostAuthentication.cs | 17 + Esiur/Security/Authority/DomainCertificate.cs | 220 ++++ .../Security/Authority/HostAuthentication.cs | 17 + Esiur/Security/Authority/Session.cs | 23 + Esiur/Security/Authority/Source.cs | 30 + .../Security/Authority/SourceAttributeType.cs | 53 + Esiur/Security/Authority/UserCertificate.cs | 230 ++++ .../AsymetricEncryptionAlgorithmType.cs | 16 + .../SymetricEncryptionAlgorithmType.cs | 15 + Esiur/Security/Integrity/HashFunctionType.cs | 17 + Esiur/Security/Membership/IDomain.cs | 16 + Esiur/Security/Membership/IMembership.cs | 48 + Esiur/Security/Membership/IUser.cs | 17 + Esiur/Security/Permissions/ActionType.cs | 17 + .../Permissions/IPermissionManager.cs | 18 + Esiur/Stores/MemoryStore.cs | 56 + Test/MyMembership.cs | 39 + Test/MyObject.cs | 92 ++ Test/Program.cs | 152 +++ Test/Test.csproj | 13 + 99 files changed, 14687 insertions(+) create mode 100644 Esiur.Stores.MongoDB/Esiur.Stores.MongoDB.csproj create mode 100644 Esiur.Stores.MongoDB/MongoDBStore.cs create mode 100644 Esiur.sln create mode 100644 Esiur/Data/AutoList.cs create mode 100644 Esiur/Data/BinaryList.cs create mode 100644 Esiur/Data/Codec.cs create mode 100644 Esiur/Data/DataConverter.cs create mode 100644 Esiur/Data/DataType.cs create mode 100644 Esiur/Data/KeyList.cs create mode 100644 Esiur/Data/NotModified.cs create mode 100644 Esiur/Data/StringKeyList.cs create mode 100644 Esiur/Data/Structure.cs create mode 100644 Esiur/Engine/AsyncBag.cs create mode 100644 Esiur/Engine/AsyncQueue.cs create mode 100644 Esiur/Engine/AsyncReply.cs create mode 100644 Esiur/Engine/AsyncReplyGeneric.cs create mode 100644 Esiur/Engine/IDestructible.cs create mode 100644 Esiur/Engine/LogType.cs create mode 100644 Esiur/Esiur.csproj create mode 100644 Esiur/Misc/Global.cs create mode 100644 Esiur/Net/DataLink/PacketFilter.cs create mode 100644 Esiur/Net/DataLink/PacketServer.cs create mode 100644 Esiur/Net/DataLink/PacketSource.cs create mode 100644 Esiur/Net/HTTP/HTTPConnection.cs create mode 100644 Esiur/Net/HTTP/HTTPFilter.cs create mode 100644 Esiur/Net/HTTP/HTTPServer.cs create mode 100644 Esiur/Net/HTTP/HTTPSession.cs create mode 100644 Esiur/Net/HTTP/IIPoWS.cs create mode 100644 Esiur/Net/IIP/DistributedConnection.cs create mode 100644 Esiur/Net/IIP/DistributedConnectionProtocol.cs create mode 100644 Esiur/Net/IIP/DistributedResource.cs create mode 100644 Esiur/Net/IIP/DistributedResourceEvent.cs create mode 100644 Esiur/Net/IIP/DistributedResourceQueueItem.cs create mode 100644 Esiur/Net/IIP/DistributedServer.cs create mode 100644 Esiur/Net/IIP/DistributedSession.cs create mode 100644 Esiur/Net/NetworkBuffer.cs create mode 100644 Esiur/Net/NetworkConnection.cs create mode 100644 Esiur/Net/NetworkServer.cs create mode 100644 Esiur/Net/NetworkSession.cs create mode 100644 Esiur/Net/Packets/HTTPRequestPacket.cs create mode 100644 Esiur/Net/Packets/HTTPResponsePacket.cs create mode 100644 Esiur/Net/Packets/IIPAuthPacket.cs create mode 100644 Esiur/Net/Packets/IIPPacket.cs create mode 100644 Esiur/Net/Packets/Packet.cs create mode 100644 Esiur/Net/Packets/WebsocketPacket.cs create mode 100644 Esiur/Net/Sockets/ISocket.cs create mode 100644 Esiur/Net/Sockets/SSLSocket.cs create mode 100644 Esiur/Net/Sockets/SocketState.cs create mode 100644 Esiur/Net/Sockets/TCPSocket.cs create mode 100644 Esiur/Net/Sockets/WSSocket.cs create mode 100644 Esiur/Net/TCP/TCPConnection.cs create mode 100644 Esiur/Net/TCP/TCPFilter.cs create mode 100644 Esiur/Net/TCP/TCPServer.cs create mode 100644 Esiur/Net/TCP/TCPSession.cs create mode 100644 Esiur/Net/UDP/UDPFilter.cs create mode 100644 Esiur/Net/UDP/UDPServer.cs create mode 100644 Esiur/Resource/IResource.cs create mode 100644 Esiur/Resource/IStore.cs create mode 100644 Esiur/Resource/Instance.cs create mode 100644 Esiur/Resource/ResourceEvent.cs create mode 100644 Esiur/Resource/ResourceEventHandler.cs create mode 100644 Esiur/Resource/ResourceFunction.cs create mode 100644 Esiur/Resource/ResourceProperty.cs create mode 100644 Esiur/Resource/ResourceTrigger.cs create mode 100644 Esiur/Resource/Storable.cs create mode 100644 Esiur/Resource/Template/EventTemplate.cs create mode 100644 Esiur/Resource/Template/FunctionTemplate.cs create mode 100644 Esiur/Resource/Template/MemberTemplate.cs create mode 100644 Esiur/Resource/Template/PropertyTemplate.cs create mode 100644 Esiur/Resource/Template/ResourceTemplate.cs create mode 100644 Esiur/Resource/Warehouse.cs create mode 100644 Esiur/Security/Authority/AlienAuthentication.cs create mode 100644 Esiur/Security/Authority/Authentication.cs create mode 100644 Esiur/Security/Authority/AuthenticationState.cs create mode 100644 Esiur/Security/Authority/AuthenticationType.cs create mode 100644 Esiur/Security/Authority/CACertificate.cs create mode 100644 Esiur/Security/Authority/Certificate.cs create mode 100644 Esiur/Security/Authority/CertificateType.cs create mode 100644 Esiur/Security/Authority/ClientAuthentication.cs create mode 100644 Esiur/Security/Authority/CoHostAuthentication.cs create mode 100644 Esiur/Security/Authority/DomainCertificate.cs create mode 100644 Esiur/Security/Authority/HostAuthentication.cs create mode 100644 Esiur/Security/Authority/Session.cs create mode 100644 Esiur/Security/Authority/Source.cs create mode 100644 Esiur/Security/Authority/SourceAttributeType.cs create mode 100644 Esiur/Security/Authority/UserCertificate.cs create mode 100644 Esiur/Security/Cryptography/AsymetricEncryptionAlgorithmType.cs create mode 100644 Esiur/Security/Cryptography/SymetricEncryptionAlgorithmType.cs create mode 100644 Esiur/Security/Integrity/HashFunctionType.cs create mode 100644 Esiur/Security/Membership/IDomain.cs create mode 100644 Esiur/Security/Membership/IMembership.cs create mode 100644 Esiur/Security/Membership/IUser.cs create mode 100644 Esiur/Security/Permissions/ActionType.cs create mode 100644 Esiur/Security/Permissions/IPermissionManager.cs create mode 100644 Esiur/Stores/MemoryStore.cs create mode 100644 Test/MyMembership.cs create mode 100644 Test/MyObject.cs create mode 100644 Test/Program.cs create mode 100644 Test/Test.csproj diff --git a/Esiur.Stores.MongoDB/Esiur.Stores.MongoDB.csproj b/Esiur.Stores.MongoDB/Esiur.Stores.MongoDB.csproj new file mode 100644 index 0000000..3e5c8dc --- /dev/null +++ b/Esiur.Stores.MongoDB/Esiur.Stores.MongoDB.csproj @@ -0,0 +1,16 @@ + + + + netstandard1.5 + + + + + + + + + + + + \ No newline at end of file diff --git a/Esiur.Stores.MongoDB/MongoDBStore.cs b/Esiur.Stores.MongoDB/MongoDBStore.cs new file mode 100644 index 0000000..3519a9e --- /dev/null +++ b/Esiur.Stores.MongoDB/MongoDBStore.cs @@ -0,0 +1,440 @@ +using Esiur.Resource; +using System; +using Esiur.Engine; +using MongoDB.Driver.Core; +using MongoDB.Driver; +using MongoDB.Bson; +using Esiur.Data; +using System.Collections.Generic; +using System.Reflection; + +namespace Esiur.Stores.MongoDB +{ + public class MongoDBStore : IStore + { + public Instance Instance { get; set; } + + public event DestroyedEvent OnDestroy; + MongoClient client; + IMongoDatabase database; + + Dictionary resources = new Dictionary(); + + + public int Count + { + get { return resources.Count; } + } + public void Destroy() + { + + } + + public MongoDBStore() + { + client = new MongoClient(); + this.database = client.GetDatabase("esiur"); + } + + public MongoDBStore(string connectionString, string database) + { + client = new MongoClient(connectionString); + this.database = client.GetDatabase(database); + } + + + AsyncReply Fetch(string id) + { + var filter = Builders.Filter.Eq("_id", new BsonObjectId(new ObjectId(id))); + var list = this.database.GetCollection("resources").Find(filter).ToList(); + if (list.Count == 0) + return new AsyncReply(null); + var document = list[0]; + + + IResource resource = (IResource)Activator.CreateInstance(Type.GetType(document["classname"].AsString)); + resources.Add(document["_id"].AsObjectId.ToString(), resource); + + Warehouse.Put(resource, document["name"].AsString, this); + + + var parents = document["parents"].AsBsonArray; + var children = document["children"].AsBsonArray; + + var bag = new AsyncBag(); + + foreach (var p in parents) + { + var ap = Warehouse.Get(p.AsString); + bag.Add(ap); + ap.Then((x) => + { + resource.Instance.Parents.Add(x); + }); + } + + foreach (var c in children) + { + + var ac = Warehouse.Get(c.AsString); + bag.Add(ac); + ac.Then((x) => + { + resource.Instance.Children.Add(x); + }); + } + + // Load values + var values = document["values"].AsBsonDocument; + + + foreach (var v in values) + { +#if NETSTANDARD1_5 + var pi = resource.GetType().GetTypeInfo().GetProperty(v.Name); +#else + var pi = resource.GetType().GetProperty(pt.Name); +#endif + + var av = Parse(v.Value); + bag.Add(av); + av.Then((x) => + { + if (pi.CanWrite) + pi.SetValue(resource, DC.CastConvert(x, pi.PropertyType)); + }); + } + + bag.Seal(); + + var rt = new AsyncReply(); + + bag.Then((x) => + { + rt.Trigger(resource); + }); + + return rt; + } + + AsyncReply Parse(BsonValue value) + { + if (value.BsonType == BsonType.Document) + { + var doc = value.AsBsonDocument; + if (doc["type"] == 0) + { + return Warehouse.Get(doc["link"].AsString); + } // structure + else if (doc["type"] == 1) + { + var bag = new AsyncBag(); + var rt = new AsyncReply(); + + var bs = (BsonDocument)doc["values"].AsBsonDocument; + var s = new Structure(); + + foreach (var v in bs) + bag.Add(Parse(v.Value)); + + bag.Seal(); + bag.Then((x) => + { + for (var i = 0; i < x.Length; i++) + s[bs.GetElement(i).Name] = x[i]; + + rt.Trigger(s); + }); + + return rt; + } + else + return new AsyncReply(null); + } + else if (value.BsonType == BsonType.Array) + { + var array = value.AsBsonArray; + var bag = new AsyncBag(); + + foreach (var v in array) + bag.Add(Parse(v)); + + bag.Seal(); + + return bag; + } + else + { + return new AsyncReply(value.RawValue); + } + } + + public AsyncReply Get(string path) + { + var p = path.Split('/'); + if (p.Length == 2) + if (p[0] == "id") + { + // load from Id + + if (resources.ContainsKey(p[1])) + return new AsyncReply(resources[p[1]]); + else + return Fetch(p[1]); + } + + return new AsyncReply(null); + } + + public string Link(IResource resource) + { + return this.Instance.Name + "/id/" + (string)resource.Instance.Attributes["objectId"]; + } + + public bool Put(IResource resource) + { + + foreach (var kv in resources) + if (kv.Value == resource) + { + resource.Instance.Attributes.Add("objectId", kv.Key); + return true; + } + + var parents = new BsonArray(); + var children = new BsonArray(); + var template = resource.Instance.Template; + + foreach (IResource c in resource.Instance.Children) + children.Add(c.Instance.Link); + + foreach (IResource p in resource.Instance.Parents) + parents.Add(p.Instance.Link); + + var document = new BsonDocument + { + { "parents", parents }, + { "children", children }, + { "classname", resource.GetType().AssemblyQualifiedName }, + { "name", resource.Instance.Name } + }; + + + var col = this.database.GetCollection("resources"); + + col.InsertOne(document); + + resource.Instance.Attributes["objectId"] = document["_id"].ToString(); + + var values = new BsonDocument(); + + foreach (var pt in template.Properties) + { +#if NETSTANDARD1_5 + var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name); +#else + var pi = resource.GetType().GetProperty(pt.Name); +#endif + var rt = pi.GetValue(resource, null); + + values.Add(pt.Name, Compose(rt)); + } + + var filter = Builders.Filter.Eq("_id", document["_id"]); + var update = Builders.Update + .Set("values", values); + + col.UpdateOne(filter, update); + + //document.Add("values", values); + + //col.ReplaceOne(document, document); + return true; + } + + + public BsonDocument ComposeStructure(Structure value) + { + var rt = new BsonDocument { { "type", 1 } }; + + var values = new BsonDocument(); + foreach (var i in value) + values.Add(i.Key, Compose(i.Value)); + + rt.Add("values", values); + return rt; + } + + public BsonArray ComposeVarArray(object[] array) + { + var rt = new BsonArray(); + + for (var i = 0; i < array.Length; i++) + rt.Add(Compose(array[i])); + + return rt; + } + + BsonArray ComposeStructureArray(Structure[] structures) + { + var rt = new BsonArray(); + + if (structures == null || structures?.Length == 0) + return rt; + + foreach (var s in structures) + rt.Add(ComposeStructure(s)); + + return rt; + } + + BsonArray ComposeResourceArray(IResource[] array) + { + var rt = new BsonArray(); + foreach (var r in array) + { + rt.Add(new BsonDocument { { "type", 0 }, { "link", r.Instance.Link } }); + + //if (r.Instance.Attributes.ContainsKey("objectId")) + + //rt.Add(new BsonObjectId(new ObjectId((string)r.Instance.Attributes["objectId"]))); + } + + return rt; + } + + private BsonValue Compose(object value) + { + var type = Codec.GetDataType(value, null); + + switch (type) + { + case DataType.Void: + // nothing to do; + return BsonNull.Value; + + case DataType.String: + return new BsonString((string)value); + + case DataType.Resource: + case DataType.DistributedResource: + return new BsonDocument { { "type", 0 }, { "link", (value as IResource).Instance.Link } }; + + //return new BsonObjectId(new ObjectId((string)(value as IResource).Instance.Attributes["objectId"])); + + case DataType.Structure: + return ComposeStructure((Structure)value); + + case DataType.VarArray: + return ComposeVarArray((object[])value); + + case DataType.ResourceArray: + if (value is IResource[]) + return ComposeResourceArray((IResource[])value); + else + return ComposeResourceArray((IResource[])DC.CastConvert(value, typeof(IResource[]))); + + + case DataType.StructureArray: + return ComposeStructureArray((Structure[])value); + + + default: + return BsonValue.Create(value); + } + } + + public AsyncReply Retrieve(uint iid) + { + throw new NotImplementedException(); + } + + public AsyncReply Trigger(ResourceTrigger trigger) + { + + if (trigger == ResourceTrigger.Initialize) + { + var filter = new BsonDocument(); + + var list = this.database.GetCollection("resources").Find(filter).ToList(); + + + // if (list.Count == 0) + // return new AsyncBag(new IResource[0]); + + var bag = new AsyncBag(); + + foreach (var r in list) + { + bag.Add(Get("id/" + r["_id"].AsObjectId.ToString())); + } + + bag.Seal(); + + var rt = new AsyncReply(); + + bag.Then((x) => { rt.Trigger(true); }); + + return rt; + } + else if (trigger == ResourceTrigger.Terminate) + { + // save all resources + foreach (var resource in resources.Values) + SaveResource(resource); + + return new AsyncReply(true); + } + else + return new AsyncReply(true); + } + + + public void SaveResource(IResource resource) + { + var parents = new BsonArray(); + var children = new BsonArray(); + var template = resource.Instance.Template; + + foreach (IResource c in resource.Instance.Children) + children.Add(c.Instance.Link); + + foreach (IResource p in resource.Instance.Parents) + parents.Add(p.Instance.Link); + + var values = new BsonDocument(); + + foreach (var pt in template.Properties) + { +#if NETSTANDARD1_5 + var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name); +#else + var pi = resource.GetType().GetProperty(pt.Name); +#endif + var rt = pi.GetValue(resource, null); + + values.Add(pt.Name, Compose(rt)); + } + + var document = new BsonDocument + { + { "parents", parents }, + { "children", children }, + { "classname", resource.GetType().AssemblyQualifiedName }, + { "name", resource.Instance.Name }, + { "_id", new BsonObjectId(new ObjectId(resource.Instance.Attributes["objectId"].ToString())) }, + {"values", values } + }; + + + var col = this.database.GetCollection("resources"); + + var filter = Builders.Filter.Eq("_id", document["_id"]); + var update = Builders.Update + .Set("values", values); + + col.UpdateOne(filter, update); + + } + } +} diff --git a/Esiur.sln b/Esiur.sln new file mode 100644 index 0000000..c5967c0 --- /dev/null +++ b/Esiur.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.9 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur", "Esiur\Esiur.csproj", "{DEBF78DB-E3B3-47F4-994C-5C970E98C686}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{08AC2E1C-24F0-4309-B35E-73D36A427D2D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Stores.MongoDB", "Esiur.Stores.MongoDB\Esiur.Stores.MongoDB.csproj", "{F7FB2243-12AB-46A6-9DA4-FF8DABC77D8F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DEBF78DB-E3B3-47F4-994C-5C970E98C686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DEBF78DB-E3B3-47F4-994C-5C970E98C686}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DEBF78DB-E3B3-47F4-994C-5C970E98C686}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DEBF78DB-E3B3-47F4-994C-5C970E98C686}.Release|Any CPU.Build.0 = Release|Any CPU + {08AC2E1C-24F0-4309-B35E-73D36A427D2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08AC2E1C-24F0-4309-B35E-73D36A427D2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08AC2E1C-24F0-4309-B35E-73D36A427D2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08AC2E1C-24F0-4309-B35E-73D36A427D2D}.Release|Any CPU.Build.0 = Release|Any CPU + {F7FB2243-12AB-46A6-9DA4-FF8DABC77D8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7FB2243-12AB-46A6-9DA4-FF8DABC77D8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7FB2243-12AB-46A6-9DA4-FF8DABC77D8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7FB2243-12AB-46A6-9DA4-FF8DABC77D8F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Esiur/Data/AutoList.cs b/Esiur/Data/AutoList.cs new file mode 100644 index 0000000..e6426c7 --- /dev/null +++ b/Esiur/Data/AutoList.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using Esiur.Engine; +using System.Reflection; + +namespace Esiur.Data +{ + public class AutoList : IEnumerable + { + + private readonly object syncRoot = new object(); + private List list = new List(); + + 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; + + ST state; + bool removableList; + + /* + IOrderedEnumerable OrderBy(Func keySelector) + { + return list.OrderBy(keySelector); + } + */ + + public void Sort() + { + list.Sort(); + } + + public void Sort(IComparer comparer) + { + list.Sort(comparer); + } + + + public IEnumerable Where(Func predicate) + { + return list.Where(predicate); + } + + /// + /// Convert AutoList to array + /// + /// Array + public T[] ToArray() + { + // list.OrderBy() + return list.ToArray(); + } + + /// + /// Create a new instance of AutoList + /// + /// State object to be included when an event is raised. + public AutoList(ST state) + { + this.state = state; +#if NETSTANDARD1_5 + removableList = (typeof(IDestructible).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo())); +#else + removableList = (typeof(IDestructible).IsAssignableFrom(typeof(T))); +#endif + } + + /// + /// Create a new instance of AutoList + /// + /// Populate the list with items + /// + public AutoList(T[] values) + { + + #if NETSTANDARD1_5 + removableList = (typeof(IDestructible).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo())); + #else + removableList = (typeof(IDestructible).IsAssignableFrom(typeof(T))); + #endif + + AddRange(values); + } + + /// + /// Synchronization lock of the list + /// + public object SyncRoot + { + get + { + return syncRoot; + } + } + + /// + /// First item in the list + /// + public T First() + { + return list.First(); + } + + /// + /// Get an item at a specified index + /// + 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); + } + } + + /// + /// Add item to the list + /// + public void Add(T value) + { + if (removableList) + if (value != null) + ((IDestructible)value).OnDestroy += ItemDestroyed; + + lock (syncRoot) + list.Add(value); + + OnAdd?.Invoke(state, value); + } + + /// + /// Add an array of items to the list + /// + public void AddRange(T[] values) + { + foreach (var v in values) + Add(v); + } + + private void ItemDestroyed(object sender) + { + Remove((T)sender); + } + + public IEnumerator GetEnumerator() + { + return list.GetEnumerator(); + } + + /// + /// Clear the list + /// + public void Clear() + { + if (removableList) + foreach(IDestructible v in list) + if (v!=null) + v.OnDestroy -= ItemDestroyed; + + lock (syncRoot) + list.Clear(); + + OnCleared?.Invoke(state); + } + + /// + /// Remove an item from the list + /// Item to remove + /// + public void Remove(T value) + { + if (!list.Contains(value)) + return; + + if (removableList) + if (value != null) + ((IDestructible)value).OnDestroy -= ItemDestroyed; + + lock (syncRoot) + list.Remove(value); + + OnRemoved?.Invoke(state, value); + } + + /// + /// Number of items in the list + /// + public int Count + { + get { return list.Count; } + } + + + /// + /// Check if an item exists in the list + /// + /// Item to check if exists + public bool Contains(T value) + { + return list.Contains(value); + } + + /// + /// Check if any item of the given array is in the list + /// + /// Array of items + public bool ContainsAny(T[] values) + { + foreach (var v in values) + if (list.Contains(v)) + return true; + return false; + } + + /// + /// Check if any item of the given list is in the list + /// + /// List of items + public bool ContainsAny(AutoList values) + { + foreach (var v in values) + if (list.Contains((T)v)) + return true; + return false; + } + } +} diff --git a/Esiur/Data/BinaryList.cs b/Esiur/Data/BinaryList.cs new file mode 100644 index 0000000..8e669fa --- /dev/null +++ b/Esiur/Data/BinaryList.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Misc; +using System.Reflection; + +namespace Esiur.Data +{ + /// + /// BinaryList holds a list of items to be converted to binary for storage and transmission + /// + public class BinaryList + { + private List held = new List(); + + /// + /// Create an instance of BinaryList + /// + public BinaryList() + { + + } + + /// + /// Converts parameters to binary in same order + /// + /// Variables to convert + public static byte[] ToBytes(params object[] values) + { + var held = new List(); + + foreach (var i in values) + { + if (i is byte) + held.Add((byte)i); + else + { +#if NETSTANDARD1_5 + MethodInfo mi = typeof(DC).GetTypeInfo().GetMethod("ToBytes", new Type[] { i.GetType() }); +#else + MethodInfo mi = typeof(DC).GetMethod("ToBytes", new Type[] { i.GetType() }); +#endif + if (mi != null) + { + var b = (byte[])mi.Invoke(null, new object[] { i }); + held.AddRange(b); + } + } + } + + return held.ToArray(); + } + + /// + /// Create a new instance of BinaryList + /// + /// Populate the list items + public BinaryList(params object[] values) + { + AddRange(values); + } + + /// + /// Add an array of items at the end of the list + /// + /// Array of items + public void AddRange(object[] values) + { + foreach (var i in values) + { + if (i is byte) + held.Add((byte)i); + else + { +#if NETSTANDARD1_5 + MethodInfo mi = typeof(DC).GetTypeInfo().GetMethod("ToBytes", new Type[] { i.GetType() }); +#else + MethodInfo mi = typeof(DC).GetMethod("ToBytes", new Type[] { i.GetType() }); +#endif + if (mi != null) + { + var b = (byte[])mi.Invoke(null, new object[] {i}); + held.AddRange(b); + } + } + } + } + + /// + /// Add multiple items at the end of the list + /// + /// Parameters of items + public void Append(params object[] values) + { + AddRange(values); + } + + /// + /// Insert new items to the list at a specified index + /// + /// Position in the list + /// Items to insert + public void Insert(int offset, params object[] values) + { + foreach (var i in values) + { + if (i is byte) + { + held.Insert(offset++, (byte)i); + } + else + { +#if NETSTANDARD1_5 + MethodInfo mi = typeof(DC).GetTypeInfo().GetMethod("ToBytes", new Type[] { i.GetType() }); +#else + MethodInfo mi = typeof(DC).GetMethod("ToBytes", new Type[] { i.GetType() }); +#endif + if (mi != null) + { + var b = (byte[])mi.Invoke(null, new object[] { i }); + held.InsertRange(offset, b); + offset += b.Length; + } + } + } + } + + /// + /// Number of the items in the list + /// + public int Length + { + get + { + return held.Count; + } + } + + /* + public void Append(byte data) + { + held.Add(data); + } + + public void Append(byte[] data) + { + held.AddRange(data); + } + + public void Append(int data) + { + held.AddRange(DC.ToBytes(data)); + } + + public void Append(uint data) + { + held.AddRange(DC.ToBytes(data)); + } + + public void Append(float data) + { + held.AddRange(DC.ToBytes(data)); + } + + public void Append(short data) + { + held.AddRange(DC.ToBytes(data)); + } + + public void Append(ushort data) + { + held.AddRange(DC.ToBytes(data)); + } + + public void Append(double data) + { + held.AddRange(DC.ToBytes(data)); + } + + public void Append(sbyte data) + { + held.Add((byte)data); + } + */ + + /// + /// Convert the list to an array of bytes + /// + /// Bytes array + public byte[] ToArray() + { + return held.ToArray(); + } + } +} diff --git a/Esiur/Data/Codec.cs b/Esiur/Data/Codec.cs new file mode 100644 index 0000000..c0836ca --- /dev/null +++ b/Esiur/Data/Codec.cs @@ -0,0 +1,991 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Esiur.Misc; +using System.ComponentModel; +using Esiur.Data; +using Esiur.Engine; +using Esiur.Net.IIP; +using Esiur.Resource; +using System.Linq; +using System.Reflection; + +namespace Esiur.Data +{ + public static class Codec + { + /// + /// Check if a DataType is an array + /// + /// DataType to check + /// True if DataType is an array, otherwise false + public static bool IsArray(this DataType type) + { + return (((byte)type & 0x80) == 0x80) && (type != DataType.NotModified); + } + + /// + /// Get the element DataType + /// + /// + /// Passing UInt8Array will return UInt8 + /// + /// DataType to get its element DataType + public static DataType GetElementType(this DataType type) + { + return (DataType)((byte)type & 0x7F); + } + + /// + /// Get DataType array of a given Structure + /// + /// Structure to get its DataTypes + /// Distributed connection is required in case a type is at the other end + private static DataType[] GetStructureDateTypes(Structure structure, DistributedConnection connection) + { + var keys = structure.GetKeys(); + var types = new DataType[keys.Length]; + + for (var i = 0; i < keys.Length; i++) + types[i] = Codec.GetDataType(structure[keys[i]], connection); + return types; + } + + /// + /// Compare two structures + /// + /// Initial structure to compare with + /// Next structure to compare with the initial + /// DistributedConnection is required in case a structure holds items at the other end + public static StructureComparisonResult Compare(Structure initial, Structure next, DistributedConnection connection) + { + if (next == null) + return StructureComparisonResult.Null; + + if (initial == null) + return StructureComparisonResult.Structure; + + if (next == initial) + return StructureComparisonResult.Same; + + if (initial.Length != next.Length) + return StructureComparisonResult.Structure; + + var previousKeys = initial.GetKeys(); + var nextKeys = next.GetKeys(); + + for (var i = 0; i < previousKeys.Length; i++) + if (previousKeys[i] != nextKeys[i]) + return StructureComparisonResult.Structure; + + var previousTypes = GetStructureDateTypes(initial, connection); + var nextTypes = GetStructureDateTypes(next, connection); + + for (var i = 0; i < previousTypes.Length; i++) + if (previousTypes[i] != nextTypes[i]) + return StructureComparisonResult.StructureSameKeys; + + return StructureComparisonResult.StructureSameTypes; + } + + /// + /// Compose an array of structures into an array of bytes + /// + /// Array of Structure to compose + /// DistributedConnection is required in case a structure in the array holds items at the other end + /// If true, prepend the length as UInt32 at the beginning of the returned bytes array + /// Array of bytes in the network byte order + public static byte[] ComposeStructureArray(Structure[] structures, DistributedConnection connection, bool prependLength = false) + { + if (structures == null || structures?.Length == 0) + return new byte[0]; + + var rt = new BinaryList(); + var comparsion = StructureComparisonResult.Structure; + + rt.Append((byte)comparsion); + rt.Append(ComposeStructure(structures[0], connection)); + + for (var i = 1; i < structures.Length; i++) + { + comparsion = Compare(structures[i - 1], structures[i], connection); + rt.Append((byte)comparsion); + + if (comparsion == StructureComparisonResult.Structure) + rt.Append(ComposeStructure(structures[i], connection)); + else if (comparsion == StructureComparisonResult.StructureSameKeys) + rt.Append(ComposeStructure(structures[i], connection, false)); + else if (comparsion == StructureComparisonResult.StructureSameTypes) + rt.Append(ComposeStructure(structures[i], connection, false, false)); + } + + if (prependLength) + rt.Insert(0, rt.Length); + + return rt.ToArray(); + } + + /// + /// Parse an array of structures + /// + /// Bytes array + /// Zero-indexed offset + /// Number of bytes to parse + /// DistributedConnection is required in case a structure in the array holds items at the other end + /// Array of structures + public static AsyncBag ParseStructureArray(byte[] data, uint offset, uint length, DistributedConnection connection) + { + var reply = new AsyncBag(); + if (length == 0) + { + reply.Seal(); + return reply; + } + + var end = offset + length; + + var result = (StructureComparisonResult)data[offset++]; + + AsyncReply previous = null; + string[] previousKeys = null; + DataType[] previousTypes = null; + + + + if (result == StructureComparisonResult.Null) + previous = new AsyncReply(null); + else if (result == StructureComparisonResult.Structure) + { + uint cs = data.GetUInt32(offset); + cs += 4; + previous = ParseStructure(data, offset, cs, connection, out previousKeys, out previousTypes); + offset += cs; + } + + reply.Add(previous); + + + while (offset < end) + { + result = (StructureComparisonResult)data[offset++]; + + if (result == StructureComparisonResult.Null) + previous = new AsyncReply(null); + else if (result == StructureComparisonResult.Structure) + { + uint cs = data.GetUInt32(offset); + cs += 4; + previous = ParseStructure(data, offset, cs, connection, out previousKeys, out previousTypes); + offset += cs; + } + else if (result == StructureComparisonResult.StructureSameKeys) + { + uint cs = data.GetUInt32(offset); + cs += 4; + previous = ParseStructure(data, offset, cs, connection, out previousKeys, out previousTypes, previousKeys); + offset += cs; + } + else if (result == StructureComparisonResult.StructureSameTypes) + { + uint cs = data.GetUInt32(offset); + cs += 4; + previous = ParseStructure(data, offset, cs, connection, out previousKeys, out previousTypes, previousKeys, previousTypes); + offset += cs; + } + + reply.Add(previous); + } + + reply.Seal(); + return reply; + } + + /// + /// Compose a structure into an array of bytes + /// + /// Structure to compose + /// DistributedConnection is required in case an item in the structure is at the other end + /// Whether to include the structure keys + /// Whether to include each item DataType + /// If true, prepend the length as UInt32 at the beginning of the returned bytes array + /// Array of bytes in the network byte order + public static byte[] ComposeStructure(Structure value, DistributedConnection connection, bool includeKeys = true, bool includeTypes = true, bool prependLength = false) + { + var rt = new BinaryList(); + + if (includeKeys) + { + foreach (var i in value) + { + var key = DC.ToBytes(i.Key); + rt.Append((byte)key.Length, key, Compose(i.Value, connection)); + } + } + else + { + foreach (var i in value) + rt.Append(Compose(i.Value, connection, includeTypes)); + } + + if (prependLength) + rt.Insert(0, rt.Length); + + return rt.ToArray(); + } + + /// + /// Parse a structure + /// + /// Bytes array + /// Zero-indexed offset. + /// Number of bytes to parse. + /// DistributedConnection is required in case a structure in the array holds items at the other end. + /// Value + public static AsyncReply ParseStructure(byte[] data, uint offset, uint contentLength, DistributedConnection connection) + { + string[] pk; + DataType[] pt; + return ParseStructure(data, offset, contentLength, connection, out pk, out pt); + } + + /// + /// Parse a structure + /// + /// Bytes array + /// Zero-indexed offset. + /// Number of bytes to parse. + /// DistributedConnection is required in case a structure in the array holds items at the other end. + /// Array to store keys in. + /// Array to store DataTypes in. + /// Array of keys, in case the data doesn't include keys + /// Array of DataTypes, in case the data doesn't include DataTypes + /// Structure + public static AsyncReply ParseStructure(byte[] data, uint offset, uint length, DistributedConnection connection, out string[] parsedKeys, out DataType[] parsedTypes, string[] keys = null, DataType[] types = null) + { + var reply = new AsyncReply(); + var bag = new AsyncBag(); + var keylist = new List(); + var typelist = new List(); + + if (keys == null) + { + while (length > 0) + { + var len = data[offset++]; + keylist.Add(data.GetString(offset, len)); + offset += len; + + typelist.Add((DataType)data[offset]); + + uint rt; + bag.Add(Codec.Parse(data, offset, out rt, connection)); + length -= rt + len + 1; + offset += rt; + } + } + else if (types == null) + { + keylist.AddRange(keys); + + while (length > 0) + { + typelist.Add((DataType)data[offset]); + + uint rt; + bag.Add(Codec.Parse(data, offset, out rt, connection)); + length -= rt + 1; + offset += rt + 1; + } + } + else + { + keylist.AddRange(keys); + typelist.AddRange(types); + + var i = 0; + while (length > 0) + { + uint rt; + bag.Add(Codec.Parse(data, offset, out rt, connection, types[i])); + length -= rt; + offset += rt; + i++; + } + } + + bag.Seal(); + + bag.Then((res) => + { + // compose the list + var s = new Structure(); + for (var i = 0; i < keylist.Count; i++) + s[keylist[i]] = res[i]; + reply.Trigger(s); + }); + + parsedKeys = keylist.ToArray(); + parsedTypes = typelist.ToArray(); + return reply; + } + + /// + /// Parse a value + /// + /// Bytes array + /// Zero-indexed offset. + /// DistributedConnection is required in case a structure in the array holds items at the other end. + /// DataType, in case the data is not prepended with DataType + /// Structure + public static AsyncReply Parse(byte[] data, uint offset, DistributedConnection connection, DataType dataType = DataType.Unspecified) + { + uint size; + return Parse(data, offset, out size, connection); + } + + /// + /// Parse a value + /// + /// Bytes array + /// Zero-indexed offset. + /// Output the number of bytes parsed + /// DistributedConnection is required in case a structure in the array holds items at the other end. + /// DataType, in case the data is not prepended with DataType + /// Value + public static AsyncReply Parse(byte[] data, uint offset, out uint size, DistributedConnection connection, DataType dataType = DataType.Unspecified) + { + var reply = new AsyncReply(); + + bool isArray; + DataType t; + + if (dataType == DataType.Unspecified) + { + size = 1; + dataType = (DataType)data[offset++]; + } + else + size = 0; + + t = (DataType)((byte)dataType & 0x7F); + + isArray = ((byte)dataType & 0x80) == 0x80; + + var payloadSize = dataType.Size();// SizeOf(); + + + uint contentLength = 0; + + // check if we have the enough data + if (payloadSize == -1) + { + contentLength = data.GetUInt32(offset); + offset += 4; + size += 4 + contentLength; + } + else + size += (uint)payloadSize; + + if (isArray) + { + switch (t) + { + // VarArray ? + case DataType.Void: + return ParseVarArray(data, offset, contentLength, connection); + + case DataType.Bool: + return new AsyncReply(data.GetBooleanArray(offset, contentLength)); + + case DataType.UInt8: + return new AsyncReply(data.GetUInt8Array(offset, contentLength)); + + case DataType.Int8: + return new AsyncReply(data.GetInt8Array(offset, contentLength)); + + case DataType.Char: + return new AsyncReply(data.GetCharArray(offset, contentLength)); + + case DataType.Int16: + return new AsyncReply(data.GetInt16Array( offset, contentLength)); + + case DataType.UInt16: + return new AsyncReply(data.GetUInt16Array(offset, contentLength)); + + case DataType.Int32: + return new AsyncReply(data.GetInt32Array(offset, contentLength)); + + case DataType.UInt32: + return new AsyncReply(data.GetUInt32Array(offset, contentLength)); + + case DataType.Int64: + return new AsyncReply(data.GetInt64Array(offset, contentLength)); + + case DataType.UInt64: + return new AsyncReply(data.GetUInt64Array(offset, contentLength)); + + case DataType.Float32: + return new AsyncReply(data.GetFloat32Array(offset, contentLength)); + + case DataType.Float64: + return new AsyncReply(data.GetFloat64Array(offset, contentLength)); + + case DataType.String: + return new AsyncReply(data.GetStringArray(offset, contentLength)); + + case DataType.Resource: + case DataType.DistributedResource: + return ParseResourceArray(data, offset, contentLength, connection); + + case DataType.DateTime: + return new AsyncReply(data.GetDateTimeArray(offset, contentLength)); + + case DataType.Structure: + return ParseStructureArray(data, offset, contentLength, connection); + } + } + else + { + switch (t) + { + case DataType.NotModified: + return new AsyncReply(new NotModified()); + + case DataType.Void: + return new AsyncReply(null); + + case DataType.Bool: + return new AsyncReply(data.GetBoolean(offset)); + + case DataType.UInt8: + return new AsyncReply(data[offset]); + + case DataType.Int8: + return new AsyncReply((sbyte)data[offset]); + + case DataType.Char: + return new AsyncReply(data.GetChar(offset)); + + case DataType.Int16: + return new AsyncReply(data.GetInt16(offset)); + + case DataType.UInt16: + return new AsyncReply(data.GetUInt16(offset)); + + case DataType.Int32: + return new AsyncReply(data.GetInt32(offset)); + + case DataType.UInt32: + return new AsyncReply(data.GetUInt32(offset)); + + case DataType.Int64: + return new AsyncReply(data.GetInt64(offset)); + + case DataType.UInt64: + return new AsyncReply(data.GetUInt64(offset)); + + case DataType.Float32: + return new AsyncReply(data.GetFloat32(offset)); + + case DataType.Float64: + return new AsyncReply(data.GetFloat64(offset)); + + case DataType.String: + return new AsyncReply(data.GetString(offset, contentLength)); + + case DataType.Resource: + return ParseResource(data, offset); + + case DataType.DistributedResource: + return ParseDistributedResource(data, offset, connection); + + case DataType.DateTime: + return new AsyncReply(data.GetDateTime(offset)); + + case DataType.Structure: + return ParseStructure(data, offset, contentLength, connection); + } + } + + + return null; + } + + /// + /// Parse a resource + /// + /// Bytes array + /// Zero-indexed offset. + /// Resource + public static AsyncReply ParseResource(byte[] data, uint offset) + { + return Warehouse.Get(data.GetUInt32(offset)); + } + + /// + /// Parse a DistributedResource + /// + /// Bytes array + /// Zero-indexed offset. + /// DistributedConnection is required. + /// DistributedResource + public static AsyncReply ParseDistributedResource(byte[] data, uint offset, DistributedConnection connection) + { + //var g = data.GetGuid(offset); + //offset += 16; + + // find the object + var iid = data.GetUInt32(offset); + + return connection.Fetch(iid);// Warehouse.Get(iid); + } + + public enum ResourceComparisonResult + { + Null, + Distributed, + DistributedSameClass, + Local, + Same + } + + public enum StructureComparisonResult : byte + { + Null, + Structure, + StructureSameKeys, + StructureSameTypes, + Same + } + + /// + /// Check if a resource is local to a given connection. + /// + /// Resource to check. + /// DistributedConnection to check if the resource is local to it. + /// True, if the resource owner is the given connection, otherwise False. + static bool IsLocalResource(IResource resource, DistributedConnection connection) + { + if (resource is DistributedResource) + if ((resource as DistributedResource).Connection == connection) + return true; + + return false; + } + + /// + /// Compare two resources + /// + /// Initial resource to make comparison with. + /// Next resource to compare with the initial. + /// DistributedConnection is required to check locality. + /// Null, same, local, distributed or same class distributed. + + public static ResourceComparisonResult Compare(IResource initial, IResource next, DistributedConnection connection) + { + if (next == null) + return ResourceComparisonResult.Null; + + if (next == initial) + return ResourceComparisonResult.Same; + + if (IsLocalResource(next, connection)) + return ResourceComparisonResult.Local; + + if (initial == null) + return ResourceComparisonResult.Distributed; + + if (initial.Instance.Template.ClassId == next.Instance.Template.ClassId) + return ResourceComparisonResult.DistributedSameClass; + + return ResourceComparisonResult.Distributed; + + } + + /// + /// Compose a resource + /// + /// Resource to compose. + /// DistributedConnection is required to check locality. + /// Array of bytes in the network byte order. + public static byte[] ComposeResource(IResource resource, DistributedConnection connection) + { + if (IsLocalResource(resource, connection)) + return DC.ToBytes((resource as DistributedResource).Id); + else + { + return BinaryList.ToBytes(resource.Instance.Template.ClassId, resource.Instance.Id); + } + } + + /// + /// Compose an array of resources + /// + /// Array of resources. + /// DistributedConnection is required to check locality. + /// If True, prepend the length of the output at the beginning. + /// Array of bytes in the network byte order. + + public static byte[] ComposeResourceArray(IResource[] resources, DistributedConnection connection, bool prependLength = false) + { + if (resources == null || resources?.Length == 0) + return new byte[0]; + + var rt = new BinaryList(); + var comparsion = Compare(null, resources[0], connection); + + rt.Append((byte)comparsion); + + if (comparsion == ResourceComparisonResult.Local) + rt.Append((resources[0] as DistributedResource).Id); + else if (comparsion == ResourceComparisonResult.Distributed) + { + rt.Append(resources[0].Instance.Template.ClassId); + rt.Append(resources[0].Instance.Id); + } + + for (var i = 1; i < resources.Length; i++) + { + comparsion = Compare(resources[i - 1], resources[i], connection); + rt.Append((byte)comparsion); + if (comparsion == ResourceComparisonResult.Local) + rt.Append((resources[0] as DistributedResource).Id); + else if (comparsion == ResourceComparisonResult.Distributed) + { + rt.Append(resources[0].Instance.Template.ClassId); + rt.Append(resources[0].Instance.Id); + } + else if (comparsion == ResourceComparisonResult.DistributedSameClass) + { + rt.Append(resources[0].Instance.Id); + } + } + + if (prependLength) + rt.Insert(0, rt.Length); + + return rt.ToArray(); + } + + /// + /// Parse an array of bytes into array of resources + /// + /// Array of bytes. + /// Number of bytes to parse. + /// Zero-indexed offset. + /// DistributedConnection is required to fetch resources. + /// Array of resources. + public static AsyncBag ParseResourceArray(byte[] data, uint offset, uint length, DistributedConnection connection) + { + var reply = new AsyncBag(); + if (length == 0) + { + reply.Seal(); + return reply; + } + + var end = offset + length; + + // + var result = (ResourceComparisonResult)data[offset++]; + + AsyncReply previous = null; + Guid previousGuid = Guid.Empty; + + if (result == ResourceComparisonResult.Null) + previous = new AsyncReply(null); + else if (result == ResourceComparisonResult.Local) + { + previous = Warehouse.Get(data.GetUInt32(offset)); + offset += 4; + } + else if (result == ResourceComparisonResult.Distributed) + { + previousGuid = data.GetGuid(offset); + offset += 16; + //previous = connection.Fetch(previousGuid, data.GetUInt32(offset)); + offset += 4; + } + + reply.Add(previous); + + + while (offset < end) + { + result = (ResourceComparisonResult)data[offset++]; + + if (result == ResourceComparisonResult.Null) + previous = new AsyncReply(null); + //else if (result == ResourceComparisonResult.Same) + // reply.Add(previous); + else if (result == ResourceComparisonResult.Local) + { + // overwrite previous + previous = Warehouse.Get(data.GetUInt32(offset)); + offset += 4; + } + else if (result == ResourceComparisonResult.Distributed) + { + // overwrite previous + previousGuid = data.GetGuid(offset); + offset += 16; + //previous = connection.Fetch(previousGuid, data.GetUInt32(offset)); + offset += 4; + } + else if (result == ResourceComparisonResult.DistributedSameClass) + { + // overwrite previous + //previous = connection.Fetch(previousGuid, data.GetUInt32(offset)); + offset += 4; + } + + reply.Add(previous); + } + + reply.Seal(); + return reply; + } + + /// + /// Compose an array of variables + /// + /// Variables. + /// DistributedConnection is required to check locality. + /// If True, prepend the length as UInt32 at the beginning of the output. + /// Array of bytes in the network byte order. + public static byte[] ComposeVarArray(object[] array, DistributedConnection connection, bool prependLength = false) + { + var rt = new List(); + + for (var i = 0; i < array.Length; i++) + rt.AddRange(Compose(array[i], connection)); + if (prependLength) + rt.InsertRange(0, DC.ToBytes(rt.Count)); + return rt.ToArray(); + } + + public static AsyncBag ParseVarArray(byte[] data, DistributedConnection connection) + { + return ParseVarArray(data, 0, (uint)data.Length, connection); + } + + /// + /// Parse an array of bytes into an array of varialbes. + /// + /// Array of bytes. + /// Zero-indexed offset. + /// Number of bytes to parse. + /// DistributedConnection is required to fetch resources. + /// Array of variables. + public static AsyncBag ParseVarArray(byte[] data, uint offset, uint length, DistributedConnection connection) + { + var rt = new AsyncBag(); + + while (length > 0) + { + uint cs; + + rt.Add(Parse(data, offset, out cs, connection)); + + if (cs > 0) + { + offset += (uint)cs; + length -= (uint)cs; + } + else + throw new Exception("Error while parsing structured data"); + + } + + rt.Seal(); + return rt; + } + + /// + /// Compose a variable + /// + /// Value to compose. + /// DistributedConnection is required to check locality. + /// If True, prepend the DataType at the beginning of the output. + /// Array of bytes in the network byte order. + public static byte[] Compose(object value, DistributedConnection connection, bool prependType = true) + { + + var type = GetDataType(value, connection); + var rt = new BinaryList(); + + switch (type) + { + case DataType.Void: + // nothing to do; + break; + + case DataType.String: + var st = DC.ToBytes((string)value); + rt.Append(st.Length, st); + break; + + case DataType.Resource: + rt.Append((value as DistributedResource).Id); + break; + + case DataType.DistributedResource: + //rt.Append((value as IResource).Instance.Template.ClassId, (value as IResource).Instance.Id); + rt.Append((value as IResource).Instance.Id); + + break; + + case DataType.Structure: + rt.Append(ComposeStructure((Structure)value, connection, true, true, true)); + break; + + case DataType.VarArray: + rt.Append(ComposeVarArray((object[])value, connection, true)); + break; + + case DataType.ResourceArray: + if (value is IResource[]) + rt.Append(ComposeResourceArray((IResource[])value, connection, true)); + else + rt.Append(ComposeResourceArray((IResource[])DC.CastConvert(value, typeof(IResource[])), connection, true)); + break; + + case DataType.StructureArray: + rt.Append(ComposeStructureArray((Structure[])value, connection, true)); + break; + + default: + rt.Append(value); + if (type.IsArray()) + rt.Insert(0, rt.Length); + break; + } + + if (prependType) + rt.Insert(0, (byte)type); + + return rt.ToArray(); + } + + /// + /// Check if a type implements an interface + /// + /// Sub-class type. + /// Super-interface type. + /// True, if implements . + private static bool ImplementsInterface(Type type, Type iface) + { + + while (type != null) + { +#if NETSTANDARD1_5 + 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; + } + + /// + /// Check if a type inherits another type. + /// + /// Child type. + /// Parent type. + /// True, if inherits . + private static bool HasParentType(Type childType, Type parentType) + { + while (childType != null) + { + if (childType == parentType) + return true; +#if NETSTANDARD1_5 + childType = childType.GetTypeInfo().BaseType; +#else + childType = childType.BaseType; +#endif + } + + return false; + } + + /// + /// Get the DataType of a given value. + /// This function is needed to compose a value. + /// + /// Value to find its DataType. + /// DistributedConnection is required to check locality of resources. + /// DataType. + public static DataType GetDataType(object value, DistributedConnection connection) + { + if (value == null) + return DataType.Void; + + var t = value.GetType(); + + var isArray = t.IsArray; + if (isArray) + t = t.GetElementType(); + + DataType type; + + if (t == typeof(bool)) + type = DataType.Bool; + else if (t == typeof(char)) + type = DataType.Char; + else if (t == typeof(byte)) + type = DataType.UInt8; + else if (t == typeof(sbyte)) + type = DataType.Int8; + else if (t == typeof(short)) + type = DataType.Int16; + else if (t == typeof(ushort)) + type = DataType.UInt16; + else if (t == typeof(int)) + type = DataType.Int32; + else if (t == typeof(uint)) + type = DataType.UInt32; + else if (t == typeof(long)) + type = DataType.Int64; + else if (t == typeof(ulong)) + type = DataType.UInt64; + else if (t == typeof(float)) + type = DataType.Float32; + else if (t == typeof(double)) + type = DataType.Float64; + else if (t == typeof(decimal)) + type = DataType.Decimal; + else if (t == typeof(string)) + type = DataType.String; + else if (t == typeof(DateTime)) + type = DataType.DateTime; + else if (t == typeof(Structure)) + type = DataType.Structure; + //else if (t == typeof(DistributedResource)) + // type = DataType.DistributedResource; + else if (ImplementsInterface(t, typeof(IResource))) + { + if (isArray) + return DataType.ResourceArray; + else + { + return IsLocalResource((IResource)value, connection) ? DataType.Resource : DataType.DistributedResource; + } + } + else + return DataType.Void; + + + if (isArray) + return (DataType)((byte)type | 0x80); + else + return type; + + } + + } +} diff --git a/Esiur/Data/DataConverter.cs b/Esiur/Data/DataConverter.cs new file mode 100644 index 0000000..714f742 --- /dev/null +++ b/Esiur/Data/DataConverter.cs @@ -0,0 +1,1069 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Net.IIP; + +using System.Runtime.InteropServices; +using System.Diagnostics; +using System.Net; +using System.Net.NetworkInformation; +using System.Reflection; +using Esiur.Data; +using Esiur.Engine; +using Esiur.Resource; + +namespace Esiur.Data +{ + public static class DC // Data Converter + { + public static object CastConvert(object value, Type destinationType) + { + if (value == null) + return null; + + var sourceType = value.GetType(); + + if (destinationType == sourceType) + { + return value; + } + else + { + if (sourceType.IsArray) + { + if (destinationType.IsArray) + destinationType = destinationType.GetElementType(); + + var v = value as Array; + + var rt = Array.CreateInstance(destinationType, v.Length); + + for (var i = 0; i < rt.Length; i++) + { + try + { +#if NETSTANDARD1_5 + if (destinationType.GetTypeInfo().IsInstanceOfType(v.GetValue(i))) +#else + if (destinationType.IsInstanceOfType(v.GetValue(i))) +#endif + rt.SetValue(v.GetValue(i), i); + else + rt.SetValue(Convert.ChangeType(v.GetValue(i), destinationType), i); + } + catch + { + rt.SetValue(null, i); + } + } + + return rt; + } + else + { + try + { +#if NETSTANDARD1_5 + if (destinationType.GetTypeInfo().IsInstanceOfType(value)) +#else + if (destinationType.IsInstanceOfType(value)) +#endif + return value; + else + return Convert.ChangeType(value, destinationType); + } + catch + { + return null; + } + } + } + } + + /* + private static byte[] ReverseArray(byte[] data, uint offset, uint count) + { + if (offset + count > data.Length) + { + Console.WriteLine("ReverseArray: Bad offset " + data.Length + " " + offset + " " + count); + + StackTrace st = new StackTrace(); + Console.WriteLine(st.ToString()); + + return null; + } + + byte[] rt = new byte[count]; + + uint b = count; + + for (var i = offset; i < (offset + count); i++) + rt[--b] = data[i]; + + return rt; + } + */ + + /* + public static T[] ArrayFromBytes(byte[] data, uint offset, uint length) + { + + if (typeof(T) == typeof(string)) + { + List ar = new List(); + + uint i = 0; + + while (i < length) + { + var cl = GetUInt32(data, 0); + i += 4; + ar.Add(Encoding.UTF8.GetString(data, (int)(offset + i), (int)cl)); + i += cl; + } + + return (T[])(object)ar.ToArray(); + + } + else + { + uint blockSize = (uint)Marshal.SizeOf(typeof(T)); + + T[] ar = new T[length / blockSize]; + + for (uint i = 0; i < ar.Length; i += blockSize) + ar[i] = FromBytes(data, offset + i); + + return ar; + } + } + */ + + public static sbyte GetInt8(this byte[] data, uint offset) + { + return (sbyte)data[offset]; + } + + public static sbyte[] GetInt8Array(this byte[] data, uint offset, uint length) + { + var rt = new sbyte[length]; + Buffer.BlockCopy(rt, (int)offset, rt, 0, (int)length); + return rt; + } + + public static byte GetUInt8(this byte[] data, uint offset) + { + return data[offset]; + } + + public static byte[] GetUInt8Array(this byte[] data, uint offset, uint length) + { + var rt = new byte[length]; + Buffer.BlockCopy(rt, (int)offset, rt, 0, (int)length); + return rt; + } + + public static Int16 GetInt16(this byte[] data, uint offset) + { + return (Int16)((data[offset] << 8) | data[offset + 1]); + } + + public static Int16[] GetInt16Array(this byte[] data, uint offset, uint length) + { + var rt = new Int16[length/2]; + for (var i = 0; i < length; i += 2) + rt[i] = GetInt16(data, (uint)(offset + i)); + + return rt; + } + + public static UInt16 GetUInt16(this byte[] data, uint offset) + { + return (UInt16)((data[offset] << 8) | data[offset + 1]); + } + + public static UInt16[] GetUInt16Array(this byte[] data, uint offset, uint length) + { + var rt = new UInt16[length / 2]; + for (var i = 0; i < length; i += 2) + rt[i] = GetUInt16(data, (uint)(offset + i)); + return rt; + } + + public static Int32 GetInt32(this byte[] data, uint offset) + { + return (Int32)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); + } + + public static Int32[] GetInt32Array(this byte[] data, uint offset, uint length) + { + var rt = new Int32[length / 4]; + for (var i = 0; i < length; i += 4) + rt[i] = GetInt32(data, (uint)(offset + i)); + + return rt; + } + + public static UInt32 GetUInt32(this byte[] data, uint offset) + { + return (UInt32)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); + } + + public static UInt32[] GetUInt32Array(this byte[] data, uint offset, uint length) + { + var rt = new UInt32[length / 4]; + for (var i = 0; i < length; i += 4) + rt[i] = GetUInt32(data, (uint)(offset + i)); + + return rt; + } + + + public static unsafe UInt64 GetUInt64(this byte[] data, uint offset) + { + UInt64 rt = 0; + byte* p = (byte*)&rt; + + *(p + 7) = data[0]; + *(p + 6) = data[1]; + *(p + 5) = data[2]; + *(p + 4) = data[3]; + *(p + 3) = data[4]; + *(p + 2) = data[5]; + *(p + 1) = data[6]; + *(p) = data[7]; + + return rt; + + } + + public static Int64[] GetInt64Array(this byte[] data, uint offset, uint length) + { + var rt = new Int64[length / 8]; + for (var i = 0; i < length; i += 8) + rt[i] = GetInt64(data, (uint)(offset + i)); + + return rt; + } + + public static unsafe Int64 GetInt64(this byte[] data, uint offset) + { + Int64 rt = 0; + byte* p = (byte*)&rt; + + *(p + 7) = data[0]; + *(p + 6) = data[1]; + *(p + 5) = data[2]; + *(p + 4) = data[3]; + *(p + 3) = data[4]; + *(p + 2) = data[5]; + *(p + 1) = data[6]; + *(p) = data[7]; + + return rt; + + /* Or + return (Int64)( + (data[offset] << 56) + | (data[offset + 1] << 48) + | (data[offset + 2] << 40) + | (data[offset + 3] << 32) + | (data[offset + 4] << 24) + | (data[offset + 5] << 16) + | (data[offset + 6] << 8) + | (data[offset + 7]) + ); + */ + } + + public static UInt64[] GetUInt64Array(this byte[] data, uint offset, uint length) + { + var rt = new UInt64[length / 8]; + for (var i = 0; i < length; i += 8) + rt[i] = GetUInt64(data, (uint)(offset + i)); + + return rt; + } + + public static unsafe float GetFloat32(this byte[] data, uint offset) + { + 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 float[] GetFloat32Array(this byte[] data, uint offset, uint length) + { + var rt = new float[length / 4]; + for (var i = 0; i < length; i += 4) + rt[i] = GetFloat32(data, (uint)(offset + i)); + + return rt; + } + + public static unsafe double GetFloat64(this byte[] data, uint offset) + { + double rt = 0; + byte* p = (byte*)&rt; + + *(p + 7) = data[0]; + *(p + 6) = data[1]; + *(p + 5) = data[2]; + *(p + 4) = data[3]; + *(p + 3) = data[4]; + *(p + 2) = data[5]; + *(p + 1) = data[6]; + *(p) = data[7]; + + return rt; + } + + public static double[] GetFloat64Array(this byte[] data, uint offset, uint length) + { + var rt = new double[length / 8]; + for (var i = 0; i < length; i += 8) + rt[i] = GetFloat64(data, (uint)(offset + i)); + + return rt; + } + + public static bool GetBoolean(this byte[] data, uint offset) + { + return data[offset] > 0; + } + + public static bool[] GetBooleanArray(this byte[] data, uint offset, uint length) + { + var rt = new bool[length]; + for (var i = 0; i < length; i++) + rt[i] = data[offset + i] > 0; + return rt; + } + + public static char GetChar(this byte[] data, uint offset) + { + return Convert.ToChar(((data[offset] << 8) | data[offset + 1])); + } + + public static char[] GetCharArray(this byte[] data, uint offset, uint length) + { + var rt = new char[length/2]; + for (var i = 0; i < length; i+=2) + rt[i] = GetChar(data, (uint)(offset + i)); + return rt; + } + + 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 ar = new List(); + + uint i = 0; + + while (i < length) + { + var cl = GetUInt32(data, offset + i); + i += 4; + ar.Add(Encoding.UTF8.GetString(data, (int)(offset + i), (int)cl)); + i += cl; + } + + return ar.ToArray(); + } + + public static Guid GetGuid(this byte[] data, uint offset) + { + return new Guid(DC.Clip(data, offset, 16)); + } + + public static Guid[] GetGuidArray(this byte[] data, uint offset, uint length) + { + var rt = new Guid[length / 16]; + for (var i = 0; i < length; i += 16) + rt[i] = GetGuid(data, (uint)(offset + i)); + return rt; + } + + public static DateTime GetDateTime(this byte[] data, uint offset) + { + var ticks = GetInt64(data, offset); + return new DateTime(ticks, DateTimeKind.Utc); + } + + public static DateTime[] GetDateTimeArray(this byte[] data, uint offset, uint length) + { + var rt = new DateTime[length / 8]; + for (var i = 0; i < length; i += 8) + rt[i] = GetDateTime(data, (uint)(offset + i)); + return rt; + } + + /* + public static PhysicalAddress GetPhysicalAddress(this byte[] data, uint offset) + { + return new PhysicalAddress(Clip(data, offset, 6)); + } + + public static PhysicalAddress[] GetPhysicalAddressArray(this byte[] data, uint offset, uint length) + { + var rt = new PhysicalAddress[length / 6]; + for (var i = 0; i < length; i += 6) + rt[i] = GetPhysicalAddress(data, (uint)(offset + i)); + return rt; + } + */ + public static IPAddress GetIPv4Address(this byte[] data, uint offset) + { + return new IPAddress((long)GetUInt32(data, offset)); + } + + public static IPAddress[] GetIPv4AddressArray(this byte[] data, uint offset, uint length) + { + var rt = new IPAddress[length / 4]; + for (var i = 0; i < length; i += 4) + rt[i] = GetIPv4Address(data, (uint)(offset + i)); + return rt; + } + + public static IPAddress GetIPv6Address(this byte[] data, uint offset) + { + return new IPAddress(Clip(data, offset, 16)); + } + + public static IPAddress[] GetIPv6AddressArray(this byte[] data, uint offset, uint length) + { + var rt = new IPAddress[length / 16]; + for (var i = 0; i < length; i += 16) + rt[i] = GetIPv6Address(data, (uint)(offset + i)); + return rt; + } + + /* + public static T FromBytes(byte[] data, uint offset, uint length = 0) + { + if (typeof(T) == typeof(bool)) + { + return (T)(object)(data[offset] == 1); + } + else if (typeof(T) == typeof(byte)) + { + return (T)(object)data[offset]; + } + else if (typeof(T) == typeof(char)) + { + return (T)(object)BitConverter.ToChar(ReverseArray(data, offset, 2), 0); + } + else if (typeof(T) == typeof(short)) + { + return (T)(object)BitConverter.ToInt16(ReverseArray(data, offset, 2), 0); + } + else if (typeof(T) == typeof(ushort)) + { + return (T)(object)BitConverter.ToUInt16(ReverseArray(data, offset, 2), 0); + } + else if (typeof(T) == typeof(int)) + { + return (T)(object)BitConverter.ToInt32(ReverseArray(data, offset, 4), 0); + } + else if (typeof(T) == typeof(uint)) + { + return (T)(object)BitConverter.ToUInt32(ReverseArray(data, offset, 4), 0); + } + else if (typeof(T) == typeof(long)) + { + return (T)(object)BitConverter.ToInt64(ReverseArray(data, offset, 8), 0); + } + else if (typeof(T) == typeof(ulong)) + { + return (T)(object)BitConverter.ToUInt64(ReverseArray(data, offset, 8), 0); + } + else if (typeof(T) == typeof(float)) + { + return (T)(object)BitConverter.ToSingle(ReverseArray(data, offset, 4), 0); + } + else if (typeof(T) == typeof(double)) + { + return (T)(object)BitConverter.ToDouble(ReverseArray(data, offset, 8), 0); + } + else if (typeof(T) == typeof(string)) + { + return (T)(object)Encoding.UTF8.GetString(data, (int)offset, (int)length); + } + else if (typeof(T) == typeof(Guid)) + { + return (T)(object)new Guid(DC.Clip(data, offset, 16)); + } + else if (typeof(T) == typeof(IPAddress)) + { + if (length == 0) + return (T)(object)(new IPAddress((long)GetUInt32(data, offset))); + else + return (T)(object)(new IPAddress(Clip(data, offset, length))); + } + else if (typeof(T) == typeof(PhysicalAddress)) + { + return (T)(object)new PhysicalAddress(Clip(data, offset, 6)); + } + else if (typeof(T) == typeof(DateTime)) + { + long ticks = BitConverter.ToInt64(ReverseArray(data, offset, 8), 0); + return (T)(object)new DateTime(ticks, DateTimeKind.Utc); + } + else + { + return default(T); + } + } + */ + + 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[] ToBytes(byte[] value) + { + return value; + } + + //public static byte[] ToBytes(Codec value) + //{ + // return value.ToStructuredValue(); + //} + + 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(IResource value) + { + + if (value is DistributedResource) + return DC.ToBytes((value as DistributedResource).Id); + else + { + List rt = new List(); + // Add GUID + rt.AddRange(value.Instance.Template.ClassId.ToByteArray()); + // Add Instance ID + rt.AddRange(DC.ToBytes(value.Instance.Id)); + + return rt.ToArray(); + } + } + */ + + /* + public static byte[] ToBytes(Structure value) + { + return value.Compose(); + }*/ + + 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 byte[] ToBytes(char[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + public static byte[] ToBytes(short[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + + + public static byte[] ToBytes(ushort[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + 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(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[] Clip(this byte[] data, uint offset, uint length) + { + if (data.Length < offset + length) + return null; + + if (length == data.Length && offset == 0) + return data; + + var b = new byte[length]; + Buffer.BlockCopy(data, (int)offset, b, 0, (int)length); + return b; + } + + public static byte[] ToBytes(int[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + public static byte[] ToBytes(uint[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + public static byte[] ToBytes(long[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + public static byte[] ToBytes(ulong[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + public static byte[] ToBytes(float[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + public static byte[] ToBytes(double[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + + public static byte[] ToBytes(decimal[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + + public static byte[] ToBytes(DateTime[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + rt.AddRange(ToBytes(value[i])); + + return rt.ToArray(); + } + + + public static byte[] ToBytes(string[] value) + { + List rt = new List(); + + for (int i = 0; i < value.Length; i++) + { + byte[] ba = ToBytes(value[i]); + // add string length + rt.AddRange(ToBytes(ba.Length)); + // add encoded string + rt.AddRange(ba); + } + + return rt.ToArray(); + } + + public static byte[] ToBytes(int value) + { + byte[] ret = BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + public static byte[] ToBytes(short value) + { + byte[] ret = BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + public static byte[] ToBytes(float value) + { + byte[] ret = BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + /* + public static byte[] ToBytes(Structure[] values) + { + if (values == null || values.Length == 0) + return new byte[0]; + + var rt = new BinaryList(); + var previous = values[0]; + + // include first one + if (previous == null) + rt.Append((byte)Structure.ComparisonResult.Null); + else + rt.Append((byte)Structure.ComparisonResult.DifferentStructure, previous.Compose(true,true,true)); + + for (var i = 1; i < values.Length; i++) + { + var current = values[i]; + var results = Structure.Compare(previous, current); + + rt.Append((byte)results); + + if (results == Structure.ComparisonResult.DifferentStructure) + rt.Append(current.Compose(true, true, true)); + else if (results == Structure.ComparisonResult.SameStructureDifferentValueTypes) + rt.Append(current.Compose(false)); + else if (results == Structure.ComparisonResult.SameStructureDifferentValues) + rt.Append(current.Compose(false, false)); + } + + return rt.ToArray(); + } + + + public static byte[] ToBytes(IIPObject[] values) + { + if (values == null || values.Length == 0) + return new byte[0]; + + var rt = new BinaryList(); + var previous = values[0]; + + // include first one + if (previous == null) + rt.Append((byte)IIPObject.ComparisonResult.Null); + else + rt.Append((byte)IIPObject.ComparisonResult.DifferentObject, previous.GUID.ToByteArray(), previous.InstanceID); + + for (var i = 1; i < values.Length; i++) + { + var current = values[i]; + var results = IIPObject.Compare(previous, current); + + rt.Append((byte)results); + + if (results == IIPObject.ComparisonResult.DifferentObject) + rt.Append(current.GUID.ToByteArray(), current.InstanceID); + else if (results == IIPObject.ComparisonResult.SameTypeDifferentInstance) + rt.Append(current.InstanceID); + } + + return rt.ToArray(); + } + */ + public static byte[] ToBytes(string value) + { + return Encoding.UTF8.GetBytes(value); + } + + public static byte[] ToBytes(double value) + { + byte[] ret = BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + public static byte[] ToBytes(long value) + { + byte[] ret = BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + public static byte[] ToBytes(DateTime value) + { + byte[] ret = BitConverter.GetBytes(value.ToUniversalTime().Ticks); + Array.Reverse(ret); + return ret; + } + + + public static byte[] ToBytes(ulong value) + { + byte[] ret = BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + public static byte[] ToBytes(uint value) + { + + byte[] ret = BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + public static byte[] ToBytes(ushort value) + { + byte[] ret = BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + public static byte[] ToBytes(decimal value) + { + byte[] ret = new byte[0];// BitConverter.GetBytes(value); + + Array.Reverse(ret); + + return ret; + } + + public static string ToHex(byte[] ba) + { + if (ba == null) + return "NULL"; + return ToHex(ba, 0, (uint)ba.Length); + } + + public static string ToHex(byte[] ba, uint offset, uint length, string separator = " ") + { + StringBuilder hex = new StringBuilder((int)length * 2); + + for (var i = offset; i < offset + length; i++) + { + hex.AppendFormat("{0:x2}", ba[i]); + if (separator != null) + hex.Append(separator); + } + + return hex.ToString(); + } + + public static byte[] FromHex(string hexString, string separator = " ") + { + var rt = new List(); + + 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(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 string ToISODateTime(this DateTime date) + { + return date.ToString("yyyy-MM-dd HH:mm:ss"); + } + + + public static bool TryParse(object Input, out T Results) + { + try + { +#if NETSTANDARD1_5 + 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 uint ToUnixTime(this DateTime date) + { + return (uint)(date - new DateTime(1970, 1, 1)).TotalSeconds; + } + + 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); + } + } + + +} diff --git a/Esiur/Data/DataType.cs b/Esiur/Data/DataType.cs new file mode 100644 index 0000000..f61cb33 --- /dev/null +++ b/Esiur/Data/DataType.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Data +{ + public enum DataType : byte + { + Void = 0x0, + //Variant, + Bool, + Int8, + UInt8, + Char, + Int16, + UInt16, + Int32, + UInt32, + Int64, + UInt64, + Float32, + Float64, + Decimal, + DateTime, + Resource, + DistributedResource, + ResourceLink, + String, + Structure, + //Stream, + //Array = 0x80, + VarArray = 0x80, + BoolArray, + UInt8Array, + Int8Array, + CharArray, + Int16Array, + UInt16Array, + Int32Array, + UInt32Array, + Int64Array, + UInt64Array, + Float32Array, + Float64Array, + DecimalArray, + DateTimeArray, + ResourceArray, + DistributedResourceArray, + ResourceLinkArray, + StringArray, + StructureArray, + NotModified = 0x7f, + Unspecified = 0xff, + } + + public static class DataTypeExpansions + { + public static int Size(this DataType t) + { + switch (t) + { + case DataType.Void: + case DataType.NotModified: + return 0; + case DataType.Bool: + case DataType.UInt8: + case DataType.Int8: + return 1; + case DataType.Char: + case DataType.UInt16: + case DataType.Int16: + return 2; + case DataType.Int32: + case DataType.UInt32: + case DataType.Float32: + case DataType.Resource: + return 4; + case DataType.Int64: + case DataType.UInt64: + case DataType.Float64: + case DataType.DateTime: + return 8; + case DataType.DistributedResource: + return 4; + + default: + return -1; + } + } + + + } +} diff --git a/Esiur/Data/KeyList.cs b/Esiur/Data/KeyList.cs new file mode 100644 index 0000000..5e2af0a --- /dev/null +++ b/Esiur/Data/KeyList.cs @@ -0,0 +1,204 @@ +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.Engine; + +namespace Esiur.Data +{ + + public class KeyList : IEnumerable + { + private readonly object syncRoot = new object(); + private Dictionary dic; + + public delegate void Modified(KT key, T oldValue, T newValue); + public delegate void Added(T value); + public delegate void Removed(KT key, T value); + public delegate void Cleared(); + + 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, 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); + } + else + { + dic.Add(key, value); + + if (OnAdd != null) + OnAdd(value); + } + } + } + + private void ItemDestroyed(object sender) + { + RemoveValue((T)sender); + } + + public void RemoveValue(T value) + { + var toRemove = new List(); + 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 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(); + } + + public Dictionary.KeyCollection Keys + { + get { return dic.Keys; } + } + + public Dictionary.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); + } + + 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() + { + #if NETSTANDARD1_5 + removableList = (typeof(IDestructible).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo())); + #else + removableList = (typeof(IDestructible).IsAssignableFrom(typeof(T))); + #endif + + if (typeof(KT) == typeof(string)) + dic = (Dictionary)(object)new Dictionary(StringComparer.OrdinalIgnoreCase); + else + dic = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/Esiur/Data/NotModified.cs b/Esiur/Data/NotModified.cs new file mode 100644 index 0000000..17710b2 --- /dev/null +++ b/Esiur/Data/NotModified.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Data +{ + public class NotModified + { + + } +} diff --git a/Esiur/Data/StringKeyList.cs b/Esiur/Data/StringKeyList.cs new file mode 100644 index 0000000..7104239 --- /dev/null +++ b/Esiur/Data/StringKeyList.cs @@ -0,0 +1,194 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using System.Reflection; + +namespace Esiur.Data +{ + + public class StringKeyList : IEnumerable> + { + + //private List m_keys = new List(); + //private List m_values = new List(); + + private List> m_Variables = new List>(); + + 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); + + var 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(Key, Value)); + } + + public string this[string Key] + { + get + { + var key = Key.ToLower(); + foreach (var kv in m_Variables) + if (kv.Key.ToLower() == key) + return kv.Value; + + return null; + } + set + { + var key = Key.ToLower(); + + if (OnModified != null) + OnModified(Key, value); + + + foreach (var kv in m_Variables) + if (kv.Key.ToLower() == key) + m_Variables.Remove(kv); + + m_Variables.Add(new KeyValuePair(Key, value)); + } + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + //return m_keys.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 string[] Keys + { + get + { + return m_keys.ToArray(); + } + } + + + //public Dictionary.ValueCollection Values + public string[] Values + { + get + { + //return m_Variables.Values; + return m_values.ToArray(); + } + } + */ + + public List GetValues(string Key) + { + var key = Key.ToLower(); + + List values = new List(); + + 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) + { + var 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) + { + var key = Key.ToLower(); + foreach (var kv in m_Variables) + if (kv.Key.ToLower() == key) + return true; + return false; + } + + /* + public bool ContainsKey(string Key) + { + //return m_Variables.ContainsKey(Key); + return m_keys.Contains(Key.ToLower()); + } + */ + + public bool ContainsValue(string Value) + { + var value = Value.ToLower(); + foreach (var kv in m_Variables) + if (kv.Value.ToLower() == value) + return true; + return false; + } + + //internal KeyList() + //{ + // m_Session = Session; + // m_Server = Server; + //} + + } +} \ No newline at end of file diff --git a/Esiur/Data/Structure.cs b/Esiur/Data/Structure.cs new file mode 100644 index 0000000..946496a --- /dev/null +++ b/Esiur/Data/Structure.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Esiur.Data; +using Esiur.Misc; +using Esiur.Engine; + +namespace Esiur.Data +{ + public class Structure : IEnumerable> + { + + private Dictionary dic = new Dictionary(StringComparer.OrdinalIgnoreCase); + private object syncRoot = new object(); + + + public bool ContainsKey(string key) + { + return dic.ContainsKey(key); + } + + public override string ToString() + { + var rt = ""; + foreach (var kv in dic) + rt += kv.Key + ": " + kv.Value.ToString() + "\r\n"; + + return rt.TrimEnd('\r', '\n'); + } + + public IEnumerator> GetEnumerator() + { + return dic.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return dic.GetEnumerator(); + } + + public int Length + { + get { return dic.Count; } + } + + public KeyValuePair At(int index) + { + return dic.ElementAt(index); + } + + public object SyncRoot + { + get { return syncRoot; } + } + + public string[] GetKeys() + { + return dic.Keys.ToArray(); + } + + public object this[string index] + { + get + { + if (dic.ContainsKey(index)) + return dic[index]; + else + return null; + } + set + { + if (dic.ContainsKey(index)) + dic[index] = value; + else + dic.Add(index, value); + } + } + + } +} diff --git a/Esiur/Engine/AsyncBag.cs b/Esiur/Engine/AsyncBag.cs new file mode 100644 index 0000000..e7f5a87 --- /dev/null +++ b/Esiur/Engine/AsyncBag.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Engine +{ + public class AsyncBag:AsyncReply + { + //List replies = new List(); + //List results = new List(); + Dictionary results = new Dictionary(); + int count = 0; + bool sealedBag = false; + + public void Then(Action callback) + { + base.Then(new Action(o => callback((T[])o))); + } + + /* + public void Trigger(T[] result) + { + Trigger((object)result); + } + */ + + public void Seal() + { + sealedBag = true; + + if (results.Count == 0) + Trigger(new T[0]); + + for(var i = 0; i < results.Count; i++) + //foreach(var reply in results.Keys) + results.Keys.ElementAt(i).Then((r) => { + results[results.Keys.ElementAt(i)] = (T)r; + count++; + if (count == results.Count) + Trigger(results.Values.ToArray()); + }); + } + + public void Add(AsyncReply reply) + { + if (!sealedBag) + results.Add(reply, default(T)); + } + + public AsyncBag() + { + + } + + /* + public AsyncBag(T[] result) + { + this.result = result; + } + */ + } +} diff --git a/Esiur/Engine/AsyncQueue.cs b/Esiur/Engine/AsyncQueue.cs new file mode 100644 index 0000000..af26c5d --- /dev/null +++ b/Esiur/Engine/AsyncQueue.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Engine +{ + public class AsyncQueue : AsyncReply + { + List> list = new List>(); + //Action callback; + object queueLock = new object(); + + public void Then(Action callback) + { + base.Then(new Action(o => callback((T)o))); + } + + public void Add(AsyncReply reply) + { + lock (queueLock) + list.Add(reply); + + resultReady = false; + reply.Then(processQueue); + } + + public void Remove(AsyncReply reply) + { + lock (queueLock) + list.Remove(reply); + processQueue(default(T)); + } + + void processQueue(T o) + { + lock (queueLock) + for (var i = 0; i < list.Count; i++) + if (list[i].Ready) + { + Trigger(list[i].Result); + list.RemoveAt(i); + i--; + } + else + break; + + resultReady = (list.Count == 0); + } + + public AsyncQueue() + { + + } + } +} diff --git a/Esiur/Engine/AsyncReply.cs b/Esiur/Engine/AsyncReply.cs new file mode 100644 index 0000000..2baf7c6 --- /dev/null +++ b/Esiur/Engine/AsyncReply.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Engine +{ + public class AsyncReply + { + protected List> callbacks = new List>(); + protected object result; + object callbacksLock = new object(); + + //bool fired = false; + protected bool resultReady = false; + + public bool Ready + { + get { return resultReady; } + } + + public object Result + { + get { return result; } + } + + public void Then(Action callback) + { + callbacks.Add(callback); + + if (resultReady) + callback(result); + // Trigger(this.result); + } + + public void Trigger(object result) + { + //if (!fired) + //{ + this.result = result; + resultReady = true; + + lock (callbacksLock) + { + foreach (var cb in callbacks) + cb(result); + //callbacks.Clear(); + } + /* + if (callback == null) + { + fireAtChance = true; + } + else + { + callback(result); + fired = true; + } + */ + //} + } + + public AsyncReply() + { + + } + + public AsyncReply(object result) + { + resultReady = true; + this.result = result; + } + } +} diff --git a/Esiur/Engine/AsyncReplyGeneric.cs b/Esiur/Engine/AsyncReplyGeneric.cs new file mode 100644 index 0000000..2649339 --- /dev/null +++ b/Esiur/Engine/AsyncReplyGeneric.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Esiur.Resource; + +namespace Esiur.Engine +{ + public class AsyncReply: AsyncReply + { + public void Then(Action callback) + { + base.Then(new Action(o => callback((T)o))); + } + + public void Trigger(T result) + { + Trigger((object)result); + } + + public AsyncReply() + { + + } + + public AsyncReply(T result) + : base(result) + { + + } + + + + } +} diff --git a/Esiur/Engine/IDestructible.cs b/Esiur/Engine/IDestructible.cs new file mode 100644 index 0000000..28397e0 --- /dev/null +++ b/Esiur/Engine/IDestructible.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Engine +{ + public delegate void DestroyedEvent(object sender); + + public interface IDestructible + { + event DestroyedEvent OnDestroy; + void Destroy(); + } +} diff --git a/Esiur/Engine/LogType.cs b/Esiur/Engine/LogType.cs new file mode 100644 index 0000000..063b68b --- /dev/null +++ b/Esiur/Engine/LogType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Engine +{ + public enum LogType + { + Debug, + Warning, + Error, + } +} diff --git a/Esiur/Esiur.csproj b/Esiur/Esiur.csproj new file mode 100644 index 0000000..83dce16 --- /dev/null +++ b/Esiur/Esiur.csproj @@ -0,0 +1,32 @@ + + + + netstandard1.5 + + + + True + TRACE;DEBUG;NETSTANDARD1_5 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Esiur/Misc/Global.cs b/Esiur/Misc/Global.cs new file mode 100644 index 0000000..bb39d17 --- /dev/null +++ b/Esiur/Misc/Global.cs @@ -0,0 +1,435 @@ +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.Engine; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Esiur.Misc +{ + public static class Global + { + private static KeyList variables = new KeyList(); + // private static Hashtable m_Cached = new Hashtable(); + //internal static bool SystemIsWorking = false; + + private static Random rand = new Random(System.Environment.TickCount); + + //public static Encoding DefaultEncoding = Encoding.GetEncoding(1252);// .GetEncoding("windows-1252"); + + public static KeyList Counters = new KeyList(); + + public delegate void LogEvent(string service, LogType type, string message); + + public static LogEvent SystemLog; + + static Random random = new Random(); + + + /* + public static char GetDirectorySeparator() + { + return System.IO.Path.DirectorySeparatorChar; + } + */ + + public static void Log(Exception ex, params object[] arguments) + { + try + { + + + var stack = new StackTrace(ex, true); + var frame = stack.GetFrames().First(); + var method = frame.GetMethod(); + var parameters = method.GetParameters(); + var service = method.DeclaringType.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); + + if (SystemLog != null) + SystemLog(service, type, message); + } + + /* + public static string GetTempPath() + { + return System.IO.Path.GetTempPath(); + } + */ + + 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]); + } + } +// Encoding ANSI = Encoding.GetEncoding(1252); + + /* + public static Hashtable Cached + { + get + { + return m_Cached; + } + }*/ + + /* + public static string ByteArrayToMAC(byte[] array) + { + string rt=""; + + if (array == null) + return "00:00:00:00:00:00"; + else + { + //for (int i = 0; i < array.Length - 1; i++) + // rt += Convert.ToString(array[i], 16) + ":"; + + //rt += Convert.ToString(array[array.Length - 1], 16); + + rt = BitConverter.ToString(array); + rt = rt.Replace('-', ':'); + return rt; + } + + } + */ + + + + /* + public static string IPAddressFromInt32(UInt32 IP) + { + //var dIP = DC.ToBytes(IP); + + return (IP >> 24) + "." + ((IP >> 16) & 0xFF) + "." + ((IP >> 8) & 0xFF) + "." + (IP & 0xFF); + } + */ + + public static KeyList 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 string GetUserPart(string strAddress) + { + return strAddress.Substring(0, strAddress.IndexOf("@", 0)); + } + + public static byte[][] GetBytesFromChunk(byte[] Data, int ChunkSize) + { + if (ChunkSize == 1) + { + byte[][] ar = new byte[0][]; + int ptr = 0; + while (ptr < Data.Length) + { + Array.Resize(ref ar, ar.Length + 1); + ar[ar.Length - 1] = new byte[Data[ptr]]; + Buffer.BlockCopy(Data, ++ptr, ar[ar.Length - 1], 0, Data[ptr]); + ptr += Data[ptr] + 1; + } + return ar; + } + return null; + } + + + + + + public static string GetFileTitle(string Filename) + { + string[] s = Filename.Split(Path.DirectorySeparatorChar); + return s[s.Length - 1]; + } + + public static string GetNewFileName(string FileDir) + { + string tempGetNewFileName = null; + short i = 0; + string NewFile = null; + NewFile = FileDir; + Begin: + FileInfo FF = new FileInfo(NewFile); + if (FF.Exists) + { + //If FSO.FileExists(NewFile) Then + i++; //= i + 1; + NewFile = FileDir.Substring(0, FileDir.Length - 4) + "_" + i + "." + FileDir.Substring(FileDir.Length - 3); + goto Begin; + } + else + { + tempGetNewFileName = NewFile; + } + return tempGetNewFileName; + } + + ///////////////////////////////////// + public static string TrimEx(string strIn) + { + return strIn.Replace("\r", "").Replace("\n", ""); + } + + /* + 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) + //public static string GenerateCode(int Length) + { + //var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()_-+=\\?/"; + var result = new string( + Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]) + .ToArray()); + if (result.Length < length) + Console.WriteLine(); + return result; + /* + int len = 0; + string code = ""; + + while(len < Length) + { + var c = Convert.ToChar((byte)(rand.NextDouble() * 255)); + if (Char.IsLetterOrDigit(c)) + { + code += c; + len++; + } + } + + return code; + */ + } + + public static string ReplaceOnce(string Expression, string Find, string Replacement) + { + int pos = Expression.IndexOf(Find); + if (pos != -1) + return Expression.Substring(0, pos) + Replacement + Expression.Substring(pos + Find.Length); + else + return Expression; + } + //public void Replace(string Expression, string Find, string Replacement, int Start, int Count) + //{ + // Expression.IndexOf( + //} + } +} \ No newline at end of file diff --git a/Esiur/Net/DataLink/PacketFilter.cs b/Esiur/Net/DataLink/PacketFilter.cs new file mode 100644 index 0000000..4c96c26 --- /dev/null +++ b/Esiur/Net/DataLink/PacketFilter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Engine; +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 Trigger(ResourceTrigger trigger); + + public abstract bool Execute(Packet packet); + + public void Destroy() + { + throw new NotImplementedException(); + } + } +} diff --git a/Esiur/Net/DataLink/PacketServer.cs b/Esiur/Net/DataLink/PacketServer.cs new file mode 100644 index 0000000..cfe7047 --- /dev/null +++ b/Esiur/Net/DataLink/PacketServer.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Engine; +using Esiur.Data; +using System.Runtime.InteropServices; +using Esiur.Net.Packets; +using Esiur.Resource; + +namespace Esiur.Net.DataLink +{ + public class PacketServer:IResource + { + List sources = new List(); + List filters = new List(); + + + [Storable] + public string Mode + { + get; + set; + } + + public Instance Instance + { + get; + set; + } + + public List Sources + { + get + { + return sources; + } + } + + public event DestroyedEvent OnDestroy; + + public void Destroy() + { + throw new NotImplementedException(); + } + + public AsyncReply Trigger(ResourceTrigger trigger) + { + if (trigger == ResourceTrigger.Initialize) + { + + foreach (Instance instance in Instance.Children) + { + + if (instance.Resource is PacketFilter) + { + filters.Add(instance.Resource as PacketFilter); + } + else if (instance.Resource is PacketSource) + { + sources.Add(instance.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( true); + } + + void PacketReceived(Packet Packet) + { + foreach (var f in filters) + { + if (f.Execute(Packet)) + { + break; + } + } + } + } +} diff --git a/Esiur/Net/DataLink/PacketSource.cs b/Esiur/Net/DataLink/PacketSource.cs new file mode 100644 index 0000000..490e7a0 --- /dev/null +++ b/Esiur/Net/DataLink/PacketSource.cs @@ -0,0 +1,71 @@ +using Esiur.Net.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Engine; +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 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() + { + throw new NotImplementedException(); + } + + /* + public virtual string TypeName + { + get + { + return "Raw"; + } + } + */ + + public abstract byte[] Address + { + get; + } + + public abstract string DeviceId + { + get; + } + } +} diff --git a/Esiur/Net/HTTP/HTTPConnection.cs b/Esiur/Net/HTTP/HTTPConnection.cs new file mode 100644 index 0000000..c654078 --- /dev/null +++ b/Esiur/Net/HTTP/HTTPConnection.cs @@ -0,0 +1,346 @@ +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.Net.Packets; +using Esiur.Misc; +using System.Security.Cryptography; + +namespace Esiur.Net.HTTP +{ + //[Serializable] + public class HTTPConnection : NetworkConnection + { + /* + public enum SendOptions : int + { + AllCalculateLength, + AllDontCalculateLength, + SpecifiedHeadersOnly, + DataOnly + } + */ + + public HTTPConnection() + { + Response = new HTTPResponsePacket(); + variables = new KeyList(); + } + + public void SetParent(HTTPServer Parent) + { + Server = Parent; + } + + //public bool HeadersSent; + + + private KeyList variables; + private bool Busy = false; + private DateTime RequestTime = DateTime.MinValue; + + public bool WSMode; + private HTTPServer Server; + public WebsocketPacket WSRequest; + public HTTPRequestPacket Request; + public HTTPResponsePacket Response; + + HTTPSession session; + + public KeyList Variables + { + get + { + return variables; + } + } + + + public bool IsBusy() + { + return Busy; + } + + + 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 + { + HTTPRequestPacket rp = new HTTPRequestPacket(); + var pSize = rp.Parse(data, 0, (uint)data.Length); + if (pSize > 0) + { + Request = rp; + return 0; + } + else + { + return pSize; + } + } + } + + + /* + public override void Send(string Response) + { + Send(Response, SendOptions.AllCalculateLength); + } + + public void Send(string Message, SendOptions Options) + { + + if (Response.Handled) + return; + + if (Response != null) + Send(Encoding.Default.GetBytes(Response), Options); + else + Send((byte[])null, Options); + } + + public void Send(MemoryStream ms) + { + Send(ms.ToArray(), SendOptions.AllCalculateLength); + } + + */ + + public void Flush() + { + // close the connection + if (Request.Headers["connection"].ToLower() != "keep-alive" & Connected) + Close(); + } + + public bool Upgrade() + { + if (IsWebsocketRequest()) + { + 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.Headers["Sec-WebSocket-Protocol"] = Request.Headers["Sec-WebSocket-Protocol"]; + //Response.Headers["Origin"] = Request.Headers["Origin"]; + + Response.Number = HTTPResponsePacket.ResponseCode.HTTP_SWITCHING; + Response.Text = "Switching Protocols"; + WSMode = true; + + //Send((byte[])null, SendOptions.AllDontCalculateLength); + Send(); + + return true; + } + + return false; + } + + public HTTPServer Parent + { + get + { + return Server; + } + } + + public override void Send(string data) + { + Response.Message = Encoding.UTF8.GetBytes(data); + Send(); + } + + public override void Send(byte[] message) + { + Response.Message = message; + Send(); + } + + public void Send(HTTPResponsePacket.ComposeOptions Options = HTTPResponsePacket.ComposeOptions.AllCalculateLength) + { + if (Response.Handled) + return; + + Busy = true; + + try + { + Response.Compose(Options); + base.Send(Response.Data); + + // Refresh the current session + if (session != null) + session.Refresh(); + + } + catch + { + try + { + Close();// Server.CloseClient(Connection); + } + finally { } + } + finally + { + Busy = false; + } + } + + + public void CreateNewSession() + { + if (session == null) + { + // Create a new one + session = Server.CreateSession(Global.GenerateCode(12), 60 * 20); + + HTTPResponsePacket.HTTPCookie cookie = new HTTPResponsePacket.HTTPCookie("SID", session.Id); + cookie.Expires = DateTime.MaxValue; + cookie.Path = "/"; + cookie.HttpOnly = true; + + Response.Cookies.Add(cookie); + } + } + + + public bool IsWebsocketRequest() + { + 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; + } + } + + public void SendFile(string filename) + { + if (Response.Handled == true) + return; + + 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 = HTTPResponsePacket.ResponseCode.HTTP_NOTFOUND; + Send("File Not Found");//, SendOptions.AllCalculateLength); + return; + } + + Busy = true; + + System.DateTime FWD = File.GetLastWriteTime(filename); + if (Request.Headers.ContainsKey("if-modified-since"))// != DateTime.Parse("12:00:00 AM")) + { + try + { + DateTime IMS = DateTime.Parse(Request.Headers["if-modified-since"]); + if (FWD <= IMS) + { + Response.Number = HTTPResponsePacket.ResponseCode.HTTP_NOTMODIFIED; + Response.Text = "Not Modified"; + } + } + catch + { + + } + } + + + if (Response.Number == HTTPResponsePacket.ResponseCode.HTTP_NOTMODIFIED) + { + Send((byte[])null); + } + else + { + // Fri, 30 Oct 2007 14:19:41 GMT + Response.Headers["Last-Modified"] = FWD.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss"); + FileInfo fi = new FileInfo(filename); + Response.Headers["Content-Length"] = fi.Length.ToString(); + Send(HTTPResponsePacket.ComposeOptions.SpecifiedHeadersOnly); + using (var fs = new FileStream(filename, FileMode.Open)) + { + var buffer = new byte[5000]; + var offset = 0; + while (offset < fs.Length) + { + var n = fs.Read(buffer, offset, buffer.Length); + offset += n; + base.Send(buffer); + } + } + } + + Busy = false; + + return; + } + catch + { + Busy = false; + + try + { + Close(); + } + finally { } + } + } + } +} \ No newline at end of file diff --git a/Esiur/Net/HTTP/HTTPFilter.cs b/Esiur/Net/HTTP/HTTPFilter.cs new file mode 100644 index 0000000..deb1196 --- /dev/null +++ b/Esiur/Net/HTTP/HTTPFilter.cs @@ -0,0 +1,58 @@ +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.Engine; +using Esiur.Resource; + +namespace Esiur.Net.HTTP +{ + + public abstract class HTTPFilter : IResource + { + public Instance Instance + { + get; + set; + } + + public event DestroyedEvent OnDestroy; + + public abstract AsyncReply Trigger(ResourceTrigger trigger); + + /* + public virtual void SessionModified(HTTPSession session, string key, object oldValue, object newValue) + { + + } + + public virtual void SessionExpired(HTTPSession session) + { + + } + */ + + public abstract bool Execute(HTTPConnection sender); + + public virtual void ClientConnected(HTTPConnection HTTP) + { + //return false; + } + + public virtual void ClientDisconnected(HTTPConnection HTTP) + { + //return false; + } + + public void Destroy() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Esiur/Net/HTTP/HTTPServer.cs b/Esiur/Net/HTTP/HTTPServer.cs new file mode 100644 index 0000000..1ed322b --- /dev/null +++ b/Esiur/Net/HTTP/HTTPServer.cs @@ -0,0 +1,416 @@ +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.Engine; +using Esiur.Net.Packets; +using System.Security.Cryptography.X509Certificates; +using Esiur.Resource; + +namespace Esiur.Net.HTTP +{ + public class HTTPServer : NetworkServer, IResource + { + Dictionary sessions= new Dictionary(); + + public Instance Instance + { + get; + set; + } + + [Storable] + string ip + { + get; + set; + } + [Storable] + ushort port + { + get; + set; + } + + [Storable] + uint timeout + { + get; + set; + } + + [Storable] + uint clock + { + get; + set; + } + + [Storable] + uint maxPost + { + get; + set; + } + + [Storable] + bool ssl + { + get; + set; + } + + [Storable] + string certificate + { + get; + set; + } + + //public override void ClientConnected(TClient Sender) + //{ + //} + /* + public DStringDictionary Configurations + { + get { return config; } + } + */ + + public enum ResponseCodes : int + { + HTTP_OK = 200, + HTTP_NOTFOUND = 404, + HTTP_SERVERERROR = 500, + HTTP_MOVED = 301, + HTTP_NOTMODIFIED = 304, + HTTP_REDIRECT = 307 + } + + + public HTTPSession CreateSession(string id, int timeout) + { + var s = new HTTPSession(); + + s.Set(id, timeout); + + + sessions.Add(id, s); + + return s; + } + + + /* + protected override void SessionModified(NetworkSession session, string key, object oldValue, object newValue) + { + foreach (var instance in Instance.Children) + { + var f = (HTTPFilter)instance; + f.SessionModified(session as HTTPSession, key, oldValue, newValue); + } + } + */ + + //public override object InitializeLifetimeService() + //{ + // return null; + //} + + + + + + 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 sender) + { + Console.WriteLine("OUT: " + this.Connections.Count); + + foreach (IResource resource in Instance.Children) + { + if (resource is HTTPFilter) + { + (resource as HTTPFilter).ClientDisconnected(sender); + } + } + } + + + + protected override void DataReceived(HTTPConnection sender, NetworkBuffer data) + { + //Console.WriteLine(Data); + // Initialize a new session + //HTTPConnection HTTP = (HTTPConnection)sender.ExtraObject; + + //string Data = System.Text.Encoding.Default.GetString(ReceivedData); + + byte[] msg = data.Read(); + + + + + var BL = sender.Parse(msg); + + if (BL == 0) + { + if (sender.Request.Method == HTTPRequestPacket.HTTPMethod.UNKNOWN) + { + sender.Close(); + return; + } + if (sender.Request.URL == "") + { + sender.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 > maxPost) + { + sender.Send( + "POST method content is larger than " + + maxPost + + " bytes."); + + sender.Close(); + } + else + { + data.HoldFor(msg, (uint)(msg.Length + BL)); + } + return; + } + else if (BL < 0) // for security + { + sender.Close(); + return; + } + + + + if (sender.IsWebsocketRequest() & !sender.WSMode) + { + sender.Upgrade(); + //return; + } + + + //return; + + try + { + foreach (IResource resource in Instance.Children) + { + if (resource is HTTPFilter) + { + if ((resource as HTTPFilter).Execute(sender)) + return; + } + } + + sender.Send("Bad Request"); + sender.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); + sender.Send(Return500(ex.Message)); + } + + } + } + + private string Return500(string sMessage) + { + string sTMP = null; + sTMP = "500 Internal Server Error
\r\n"; + sTMP = sTMP + "
\r\n"; + sTMP = sTMP + "500 Sorry - Internal Server Error
" + sMessage + "\r\n"; + sTMP = sTMP + "
\r\n"; + sTMP = sTMP + "
\r\n"; + return sTMP; + } + + + /* + 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 AsyncReply 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; + + 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, + timeout, + clock); + } + else if (trigger == ResourceTrigger.Terminate) + { + Stop(); + } + else if (trigger == ResourceTrigger.SystemReload) + { + Trigger(ResourceTrigger.Terminate); + Trigger(ResourceTrigger.Initialize); + } + + return new AsyncReply(true); + + } + + protected override void ClientConnected(HTTPConnection sender) + { + //sender.SessionModified += SessionModified; + //sender.SessionEnded += SessionExpired; + sender.SetParent(this); + + Console.WriteLine("IN: " + this.Connections.Count); + + foreach (IResource resource in Instance.Children) + { + if (resource is HTTPFilter) + { + (resource as HTTPFilter).ClientConnected(sender); + } + } + } + + public void Destroy() + { + throw new NotImplementedException(); + } + + /* + 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(); + //} + } +} \ No newline at end of file diff --git a/Esiur/Net/HTTP/HTTPSession.cs b/Esiur/Net/HTTP/HTTPSession.cs new file mode 100644 index 0000000..075bb01 --- /dev/null +++ b/Esiur/Net/HTTP/HTTPSession.cs @@ -0,0 +1,106 @@ +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.Engine; + +namespace Esiur.Net.HTTP +{ + public class HTTPSession : IDestructible // 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 variables; + + public event SessionEndedEvent OnEnd; + public event SessionModifiedEvent OnModify; + public event DestroyedEvent OnDestroy; + + public KeyList Variables + { + get { return variables; } + } + + public HTTPSession() + { + variables = new KeyList(); + variables.OnModified += new KeyList.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) + { + 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; } + } + } + +} \ No newline at end of file diff --git a/Esiur/Net/HTTP/IIPoWS.cs b/Esiur/Net/HTTP/IIPoWS.cs new file mode 100644 index 0000000..1433060 --- /dev/null +++ b/Esiur/Net/HTTP/IIPoWS.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Esiur.Resource; +using Esiur.Net.IIP; +using Esiur.Net.Sockets; +using Esiur.Engine; + +namespace Esiur.Net.HTTP +{ + public class IIPoWS: HTTPFilter + { + public override bool Execute(HTTPConnection sender) + { + if (sender.Request.Filename.StartsWith("/iip/")) + { + // find the service + var path = sender.Request.Filename.Substring(5);// sender.Request.Query["path"]; + + + Warehouse.Get(path).Then((r) => + { + if (r is DistributedServer) + { + var httpServer = sender.Parent; + var iipServer = r as DistributedServer; + var tcpSocket = sender.Unassign(); + var wsSocket = new WSSocket(tcpSocket); + httpServer.Connections.Remove(sender); + var iipConnection = new DistributedConnection(); + iipConnection.Server = iipServer; + iipConnection.Assign(wsSocket); + } + }); + + return true; + } + + return false; + } + + public override AsyncReply Trigger(ResourceTrigger trigger) + { + return new AsyncReply(true); + } + } + +} + diff --git a/Esiur/Net/IIP/DistributedConnection.cs b/Esiur/Net/IIP/DistributedConnection.cs new file mode 100644 index 0000000..713b911 --- /dev/null +++ b/Esiur/Net/IIP/DistributedConnection.cs @@ -0,0 +1,514 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using Esiur.Net.Sockets; +using Esiur.Data; +using Esiur.Misc; +using Esiur.Engine; +using Esiur.Net.Packets; +using Esiur.Resource; +using Esiur.Security.Authority; +using Esiur.Resource.Template; +using System.Linq; +using System.Diagnostics; + +namespace Esiur.Net.IIP +{ + public partial class DistributedConnection : NetworkConnection, IStore + { + public delegate void ReadyEvent(DistributedConnection sender); + public delegate void ErrorEvent(DistributedConnection sender, byte errorCode, string errorMessage); + + /// + /// Ready event is raised when the connection is fully established. + /// + public event ReadyEvent OnReady; + + /// + /// Error event + /// + public event ErrorEvent OnError; + + IIPPacket packet = new IIPPacket(); + IIPAuthPacket authPacket = new IIPAuthPacket(); + + byte[] sessionId; + AuthenticationType hostType; + string domain; + string localUsername, remoteUsername; + byte[] localPassword; + + byte[] localNonce, remoteNonce; + + bool ready, readyToEstablish; + + DateTime loginDate; + KeyList variables = new KeyList(); + + /// + /// Local username to authenticate ourselves. + /// + public string LocalUsername { get; set; } + + /// + /// Peer's username. + /// + public string RemoteUsername { get; set; } + + /// + /// Working domain. + /// + public string Domain { get { return domain; } } + + + /// + /// Distributed server responsible for this connection, usually for incoming connections. + /// + public DistributedServer Server + { + get; + set; + } + + /// + /// Send data to the other end as parameters + /// + /// Values will be converted to bytes then sent. + internal void SendParams(params object[] values) + { + var ar = BinaryList.ToBytes(values); + Send(ar); + + //StackTrace stackTrace = new StackTrace(; + + // Get calling method name + + //Console.WriteLine("TX " + hostType + " " + ar.Length + " " + stackTrace.GetFrame(1).GetMethod().ToString()); + } + + /// + /// Send raw data through the connection. + /// + /// Data to send. + public override void Send(byte[] data) + { + //Console.WriteLine("Client: {0}", Data.Length); + + Global.Counters["IIP Sent Packets"]++; + base.Send(data); + } + + /// + /// KeyList to store user variables related to this connection. + /// + public KeyList Variables + { + get + { + return variables; + } + } + + /// + /// IResource interface. + /// + public Instance Instance + { + get; + set; + } + + /// + /// Assign a socket to the connection. + /// + /// Any socket that implements ISocket. + public override void Assign(ISocket socket) + { + base.Assign(socket); + + + if (hostType == AuthenticationType.Client) + { + // declare (Credentials -> No Auth, No Enctypt) + var un = DC.ToBytes(localUsername); + var dmn = DC.ToBytes(domain); + + if (socket.State == SocketState.Established) + { + SendParams((byte)0x60, (byte)dmn.Length, dmn, localNonce, (byte)un.Length, un); + } + else + { + socket.OnConnect += () => + { // declare (Credentials -> No Auth, No Enctypt) + SendParams((byte)0x60, (byte)dmn.Length, dmn, localNonce, (byte)un.Length, un); + }; + } + } + } + + + /// + /// Create a new distributed connection. + /// + /// Socket to transfer data through. + /// Working domain. + /// Username. + /// Password. + public DistributedConnection(ISocket socket, string domain, string username, string password) + { + //Instance.Name = Global.GenerateCode(12); + this.hostType = AuthenticationType.Client; + this.domain = domain; + this.localUsername = username; + this.localPassword = DC.ToBytes(password); + + init(); + + Assign(socket); + } + + /// + /// Create a new instance of a distributed connection + /// + public DistributedConnection() + { + //myId = Global.GenerateCode(12); + // localParams.Host = DistributedParameters.HostType.Host; + init(); + } + + + + public string Link(IResource resource) + { + if (resource is DistributedConnection) + { + var r = resource as DistributedResource; + if (r.Instance.Store == this) + return this.Instance.Name + "/" + r.Id; + } + + return null; + } + + + void init() + { + queue.Then((x) => + { + if (x.Type == DistributedResourceQueueItem.DistributedResourceQueueItemType.Event) + x.Resource._EmitEventByIndex(x.Index, (object[])x.Value); + else + x.Resource.UpdatePropertyByIndex(x.Index, x.Value); + }); + + var r = new Random(); + localNonce = new byte[32]; + r.NextBytes(localNonce); + } + + + + protected override void DataReceived(NetworkBuffer data) + { + // Console.WriteLine("DR " + hostType + " " + data.Available + " " + RemoteEndPoint.ToString()); + var msg = data.Read(); + uint offset = 0; + uint ends = (uint)msg.Length; + while (offset < ends) + { + + if (ready) + { + var rt = packet.Parse(msg, offset, ends); + if (rt <= 0) + { + data.HoldFor(msg, offset, ends - offset, (uint)(-rt)); + return; + } + else + { + offset += (uint)rt; + + if (packet.Command == IIPPacket.IIPPacketCommand.Event) + { + switch (packet.Event) + { + case IIPPacket.IIPPacketEvent.ResourceReassigned: + IIPEventResourceReassigned(packet.ResourceId, packet.NewResourceId); + break; + case IIPPacket.IIPPacketEvent.ResourceDestroyed: + IIPEventResourceDestroyed(packet.ResourceId); + break; + case IIPPacket.IIPPacketEvent.PropertyUpdated: + IIPEventPropertyUpdated(packet.ResourceId, packet.MethodIndex, packet.Content); + break; + case IIPPacket.IIPPacketEvent.EventOccured: + IIPEventEventOccured(packet.ResourceId, packet.MethodIndex, packet.Content); + break; + } + } + else if (packet.Command == IIPPacket.IIPPacketCommand.Request) + { + switch (packet.Action) + { + case IIPPacket.IIPPacketAction.AttachResource: + IIPRequestAttachResource(packet.CallbackId, packet.ResourceId); + break; + case IIPPacket.IIPPacketAction.ReattachResource: + IIPRequestReattachResource(packet.CallbackId, packet.ResourceId, packet.ResourceAge); + break; + case IIPPacket.IIPPacketAction.DetachResource: + IIPRequestDetachResource(packet.CallbackId, packet.ResourceId); + break; + case IIPPacket.IIPPacketAction.CreateResource: + IIPRequestCreateResource(packet.CallbackId, packet.ClassName); + break; + case IIPPacket.IIPPacketAction.DeleteResource: + IIPRequestDeleteResource(packet.CallbackId, packet.ResourceId); + break; + case IIPPacket.IIPPacketAction.TemplateFromClassName: + IIPRequestTemplateFromClassName(packet.CallbackId, packet.ClassName); + break; + case IIPPacket.IIPPacketAction.TemplateFromClassId: + IIPRequestTemplateFromClassId(packet.CallbackId, packet.ClassId); + break; + case IIPPacket.IIPPacketAction.TemplateFromResourceLink: + IIPRequestTemplateFromResourceLink(packet.CallbackId, packet.ResourceLink); + break; + case IIPPacket.IIPPacketAction.TemplateFromResourceId: + IIPRequestTemplateFromResourceId(packet.CallbackId, packet.ResourceId); + break; + case IIPPacket.IIPPacketAction.ResourceIdFromResourceLink: + IIPRequestResourceIdFromResourceLink(packet.CallbackId, packet.ResourceLink); + break; + case IIPPacket.IIPPacketAction.InvokeFunction: + IIPRequestInvokeFunction(packet.CallbackId, packet.ResourceId, packet.MethodIndex, packet.Content); + break; + case IIPPacket.IIPPacketAction.GetProperty: + IIPRequestGetProperty(packet.CallbackId, packet.ResourceId, packet.MethodIndex); + break; + case IIPPacket.IIPPacketAction.GetPropertyIfModified: + IIPRequestGetPropertyIfModifiedSince(packet.CallbackId, packet.ResourceId, packet.MethodIndex, packet.ResourceAge); + break; + case IIPPacket.IIPPacketAction.SetProperty: + IIPRequestSetProperty(packet.CallbackId, packet.ResourceId, packet.MethodIndex, packet.Content); + break; + } + } + else if (packet.Command == IIPPacket.IIPPacketCommand.Reply) + { + switch (packet.Action) + { + case IIPPacket.IIPPacketAction.AttachResource: + IIPReply(packet.CallbackId, packet.ClassId, packet.ResourceAge, packet.ResourceLink, packet.Content); + + //IIPReplyAttachResource(packet.CallbackId, packet.ResourceAge, Codec.ParseValues(packet.Content)); + break; + case IIPPacket.IIPPacketAction.ReattachResource: + //IIPReplyReattachResource(packet.CallbackId, packet.ResourceAge, Codec.ParseValues(packet.Content)); + IIPReply(packet.CallbackId, packet.ResourceAge, packet.Content); + + break; + case IIPPacket.IIPPacketAction.DetachResource: + //IIPReplyDetachResource(packet.CallbackId); + IIPReply(packet.CallbackId); + break; + case IIPPacket.IIPPacketAction.CreateResource: + //IIPReplyCreateResource(packet.CallbackId, packet.ClassId, packet.ResourceId); + IIPReply(packet.CallbackId, packet.ClassId, packet.ResourceId); + break; + case IIPPacket.IIPPacketAction.DeleteResource: + //IIPReplyDeleteResource(packet.CallbackId); + IIPReply(packet.CallbackId); + break; + case IIPPacket.IIPPacketAction.TemplateFromClassName: + //IIPReplyTemplateFromClassName(packet.CallbackId, ResourceTemplate.Parse(packet.Content)); + IIPReply(packet.CallbackId, ResourceTemplate.Parse(packet.Content)); + break; + case IIPPacket.IIPPacketAction.TemplateFromClassId: + //IIPReplyTemplateFromClassId(packet.CallbackId, ResourceTemplate.Parse(packet.Content)); + IIPReply(packet.CallbackId, ResourceTemplate.Parse(packet.Content)); + break; + case IIPPacket.IIPPacketAction.TemplateFromResourceLink: + //IIPReplyTemplateFromResourceLink(packet.CallbackId, ResourceTemplate.Parse(packet.Content)); + IIPReply(packet.CallbackId, ResourceTemplate.Parse(packet.Content)); + break; + case IIPPacket.IIPPacketAction.TemplateFromResourceId: + //IIPReplyTemplateFromResourceId(packet.CallbackId, ResourceTemplate.Parse(packet.Content)); + IIPReply(packet.CallbackId, ResourceTemplate.Parse(packet.Content)); + break; + case IIPPacket.IIPPacketAction.ResourceIdFromResourceLink: + //IIPReplyResourceIdFromResourceLink(packet.CallbackId, packet.ClassId, packet.ResourceId, packet.ResourceAge); + IIPReply(packet.CallbackId, packet.ClassId, packet.ResourceId, packet.ResourceAge); + break; + case IIPPacket.IIPPacketAction.InvokeFunction: + //IIPReplyInvokeFunction(packet.CallbackId, Codec.Parse(packet.Content, 0)); + IIPReply(packet.CallbackId, packet.Content); + break; + case IIPPacket.IIPPacketAction.GetProperty: + //IIPReplyGetProperty(packet.CallbackId, Codec.Parse(packet.Content, 0)); + IIPReply(packet.CallbackId, packet.Content); + break; + case IIPPacket.IIPPacketAction.GetPropertyIfModified: + //IIPReplyGetPropertyIfModifiedSince(packet.CallbackId, Codec.Parse(packet.Content, 0)); + IIPReply(packet.CallbackId, packet.Content); + break; + case IIPPacket.IIPPacketAction.SetProperty: + //IIPReplySetProperty(packet.CallbackId); + IIPReply(packet.CallbackId); + break; + } + + } + + } + } + + else + { + var rt = authPacket.Parse(msg, offset, ends); + + Console.WriteLine(hostType.ToString() + " " + offset + " " + ends + " " + rt + " " + authPacket.ToString()); + + if (rt <= 0) + { + data.HoldFor(msg, ends + (uint)(-rt)); + return; + } + else + { + offset += (uint)rt; + + if (hostType == AuthenticationType.Host) + { + if (authPacket.Command == IIPAuthPacket.IIPAuthPacketCommand.Declare) + { + if (authPacket.RemoteMethod == IIPAuthPacket.IIPAuthPacketMethod.Credentials && authPacket.LocalMethod == IIPAuthPacket.IIPAuthPacketMethod.None) + { + remoteUsername = authPacket.RemoteUsername; + remoteNonce = authPacket.RemoteNonce; + domain = authPacket.Domain; + SendParams((byte)0xa0, localNonce); + } + } + else if (authPacket.Command == IIPAuthPacket.IIPAuthPacketCommand.Action) + { + if (authPacket.Action == IIPAuthPacket.IIPAuthPacketAction.AuthenticateHash) + { + var remoteHash = authPacket.Hash; + + Server.Membership.GetPassword(remoteUsername, domain).Then((pw) => + { + + + if (pw != null) + { + var hashFunc = SHA256.Create(); + var hash = hashFunc.ComputeHash(BinaryList.ToBytes(pw, remoteNonce, localNonce)); + if (hash.SequenceEqual(remoteHash)) + { + // send our hash + var localHash = hashFunc.ComputeHash(BinaryList.ToBytes(localNonce, remoteNonce, pw)); + + SendParams((byte)0, localHash); + + readyToEstablish = true; + } + else + { + Console.WriteLine("Incorrect password"); + SendParams((byte)0xc0, (byte)1, (ushort)5, DC.ToBytes("Error")); + } + } + }); + } + else if (authPacket.Action == IIPAuthPacket.IIPAuthPacketAction.NewConnection) + { + if (readyToEstablish) + { + var r = new Random(); + sessionId = new byte[32]; + r.NextBytes(sessionId); + SendParams((byte)0x28, sessionId); + ready = true; + OnReady?.Invoke(this); + } + } + } + } + else if (hostType == AuthenticationType.Client) + { + if (authPacket.Command == IIPAuthPacket.IIPAuthPacketCommand.Acknowledge) + { + remoteNonce = authPacket.RemoteNonce; + + // send our hash + var hashFunc = SHA256.Create(); + var localHash = hashFunc.ComputeHash(BinaryList.ToBytes(localPassword, localNonce, remoteNonce)); + + SendParams((byte)0, localHash); + } + else if (authPacket.Command == IIPAuthPacket.IIPAuthPacketCommand.Action) + { + if (authPacket.Action == IIPAuthPacket.IIPAuthPacketAction.AuthenticateHash) + { + // check if the server knows my password + var hashFunc = SHA256.Create(); + var remoteHash = hashFunc.ComputeHash(BinaryList.ToBytes(remoteNonce, localNonce, localPassword)); + + if (remoteHash.SequenceEqual(authPacket.Hash)) + { + // send establish request + SendParams((byte)0x20, (ushort)0); + } + else + { + SendParams((byte)0xc0, 1, (ushort)5, DC.ToBytes("Error")); + } + } + else if (authPacket.Action == IIPAuthPacket.IIPAuthPacketAction.ConnectionEstablished) + { + sessionId = authPacket.SessionId; + ready = true; + OnReady?.Invoke(this); + } + } + else if (authPacket.Command == IIPAuthPacket.IIPAuthPacketCommand.Error) + { + OnError?.Invoke(this, authPacket.ErrorCode, authPacket.ErrorMessage); + Close(); + } + } + } + } + } + + } + + /// + /// Resource interface + /// + /// Resource trigger. + /// + public AsyncReply Trigger(ResourceTrigger trigger) + { + return new AsyncReply(); + } + + /// + /// Store interface. + /// + /// Resource. + /// + public bool Put(IResource resource) + { + resources.Add(Convert.ToUInt32(resource.Instance.Name), (DistributedResource)resource); + return true; + } + } +} diff --git a/Esiur/Net/IIP/DistributedConnectionProtocol.cs b/Esiur/Net/IIP/DistributedConnectionProtocol.cs new file mode 100644 index 0000000..0c3c044 --- /dev/null +++ b/Esiur/Net/IIP/DistributedConnectionProtocol.cs @@ -0,0 +1,802 @@ +using Esiur.Data; +using Esiur.Engine; +using Esiur.Net.Packets; +using Esiur.Resource; +using Esiur.Resource.Template; +using System; +using System.Collections.Generic; + +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Net.IIP +{ + partial class DistributedConnection + { + KeyList resources = new KeyList(); + KeyList> resourceRequests = new KeyList>(); + KeyList> templateRequests = new KeyList>(); + + + KeyList> pathRequests = new KeyList>(); + + Dictionary templates = new Dictionary(); + + KeyList> requests = new KeyList>(); + + uint callbackCounter = 0; + + AsyncQueue queue = new AsyncQueue(); + + /// + /// Send IIP request. + /// + /// Packet action. + /// Arguments to send. + /// + internal AsyncReply SendRequest(IIPPacket.IIPPacketAction action, params object[] args) + { + var reply = new AsyncReply(); + callbackCounter++; + var bl = new BinaryList((byte)(0x40 | (byte)action), callbackCounter); + bl.AddRange(args); + Send(bl.ToArray()); + requests.Add(callbackCounter, reply); + return reply; + } + + void IIPReply(uint callbackId, params object[] results) + { + var req = requests.Take(callbackId); + req?.Trigger(results); + } + + void IIPEventResourceReassigned(uint resourceId, uint newResourceId) + { + + } + + void IIPEventResourceDestroyed(uint resourceId) + { + if (resources.Contains(resourceId)) + { + var r = resources[resourceId]; + resources.Remove(resourceId); + r.Destroy(); + } + } + + void IIPEventPropertyUpdated(uint resourceId, byte index, byte[] content) + { + if (resources.Contains(resourceId)) + { + // push to the queue to gaurantee serialization + var reply = new AsyncReply(); + queue.Add(reply); + + var r = resources[resourceId]; + Codec.Parse(content, 0, this).Then((arguments) => + { + var pt = r.Template.GetPropertyTemplate(index); + if (pt != null) + { + reply.Trigger(new DistributedResourceQueueItem((DistributedResource)r, DistributedResourceQueueItem.DistributedResourceQueueItemType.Propery, arguments, index)); + } + else + { // ft found, fi not found, this should never happen + queue.Remove(reply); + } + }); + } + } + + + void IIPEventEventOccured(uint resourceId, byte index, byte[] content) + { + if (resources.Contains(resourceId)) + { + // push to the queue to gaurantee serialization + var reply = new AsyncReply(); + var r = resources[resourceId]; + + queue.Add(reply); + + Codec.ParseVarArray(content, this).Then((arguments) => + { + var et = r.Template.GetEventTemplate(index); + if (et != null) + { + reply.Trigger(new DistributedResourceQueueItem((DistributedResource)r, DistributedResourceQueueItem.DistributedResourceQueueItemType.Event, arguments, index)); + } + else + { // ft found, fi not found, this should never happen + queue.Remove(reply); + } + }); + } + } + + void IIPRequestAttachResource(uint callback, uint resourceId) + { + Warehouse.Get(resourceId).Then((res) => + { + if (res != null) + { + var r = res as IResource; + r.Instance.ResourceEventOccured += Instance_EventOccured; + r.Instance.ResourceModified += Instance_PropertyModified; + r.Instance.ResourceDestroyed += Instance_ResourceDestroyed; + + var link = DC.ToBytes(r.Instance.Link); + + // reply ok + SendParams((byte)0x80, callback, r.Instance.Template.ClassId, r.Instance.Age, (ushort)link.Length, link, Codec.ComposeVarArray(r.Instance.Serialize(), this, true)); + } + else + { + // reply failed + //SendParams(0x80, r.Instance.Id, r.Instance.Age, r.Instance.Serialize(false, this)); + + } + }); + } + + void IIPRequestReattachResource(uint callback, uint resourceId, uint resourceAge) + { + Warehouse.Get(resourceId).Then((res) => + { + if (res != null) + { + var r = res as IResource; + r.Instance.ResourceEventOccured += Instance_EventOccured; + r.Instance.ResourceModified += Instance_PropertyModified; + r.Instance.ResourceDestroyed += Instance_ResourceDestroyed; + // reply ok + SendParams((byte)0x81, callback, r.Instance.Age, Codec.ComposeVarArray(r.Instance.Serialize(), this, true)); + } + else + { + // reply failed + } + }); + } + + void IIPRequestDetachResource(uint callback, uint resourceId) + { + Warehouse.Get(resourceId).Then((res) => + { + if (res != null) + { + var r = res as IResource; + r.Instance.ResourceEventOccured -= Instance_EventOccured; + r.Instance.ResourceModified -= Instance_PropertyModified; + r.Instance.ResourceDestroyed -= Instance_ResourceDestroyed; + // reply ok + SendParams((byte)0x82, callback); + } + else + { + // reply failed + } + }); + } + + void IIPRequestCreateResource(uint callback, string className) + { + // not implemented + } + + void IIPRequestDeleteResource(uint callback, uint resourceId) + { + // not implemented + + } + + void IIPRequestTemplateFromClassName(uint callback, string className) + { + Warehouse.GetTemplate(className).Then((t) => + { + if (t != null) + SendParams((byte)0x88, callback, t.Content); + else + { + // reply failed + } + }); + } + + void IIPRequestTemplateFromClassId(uint callback, Guid classId) + { + Warehouse.GetTemplate(classId).Then((t) => + { + if (t != null) + SendParams((byte)0x89, callback, (uint)t.Content.Length, t.Content); + else + { + // reply failed + } + }); + } + + void IIPRequestTemplateFromResourceLink(uint callback, string resourceLink) + { + Warehouse.GetTemplate(resourceLink).Then((t) => + { + if (t != null) + SendParams((byte)0x8A, callback, t.Content); + else + { + // reply failed + } + }); + } + + void IIPRequestTemplateFromResourceId(uint callback, uint resourceId) + { + Warehouse.Get(resourceId).Then((r) => + { + if (r != null) + SendParams((byte)0x8B, callback, r.Instance.Template.Content); + else + { + // reply failed + } + }); + } + + void IIPRequestResourceIdFromResourceLink(uint callback, string resourceLink) + { + Warehouse.Get(resourceLink).Then((r) => + { + if (r != null) + SendParams((byte)0x8C, callback, r.Instance.Template.ClassId, r.Instance.Id, r.Instance.Age); + else + { + // reply failed + } + }); + } + + void IIPRequestInvokeFunction(uint callback, uint resourceId, byte index, byte[] content) + { + Warehouse.Get(resourceId).Then((r) => + { + if (r != null) + { + Codec.ParseVarArray(content, this).Then(async (arguments) => + { + var ft = r.Instance.Template.GetFunctionTemplate(index); + if (ft != null) + { + if (r is DistributedResource) + { + var rt = (r as DistributedResource)._Invoke(index, arguments); + if (rt != null) + { + rt.Then(res => + { + SendParams((byte)0x90, callback, Codec.Compose(res, this)); + }); + } + else + { + // function not found on a distributed object + } + } + else + { +#if NETSTANDARD1_5 + var fi = r.GetType().GetTypeInfo().GetMethod(ft.Name); +#else + var fi = r.GetType().GetMethod(ft.Name); +#endif + + if (fi != null) + { + // cast shit + ParameterInfo[] pi = fi.GetParameters(); + object[] args = null; + + if (pi.Length > 0) + { + int argsCount = pi.Length; + args = new object[pi.Length]; + + if (pi[pi.Length - 1].ParameterType == typeof(DistributedConnection)) + { + args[--argsCount] = this; + } + + if (arguments != null) + { + for (int i = 0; i < argsCount && i < arguments.Length; i++) + { + args[i] = DC.CastConvert(arguments[i], pi[i].ParameterType); + } + } + } + + var rt = fi.Invoke(r, args); + + if (rt is Task) + { + var t = (Task)rt; + //Console.WriteLine(t.IsCompleted); + await t; +#if NETSTANDARD1_5 + var res = t.GetType().GetTypeInfo().GetProperty("Result").GetValue(t); +#else + var res = t.GetType().GetProperty("Result").GetValue(t); +#endif + SendParams((byte)0x90, callback, Codec.Compose(res, this)); + } + else if (rt is AsyncReply) //(rt.GetType().IsGenericType && (rt.GetType().GetGenericTypeDefinition() == typeof(AsyncReply<>))) + { + (rt as AsyncReply).Then(res => + { + SendParams((byte)0x90, callback, Codec.Compose(res, this)); + }); + } + else + { + SendParams((byte)0x90, callback, Codec.Compose(rt, this)); + } + } + else + { + // ft found, fi not found, this should never happen + } + } + } + else + { + // no function at this index + } + }); + } + else + { + // no resource with this id + } + }); + } + + void IIPRequestGetProperty(uint callback, uint resourceId, byte index) + { + Warehouse.Get(resourceId).Then((r) => + { + if (r != null) + { + var pt = r.Instance.Template.GetFunctionTemplate(index); + if (pt != null) + { + if (r is DistributedResource) + { + SendParams((byte)0x91, callback, Codec.Compose((r as DistributedResource)._Get(pt.Index), this)); + } + else + { +#if NETSTANDARD1_5 + var pi = r.GetType().GetTypeInfo().GetProperty(pt.Name); +#else + var pi = r.GetType().GetProperty(pt.Name); +#endif + + if (pi != null) + { + SendParams((byte)0x91, callback, Codec.Compose(pi.GetValue(r), this)); + } + else + { + // pt found, pi not found, this should never happen + } + } + } + else + { + // pt not found + } + } + else + { + // resource not found + } + }); + } + + void IIPRequestGetPropertyIfModifiedSince(uint callback, uint resourceId, byte index, uint age) + { + Warehouse.Get(resourceId).Then((r) => + { + if (r != null) + { + var pt = r.Instance.Template.GetFunctionTemplate(index); + if (pt != null) + { + if (r.Instance.GetAge(index) > age) + { +#if NETSTANDARD1_5 + var pi = r.GetType().GetTypeInfo().GetProperty(pt.Name); +#else + var pi = r.GetType().GetProperty(pt.Name); +#endif + if (pi != null) + { + SendParams((byte)0x92, callback, Codec.Compose(pi.GetValue(r), this)); + } + else + { + // pt found, pi not found, this should never happen + } + } + else + { + SendParams((byte)0x92, callback, (byte)DataType.NotModified); + } + } + else + { + // pt not found + } + } + else + { + // resource not found + } + }); + } + + void IIPRequestSetProperty(uint callback, uint resourceId, byte index, byte[] content) + { + Warehouse.Get(resourceId).Then((r) => + { + if (r != null) + { + + + var pt = r.Instance.Template.GetPropertyTemplate(index); + if (pt != null) + { + Codec.Parse(content, 0, this).Then((value) => + { + if (r is DistributedResource) + { + // propagation + (r as DistributedResource)._Set(index, value).Then((x) => + { + SendParams((byte)0x93, callback); + }); + } + else + { +#if NETSTANDARD1_5 + var pi = r.GetType().GetTypeInfo().GetProperty(pt.Name); +#else + var pi = r.GetType().GetProperty(pt.Name); +#endif + if (pi != null) + { + // cast new value type to property type + + var v = DC.CastConvert(value, pi.PropertyType); + pi.SetValue(r, v); + + SendParams((byte)0x93, callback); + } + else + { + // pt found, pi not found, this should never happen + } + } + + }); + } + else + { + // property not found + } + } + else + { + // resource not found + } + }); + } + + /* + void IIPReplyAttachResource(uint callback, uint resourceAge, object[] properties) + { + if (requests.ContainsKey(callback)) + { + var req = requests[callback]; + var r = resources[(uint)req.Arguments[0]]; + + if (r == null) + { + r.Instance.Deserialize(properties); + r.Instance.Age = resourceAge; + r.Attached(); + + // process stack + foreach (var rr in resources.Values) + rr.Stack.ProcessStack(); + } + else + { + // resource not found + } + } + } + + void IIPReplyReattachResource(uint callback, uint resourceAge, object[] properties) + { + var req = requests.Take(callback); + + if (req != null) + { + var r = resources[(uint)req.Arguments[0]]; + + if (r == null) + { + r.Instance.Deserialize(properties); + r.Instance.Age = resourceAge; + r.Attached(); + + // process stack + foreach (var rr in resources.Values) + rr.Stack.ProcessStack(); + } + else + { + // resource not found + } + } + } + + + void IIPReplyDetachResource(uint callback) + { + var req = requests.Take(callback); + // nothing to do + } + + void IIPReplyCreateResource(uint callback, Guid classId, uint resourceId) + { + var req = requests.Take(callback); + // nothing to do + + } + void IIPReplyDeleteResource(uint callback) + { + var req = requests.Take(callback); + // nothing to do + + } + + void IIPReplyTemplateFromClassName(uint callback, ResourceTemplate template) + { + // cache + if (!templates.ContainsKey(template.ClassId)) + templates.Add(template.ClassId, template); + + var req = requests.Take(callback); + req?.Trigger(template); + } + + void IIPReplyTemplateFromClassId(uint callback, ResourceTemplate template) + { + // cache + if (!templates.ContainsKey(template.ClassId)) + templates.Add(template.ClassId, template); + + var req = requests.Take(callback); + req?.Trigger(template); + + } + + void IIPReplyTemplateFromResourceLink(uint callback, ResourceTemplate template) + { + // cache + if (!templates.ContainsKey(template.ClassId)) + templates.Add(template.ClassId, template); + + var req = requests.Take(callback); + req?.Trigger(template); + } + + void IIPReplyTemplateFromResourceId(uint callback, ResourceTemplate template) + { + // cache + if (!templates.ContainsKey(template.ClassId)) + templates.Add(template.ClassId, template); + + var req = requests.Take(callback); + req?.Trigger(template); + } + + void IIPReplyResourceIdFromResourceLink(uint callback, Guid classId, uint resourceId, uint resourceAge) + { + var req = requests.Take(callback); + req?.Trigger(template); + } + + void IIPReplyInvokeFunction(uint callback, object returnValue) + { + + } + + void IIPReplyGetProperty(uint callback, object value) + { + + } + void IIPReplyGetPropertyIfModifiedSince(uint callback, object value) + { + + } + void IIPReplySetProperty(uint callback) + { + + } + */ + + /// + /// Get the ResourceTemplate for a given class Id. + /// + /// Class GUID. + /// ResourceTemplate. + public AsyncReply GetTemplate(Guid classId) + { + if (templates.ContainsKey(classId)) + return new AsyncReply(templates[classId]); + else if (templateRequests.ContainsKey(classId)) + return templateRequests[classId]; + + var reply = new AsyncReply(); + templateRequests.Add(classId, reply); + + SendRequest(IIPPacket.IIPPacketAction.TemplateFromClassId, classId).Then((rt) => + { + templateRequests.Remove(classId); + templates.Add(((ResourceTemplate)rt[0]).ClassId, (ResourceTemplate)rt[0]); + reply.Trigger(rt[0]); + }); + + return reply; + } + + // IStore interface + /// + /// Get a resource by its path. + /// + /// Path to the resource. + /// Resource + public AsyncReply Get(string path) + { + if (pathRequests.ContainsKey(path)) + return pathRequests[path]; + + var reply = new AsyncReply(); + pathRequests.Add(path, reply); + + var bl = new BinaryList(path); + bl.Insert(0, (ushort)bl.Length); + + SendRequest(IIPPacket.IIPPacketAction.ResourceIdFromResourceLink, bl.ToArray()).Then((rt) => + { + pathRequests.Remove(path); +//(Guid)rt[0], + Fetch( (uint)rt[1]).Then((r) => + { + reply.Trigger(r); + }); + }); + + + return reply; + } + + /// + /// Retrive a resource by its instance Id. + /// + /// Instance Id + /// Resource + public AsyncReply Retrieve(uint iid) + { + foreach (var r in resources.Values) + if (r.Instance.Id == iid) + return new AsyncReply(r); + return new AsyncReply(null); + } + + /// + /// Fetch a resource from the other end + /// + /// Class GUID + /// Resource IdGuid classId + /// DistributedResource + public AsyncReply Fetch( uint id) + { + if (resourceRequests.ContainsKey(id) && resources.ContainsKey(id)) + { + // dig for dead locks + return resourceRequests[id]; + } + else if (resourceRequests.ContainsKey(id)) + return resourceRequests[id]; + else if (resources.ContainsKey(id)) + return new AsyncReply(resources[id]); + + var reply = new AsyncReply(); + + SendRequest(IIPPacket.IIPPacketAction.AttachResource, id).Then((rt) => + { + GetTemplate((Guid)rt[0]).Then((tmp) => + { + + // ClassId, ResourceAge, ResourceLink, Content + + //var dr = Warehouse.New(id.ToString(), this); + //var dr = nInitialize(this, tmp, id, (uint)rt[0]); + var dr = new DistributedResource(this, tmp, id, (uint)rt[1], (string)rt[2]); + Warehouse.Put(dr, id.ToString(), this); + + Codec.ParseVarArray((byte[])rt[3], this).Then((ar) => + { + dr._Attached(ar); + resourceRequests.Remove(id); + reply.Trigger(dr); + }); + }); + }); + + return reply; + } + + private void Instance_ResourceDestroyed(IResource resource) + { + // compose the packet + SendParams((byte)0x1, resource.Instance.Id); + } + + private void Instance_PropertyModified(IResource resource, string name, object newValue, object oldValue) + { + var pt = resource.Instance.Template.GetPropertyTemplate(name); + + if (pt == null) + return; + + // compose the packet + if (newValue is Func) + SendParams((byte)0x10, resource.Instance.Id, pt.Index, Codec.Compose((newValue as Func)(this), this)); + else + SendParams((byte)0x10, resource.Instance.Id, pt.Index, Codec.Compose(newValue, this)); + + } + + private void Instance_EventOccured(IResource resource, string name, string[] receivers, object[] args) + { + var et = resource.Instance.Template.GetEventTemplate(name); + + if (et == null) + return; + + if (receivers != null) + if (!receivers.Contains(RemoteUsername)) + return; + + var clientArgs = new object[args.Length]; + for (var i = 0; i < args.Length; i++) + if (args[i] is Func) + clientArgs[i] = (args[i] as Func)(this); + else + clientArgs[i] = args[i]; + + + // compose the packet + SendParams((byte)0x11, resource.Instance.Id, (byte)et.Index, Codec.ComposeVarArray(args, this, true)); + + } + } +} diff --git a/Esiur/Net/IIP/DistributedResource.cs b/Esiur/Net/IIP/DistributedResource.cs new file mode 100644 index 0000000..6f94617 --- /dev/null +++ b/Esiur/Net/IIP/DistributedResource.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.IO; +using System.Collections; +using System.ComponentModel; +using Esiur.Misc; +using Esiur.Data; +using System.Dynamic; +using System.Security.Cryptography; +using Esiur.Engine; +using System.Runtime.CompilerServices; +using System.Reflection.Emit; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Esiur.Resource; +using Esiur.Resource.Template; + +namespace Esiur.Net.IIP +{ + + //[System.Runtime.InteropServices.ComVisible(true)] + public class DistributedResource : DynamicObject, IResource + { + + /// + /// Raised when the distributed resource is destroyed. + /// + public event DestroyedEvent OnDestroy; + + uint instanceId; + DistributedConnection connection; + + + bool isAttached = false; + bool isReady = false; + + + //Structure properties = new Structure(); + + string link; + uint age; + uint[] ages; + object[] properties; + DistributedResourceEvent[] events; + + ResourceTemplate template; + + + //DistributedResourceStack stack; + + + bool destroyed; + + /// + /// Resource template for the remotely located resource. + /// + public ResourceTemplate Template + { + get { return template; } + } + + + /// + /// Connection responsible for the distributed resource. + /// + public DistributedConnection Connection + { + get { return connection; } + } + + /// + /// Resource link + /// + public string Link + { + get { return link; } + } + + /// + /// Instance Id given by the other end. + /// + public uint Id + { + get { return instanceId; } + } + + /// + /// IDestructible interface. + /// + public void Destroy() + { + destroyed = true; + OnDestroy?.Invoke(this); + } + + /// + /// Resource is ready when all its properties are attached. + /// + internal bool IsReady + { + get + { + return isReady; + } + } + + /// + /// Resource is attached when all its properties are received. + /// + internal bool IsAttached + { + get + { + return isAttached; + } + } + + + + // public DistributedResourceStack Stack + //{ + // get { return stack; } + //} + + /// + /// Create a new distributed resource. + /// + /// Connection responsible for the distributed resource. + /// Resource template. + /// Instance Id given by the other end. + /// Resource age. + public DistributedResource(DistributedConnection connection, ResourceTemplate template, uint instanceId, uint age, string link) + { + this.link = link; + this.connection = connection; + this.instanceId = instanceId; + this.template = template; + this.age = age; + } + + internal void _Ready() + { + isReady = true; + } + + internal bool _Attached(object[] properties) + { + + if (isAttached) + return false; + else + { + this.properties = properties; + ages = new uint[properties.Length]; + this.events = new DistributedResourceEvent[template.Events.Length]; + isAttached = true; + } + return true; + } + + internal void _EmitEventByIndex(byte index, object[] args) + { + var et = template.GetEventTemplate(index); + events[index]?.Invoke(this, args); + Instance.EmitResourceEvent(et.Name, null, args); + } + + public AsyncReply _Invoke(byte index, object[] args) + { + if (destroyed) + throw new Exception("Trying to access destroyed object"); + + if (index >= template.Functions.Length) + throw new Exception("Function index is incorrect"); + + var reply = new AsyncReply(); + + var parameters = Codec.ComposeVarArray(args, connection, true); + connection.SendRequest(Packets.IIPPacket.IIPPacketAction.InvokeFunction, instanceId, index, parameters).Then((res) => + { + Codec.Parse((byte[])res[0], 0, connection).Then((rt) => + { + reply.Trigger(rt); + }); + }); + + + return reply; + + } + + + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + var ft = template.GetFunctionTemplate(binder.Name); + + var reply = new AsyncReply(); + + if (isAttached && ft!=null) + { + result = _Invoke(ft.Index, args); + return true; + } + else + { + result = null; + return false; + } + } + + /// + /// Get a property value. + /// + /// Zero-based property index. + /// Value + internal object _Get(byte index) + { + if (index >= properties.Length) + return null; + return properties[index]; + } + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (destroyed) + throw new Exception("Trying to access destroyed object"); + + result = null; + + if (!isAttached) + return false; + + var pt = template.GetPropertyTemplate(binder.Name); + + if (pt != null) + { + result = properties[pt.Index]; + return true; + } + else + { + var et = template.GetEventTemplate(binder.Name); + if (et == null) + return false; + + result = events[et.Index]; + + return true; + } + } + + + internal void UpdatePropertyByIndex(byte index, object value) + { + var pt = template.GetPropertyTemplate(index); + properties[index] = value; + Instance.Modified(pt.Name, value); + } + + /// + /// Set property value. + /// + /// Zero-based property index. + /// Value + /// Indicator when the property is set. + internal AsyncReply _Set(byte index, object value) + { + if (index >= properties.Length) + return null; + + var reply = new AsyncReply(); + + var parameters = Codec.Compose(value, connection); + connection.SendRequest(Packets.IIPPacket.IIPPacketAction.SetProperty, instanceId, index, parameters).Then((res) => + { + // not really needed, server will always send property modified, this only happens if the programmer forgot to emit in property setter + //Update(index, value); + reply.Trigger(null); + // nothing to do here + }); + + return reply; + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (destroyed) + throw new Exception("Trying to access destroyed object"); + + if (!isAttached) + return false; + + var pt = template.GetPropertyTemplate(binder.Name); + + + if (pt != null) + { + _Set(pt.Index, value); + return true; + } + else + { + var et = template.GetEventTemplate(binder.Name); + if (et == null) + return false; + + events[et.Index] = (DistributedResourceEvent)value; + + return true; + } + + } + + /* + public async void InvokeMethod(byte index, object[] arguments, DistributedConnection sender) + { + // get function parameters + Type t = this.GetType(); + + MethodInfo mi = t.GetMethod(GetFunctionName(index), BindingFlags.DeclaredOnly | + BindingFlags.Public | + BindingFlags.Instance | BindingFlags.InvokeMethod); + if (mi != null) + { + try + { + var res = await invokeMethod(mi, arguments, sender); + object rt = Codec.Compose(res); + sender.SendParams((byte)0x80, instanceId, index, rt); + } + catch(Exception ex) + { + var msg = ex.InnerException != null ? ex.InnerException.Message : ex.Message; + sender.SendParams((byte)0x8E, instanceId, index, Codec.Compose(msg)); + } + } + } + */ + + + + /// + /// Resource interface. + /// + public Instance Instance + { + get; + set; + } + + /// + /// Create a new instance of distributed resource. + /// + public DistributedResource() + { + //stack = new DistributedResourceStack(this); + + } + + /// + /// Resource interface. + /// + /// + /// + public AsyncReply Trigger(ResourceTrigger trigger) + { + // do nothing. + return new AsyncReply(true); + } + } +} \ No newline at end of file diff --git a/Esiur/Net/IIP/DistributedResourceEvent.cs b/Esiur/Net/IIP/DistributedResourceEvent.cs new file mode 100644 index 0000000..2040408 --- /dev/null +++ b/Esiur/Net/IIP/DistributedResourceEvent.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Net.IIP +{ + public delegate void DistributedResourceEvent(DistributedResource sender, params object[] arguments); +} diff --git a/Esiur/Net/IIP/DistributedResourceQueueItem.cs b/Esiur/Net/IIP/DistributedResourceQueueItem.cs new file mode 100644 index 0000000..3c951d4 --- /dev/null +++ b/Esiur/Net/IIP/DistributedResourceQueueItem.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Net.IIP +{ + public class DistributedResourceQueueItem + { + public enum DistributedResourceQueueItemType + { + Propery, + Event + } + + DistributedResourceQueueItemType type; + byte index; + object value; + DistributedResource resource; + + public DistributedResourceQueueItem(DistributedResource resource, DistributedResourceQueueItemType type, object value, byte index) + { + this.resource = resource; + this.index = index; + this.type = type; + this.value = value; + } + + public DistributedResource Resource + { + get { return resource; } + } + public DistributedResourceQueueItemType Type + { + get { return type; } + } + + public byte Index + { + get { return index; } + } + + public object Value + { + get { return value; } + } + } +} diff --git a/Esiur/Net/IIP/DistributedServer.cs b/Esiur/Net/IIP/DistributedServer.cs new file mode 100644 index 0000000..f8bb883 --- /dev/null +++ b/Esiur/Net/IIP/DistributedServer.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Net.Sockets; +using Esiur.Misc; +using System.Threading; +using Esiur.Data; +using Esiur.Engine; +using System.Net; +using Esiur.Resource; +using Esiur.Security.Membership; + +namespace Esiur.Net.IIP +{ + public class DistributedServer : NetworkServer, IResource + { + + [Storable] + [ResourceProperty] + public string ip + { + get; + set; + } + + [Storable] + [ResourceProperty] + public IMembership Membership + { + get; + set; + } + [Storable] + [ResourceProperty] + public ushort port + { + get; + set; + } + + [Storable] + [ResourceProperty] + public uint timeout + { + get; + set; + } + + [Storable] + [ResourceProperty] + public uint clock + { + get; + set; + } + + + public Instance Instance + { + get; + set; + } + + public AsyncReply Trigger(ResourceTrigger trigger) + { + if (trigger == ResourceTrigger.Initialize) + { + TCPSocket listener; + + if (ip != null) + listener = new TCPSocket(new IPEndPoint(IPAddress.Parse(ip), port)); + else + listener = new TCPSocket(new IPEndPoint(IPAddress.Any, port)); + + Start(listener, timeout, clock); + } + else if (trigger == ResourceTrigger.Terminate) + { + Stop(); + } + else if (trigger == ResourceTrigger.SystemReload) + { + Trigger(ResourceTrigger.Terminate); + Trigger(ResourceTrigger.Initialize); + } + + return new AsyncReply(true); + } + + + + protected override void DataReceived(DistributedConnection sender, NetworkBuffer data) + { + //throw new NotImplementedException(); + + } + + private void SessionModified(DistributedConnection Session, string Key, object NewValue) + { + + } + + + + protected override void ClientConnected(DistributedConnection sender) + { + Console.WriteLine("DistributedConnection Client Connected"); + sender.Server = this; + } + + protected override void ClientDisconnected(DistributedConnection sender) + { + Console.WriteLine("DistributedConnection Client Disconnected"); + } + } +} diff --git a/Esiur/Net/IIP/DistributedSession.cs b/Esiur/Net/IIP/DistributedSession.cs new file mode 100644 index 0000000..a08bfdc --- /dev/null +++ b/Esiur/Net/IIP/DistributedSession.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Esiur.Net.Sockets; +using Esiur.Security.Authority; + +namespace Esiur.Net.IIP +{ + public class DistributedSession : NetworkSession + { + Source Source { get; } + Authentication Authentication; + } +} diff --git a/Esiur/Net/NetworkBuffer.cs b/Esiur/Net/NetworkBuffer.cs new file mode 100644 index 0000000..bec11c6 --- /dev/null +++ b/Esiur/Net/NetworkBuffer.cs @@ -0,0 +1,167 @@ +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; + + 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) + { + 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) + { + 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() + { + if (data.Length == 0) + return null; + + byte[] rt = null; + + if (neededDataLength == 0) + { + rt = data; + data = new byte[0]; + } + 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; + } + } + + return rt; + } + } +} diff --git a/Esiur/Net/NetworkConnection.cs b/Esiur/Net/NetworkConnection.cs new file mode 100644 index 0000000..5a6c666 --- /dev/null +++ b/Esiur/Net/NetworkConnection.cs @@ -0,0 +1,248 @@ +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.Engine; +using Esiur.Data; +using Esiur.Net.Sockets; +using Esiur.Resource; + +namespace Esiur.Net +{ + public class NetworkConnection: IDestructible// : IResource where TS : NetworkSession + { + private 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 ConnectionEstablishedEvent(NetworkConnection sender); + + public event ConnectionEstablishedEvent OnConnect; + public event DataReceivedEvent OnDataReceived; + public event ConnectionClosedEvent OnClose; + public event DestroyedEvent OnDestroy; + + + + public void Destroy() + { + // if (connected) + Close(); + OnDestroy?.Invoke(this); + } + + public NetworkConnection() + { + + } + + + + public ISocket Socket + { + get + { + return sock; + } + } + + public virtual void Assign(ISocket socket) + { + lastAction = DateTime.Now; + sock = socket; + //connected = true; + socket.OnReceive += Socket_OnReceive; + socket.OnClose += Socket_OnClose; + socket.OnConnect += Socket_OnConnect; + if (socket.State == SocketState.Established) + socket.Begin(); + } + + private void Socket_OnConnect() + { + OnConnect?.Invoke(this); + } + + private void Socket_OnClose() + { + OnClose?.Invoke(this); + } + + private void Socket_OnReceive(NetworkBuffer buffer) + { + try + { + // Unassigned ? + if (sock == null) + return; + + // Closed ? + if (sock.State == SocketState.Closed || sock.State == SocketState.Terminated) // || !connected) + return; + + lastAction = DateTime.Now; + + while (buffer.Available > 0 && !buffer.Protected) + DataReceived(buffer); + + } + catch (Exception ex) + { + Global.Log("NetworkConnection", LogType.Warning, ex.ToString()); + } + } + + public ISocket Unassign() + { + if (sock != null) + { + // connected = false; + sock.OnClose -= Socket_OnClose; + sock.OnConnect -= Socket_OnConnect; + sock.OnReceive -= Socket_OnReceive; + + 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 Connected + { + get + { + return sock.State == SocketState.Established;// connected; + } + } + + + /* + public void CloseAndWait() + { + try + { + if (!connected) + return; + + if (sock != null) + sock.Close(); + + while (connected) + { + Thread.Sleep(100); + } + } + finally + { + + } + } + */ + + public virtual void Send(byte[] msg) + { + //Console.WriteLine("TXX " + msg.Length); + + try + { + //if (!connected) + //{ + //Console.WriteLine("not connected"); + // return; + //} + + if (sock != null) + { + lastAction = DateTime.Now; + sock.Send(msg); + } + } + catch + { + + } + } + + public virtual void Send(string data) + { + Send(Encoding.UTF8.GetBytes(data)); + } + } +} \ No newline at end of file diff --git a/Esiur/Net/NetworkServer.cs b/Esiur/Net/NetworkServer.cs new file mode 100644 index 0000000..9e7a693 --- /dev/null +++ b/Esiur/Net/NetworkServer.cs @@ -0,0 +1,346 @@ +using System; +using System.Threading; +using System.Collections.Generic; +using Esiur.Data; +using Esiur.Misc; +using Esiur.Engine; +using Esiur.Net.Sockets; +using Esiur.Resource; +using System.Threading.Tasks; + +namespace Esiur.Net +{ + //public abstract class NetworkServer : IResource where TSession : NetworkSession, new() where TConnection : NetworkConnection, new() + + public abstract class NetworkServer: IDestructible where TConnection : NetworkConnection, new() + { + //private bool isRunning; + uint clock; + private ISocket listener; + private AutoList> connections; + + //private Thread thread; + + protected abstract void DataReceived(TConnection sender, NetworkBuffer data); + protected abstract void ClientConnected(TConnection sender); + protected abstract void ClientDisconnected(TConnection sender); + + // private int port; + // private IPAddress ip = null; + + private uint timeout; + private Timer timer; + //public KeyList Sessions = new KeyList(); + + public event DestroyedEvent OnDestroy; + + public AutoList> Connections + { + get + { + return connections; + } + } + + /* + public void RemoveSession(string ID) + { + Sessions.Remove(ID); + } + + public void RemoveSession(TSession Session) + { + if (Session != null) + Sessions.Remove(Session.Id); + } + */ + + /* + public TSession CreateSession(string ID, int Timeout) + { + TSession s = new TSession(); + + s.SetSession(ID, Timeout, new NetworkSession.SessionModifiedEvent(SessionModified) + , new NetworkSession.SessionEndedEvent(SessionEnded)); + + + Sessions.Add(ID, s); + return s; + } + */ + + /* + private void pSessionModified(TSession session, string key, object oldValue, object newValue) + { + SessionModified((TSession)session, key, oldValue, newValue); + } + + private void pSessionEnded(NetworkSession session) + { + SessionEnded((TSession)session); + } + */ + + /* + protected virtual void SessionModified(NetworkSession session, string key, object oldValue, object newValue) + { + + } + + protected virtual void SessionEnded(NetworkSession session) + { + Sessions.Remove(session.Id); + session.Destroy(); + } + */ + + private void MinuteThread(object state) + { + List ToBeClosed = null; + + + lock (connections.SyncRoot) + { + foreach (TConnection c in connections) + { + if (DateTime.Now.Subtract(c.LastAction).TotalSeconds >= timeout) + { + if (ToBeClosed == null) + ToBeClosed = new List(); + ToBeClosed.Add(c); + } + } + } + + //Console.WriteLine("UnLock MinuteThread"); + + 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(ISocket socket, uint timeout, uint clock) + { + if (listener != null) + return; + + //if (socket.State == SocketState.Listening) + // return; + + //if (isRunning) + // return; + + connections = new AutoList>(this); + + + if (timeout > 0 & clock > 0) + { + timer = new Timer(MinuteThread, null, TimeSpan.FromMinutes(0), TimeSpan.FromSeconds(clock)); + this.timeout = timeout; + } + + //this.ip = ip; + //this.port = port; + this.clock = clock; + + + // start a new thread for the server to live on + //isRunning = true; + + + + listener = socket; + + // Start accepting + listener.Accept().Then(NewConnection); + + //var rt = listener.Accept().Then() + //thread = new Thread(new System.Threading.ThreadStart(ListenForConnections)); + + //thread.Start(); + + } + + /* + public int LocalPort + { + get + { + return port; + } + } + */ + + + public uint Clock + { + get { return clock; } + } + + 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 + { + Console.WriteLine("Server@{0} is down", port); + } + } + + + + private void NewConnection(ISocket sock) + { + + try + { + + /* + if (listener.State == SocketState.Closed || listener.State == SocketState.Terminated) + { + Console.WriteLine("Listen socket break "); + Console.WriteLine(listener.LocalEndPoint.Port); + break; + } + */ + + if (sock == null) + { + Console.Write("sock == null"); + return; + } + + //sock.ReceiveBufferSize = 102400; + //sock.SendBufferSize = 102400; + + TConnection c = new TConnection(); + c.OnDataReceived += OnDataReceived; + c.OnConnect += OnClientConnect; + c.OnClose += OnClientClose; + + + connections.Add(c); + c.Assign(sock); + + ClientConnected(c); + + // Accept more + listener.Accept().Then(NewConnection); + + + + } + catch (Exception ex) + { + Console.WriteLine("TSERVER " + ex.ToString()); + Global.Log("TServer", LogType.Error, ex.ToString()); + } + + //isRunning = false; + + + } + + public bool IsRunning + { + get + { + return listener.State == SocketState.Listening; + //isRunning; + } + } + + public void OnDataReceived(NetworkConnection sender, NetworkBuffer data) + { + DataReceived((TConnection)sender, data); + } + + public void OnClientConnect(NetworkConnection 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(NetworkConnection sender) + { + try + { + sender.Destroy(); + ClientDisconnected((TConnection)sender); + } + catch (Exception ex) + { + Global.Log("NetworkServer:OnClientDisconnect", LogType.Error, ex.ToString()); + } + + sender = null; + GC.Collect(); + } + + + public void Destroy() + { + Stop(); + OnDestroy?.Invoke(this); + } + + ~NetworkServer() + { + Stop(); + //Connections = null; + listener = null; + } + } + +} \ No newline at end of file diff --git a/Esiur/Net/NetworkSession.cs b/Esiur/Net/NetworkSession.cs new file mode 100644 index 0000000..1ad583f --- /dev/null +++ b/Esiur/Net/NetworkSession.cs @@ -0,0 +1,106 @@ +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.Engine; + +namespace Esiur.Net +{ + public class NetworkSession:IDestructible // 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 variables; + + public event SessionEndedEvent OnEnd; + public event SessionModifiedEvent OnModify; + public event DestroyedEvent OnDestroy; + + public KeyList Variables + { + get { return variables; } + } + + public NetworkSession() + { + variables = new KeyList(); + variables.OnModified += new KeyList.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) + { + 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; } + } + } + +} \ No newline at end of file diff --git a/Esiur/Net/Packets/HTTPRequestPacket.cs b/Esiur/Net/Packets/HTTPRequestPacket.cs new file mode 100644 index 0000000..7317354 --- /dev/null +++ b/Esiur/Net/Packets/HTTPRequestPacket.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Misc; +using Esiur.Data; +using System.Net; + +namespace Esiur.Net.Packets +{ + public class HTTPRequestPacket : Packet + { + + public enum HTTPMethod:byte + { + GET, + POST, + HEAD, + PUT, + DELETE, + OPTIONS, + TRACE, + CONNECT, + UNKNOWN + } + + public StringKeyList Query; + public HTTPMethod Method; + public StringKeyList Headers; + + public bool WSMode; + + public string Version; + public StringKeyList Cookies; // String + public string URL; /// With query + public string Filename; /// Without query + //public byte[] PostContents; + public KeyList PostForms; + public byte[] Message; + + + private HTTPMethod getMethod(string method) + { + switch (method.ToLower()) + { + case "get": + return HTTPMethod.GET; + case "post": + return HTTPMethod.POST; + case "head": + return HTTPMethod.HEAD; + case "put": + return HTTPMethod.PUT; + case "delete": + return HTTPMethod.DELETE; + case "options": + return HTTPMethod.OPTIONS; + case "trace": + return HTTPMethod.TRACE; + case "connect": + return HTTPMethod.CONNECT; + default: + return HTTPMethod.UNKNOWN; + } + } + + public override string ToString() + { + return "HTTPRequestPacket" + + "\n\tVersion: " + Version + + "\n\tMethod: " + Method + + "\n\tURL: " + URL + + "\n\tMessage: " + (Message != null ? Message.Length.ToString() : "NULL"); + } + + public override long Parse(byte[] data, uint offset, uint ends) + { + string[] sMethod = null; + string[] sLines = null; + + uint headerSize = 0; + + for (uint i = offset; i < ends - 3; i++) + { + if (data[i] == '\r' && data[i + 1] == '\n' + && data[i + 2] == '\r' && data[i + 3] == '\n') + { + sLines = Encoding.ASCII.GetString(data, (int)offset,(int)( i - offset)).Split(new string[] { "\r\n" }, + StringSplitOptions.None); + + headerSize = i + 4; + break; + } + } + + if (headerSize == 0) + return -1; + + Cookies = new StringKeyList(); + PostForms = new KeyList(); + Query = new StringKeyList(); + Headers = new StringKeyList(); + + sMethod = sLines[0].Split(' '); + Method = getMethod(sMethod[0].Trim()); + + if (sMethod.Length == 3) + { + sMethod[1] = WebUtility.UrlDecode(sMethod[1]); + if (sMethod[1].Length >= 7) + { + if (sMethod[1].Substring(0, 7) == "http://") + { + sMethod[1] = sMethod[1].Substring(sMethod[1].IndexOf("/", 7)); + } + } + + URL = sMethod[1].Trim(); + + if (URL.IndexOf("?", 0) != -1) + { + Filename = URL.Split(new char[] { '?' }, 2)[0]; + } + else + { + Filename = URL; + } + + if (Filename.IndexOf("%", 0) != -1) + { + Filename = WebUtility.UrlDecode(Filename); + } + + Version = sMethod[2].Trim(); + } + + // Read all headers + + for (int i = 1; i < sLines.Length; i++) + { + if (sLines[i] == String.Empty) + { + // Invalid header + return 0; + } + + if (sLines[i].IndexOf(':') == -1) + { + // Invalid header + return 0; + } + + string[] header = sLines[i].Split(new char[] { ':' }, 2); + + header[0] = header[0].ToLower(); + Headers[header[0]] = header[1].Trim(); + + if (header[0] == "cookie") + { + string[] cookies = header[1].Split(';'); + + foreach (string cookie in cookies) + { + if (cookie.IndexOf('=') != -1) + { + string[] splitCookie = cookie.Split('='); + splitCookie[0] = splitCookie[0].Trim(); + splitCookie[1] = splitCookie[1].Trim(); + if (!(Cookies.ContainsKey(splitCookie[0].Trim()))) + Cookies.Add(splitCookie[0], splitCookie[1]); + } + else + { + if (!(Cookies.ContainsKey(cookie.Trim()))) + { + Cookies.Add(cookie.Trim(), String.Empty); + } + } + } + } + } + + // Query String + if (URL.IndexOf("?", 0) != -1) + { + string[] SQ = URL.Split(new char[] { '?' }, 2)[1].Split('&'); + foreach (string S in SQ) + { + if (S.IndexOf("=", 0) != -1) + { + string[] qp = S.Split(new char[] { '=' }, 2); + + if (!Query.ContainsKey(WebUtility.UrlDecode(qp[0]))) + { + Query.Add(WebUtility.UrlDecode(qp[0]), WebUtility.UrlDecode(qp[1])); + } + } + else + { + if (!(Query.ContainsKey(WebUtility.UrlDecode(S)))) + { + Query.Add(WebUtility.UrlDecode(S), null); + } + } + } + } + + // Post Content-Length + if (Method == HTTPMethod.POST) + { + try + { + + uint postSize = uint.Parse((string)Headers["content-length"]); + + // check limit + if (postSize > data.Length - headerSize) + return postSize - (data.Length - headerSize); + + + if (Headers["content-type"] == "application/x-www-form-urlencoded" + || Headers["content-type"] == "" + || Headers["content-type"] == null) + { + string[] PostVars = null; + PostVars = Encoding.UTF8.GetString(data, (int)headerSize, (int)postSize).Split('&'); + for (int J = 0; J < PostVars.Length; J++) + { + if (PostVars[J].IndexOf("=") != -1) + { + string key = WebUtility.HtmlDecode( + WebUtility.UrlDecode(PostVars[J].Split(new char[] { '=' }, 2)[0])); + if (PostForms.Contains(key)) + PostForms[key] = WebUtility.HtmlDecode( + WebUtility.UrlDecode(PostVars[J].Split(new char[] { '=' }, 2)[1])); + else + PostForms.Add(key, WebUtility.HtmlDecode( + WebUtility.UrlDecode(PostVars[J].Split(new char[] { '=' }, 2)[1]))); + } + else + if (PostForms.Contains("unknown")) + PostForms["unknown"] = PostForms["unknown"] + + "&" + WebUtility.HtmlDecode(WebUtility.UrlDecode(PostVars[J])); + else + PostForms.Add("unknown", WebUtility.HtmlDecode(WebUtility.UrlDecode(PostVars[J]))); + } + } + else if (Headers["content-type"].StartsWith("multipart/form-data")) + { + int st = 1; + int ed = 0; + string strBoundry = "--" + Headers["content-type"].Substring( + Headers["content-type"].IndexOf("boundary=", 0) + 9); + + string[] sc = Encoding.UTF8.GetString(data, (int)headerSize, (int)postSize).Split( + new string[] { strBoundry }, StringSplitOptions.None); + + + for (int j = 1; j < sc.Length - 1; j++) + { + string[] ps = sc[j].Split(new string[] { "\r\n\r\n" }, 2, StringSplitOptions.None); + ps[1] = ps[1].Substring(0, ps[1].Length - 2); // remove the empty line + st = ps[0].IndexOf("name=", 0) + 6; + ed = ps[0].IndexOf("\"", st); + PostForms.Add(ps[0].Substring(st, ed - st), ps[1]); + } + } + else + { + //PostForms.Add(Headers["content-type"], Encoding.Default.GetString( )); + Message = DC.Clip(data, headerSize, postSize); + } + + return headerSize + postSize; + + } + catch + { + return 0; + } + } + + return headerSize; + } + } +} + diff --git a/Esiur/Net/Packets/HTTPResponsePacket.cs b/Esiur/Net/Packets/HTTPResponsePacket.cs new file mode 100644 index 0000000..d15a350 --- /dev/null +++ b/Esiur/Net/Packets/HTTPResponsePacket.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Misc; +using Esiur.Data; + +namespace Esiur.Net.Packets +{ + public class HTTPResponsePacket : Packet + { + + public enum ComposeOptions : int + { + AllCalculateLength, + AllDontCalculateLength, + SpecifiedHeadersOnly, + DataOnly + } + + public enum ResponseCode : int + { + HTTP_SWITCHING = 101, + HTTP_OK = 200, + HTTP_NOTFOUND = 404, + HTTP_SERVERERROR = 500, + HTTP_MOVED = 301, + HTTP_NOTMODIFIED = 304, + HTTP_REDIRECT = 307 + } + + public class HTTPCookie + { + public string Name; + public string Value; + public DateTime Expires; + public string Path; + public bool HttpOnly; + public string Domain; + + public HTTPCookie(string Name, string Value) + { + this.Name = Name; + this.Value = Value; + } + + public HTTPCookie(string Name, string Value, DateTime Expires) + { + this.Name = Name; + this.Value = Value; + this.Expires = Expires; + } + + public override string ToString() + { + //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 = Name + "=" + 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; + } + } + + public StringKeyList Headers = new StringKeyList(true); + public string Version = "HTTP/1.1"; + + public byte[] Message; + public ResponseCode Number; + public string Text; + //public DStringDictionary Cookies; + public List Cookies = new List(); + public bool Handled; + //public bool ResponseHandled; //flag this as true if you are handling it yourself + + //private bool createSession; + + //private HTTPServer Server; + //public HTTPSession Session; + + public override string ToString() + { + return "HTTPResponsePacket" + + "\n\tVersion: " + Version + //+ "\n\tMethod: " + Method + //+ "\n\tURL: " + URL + + "\n\tMessage: " + (Message != null ? Message.Length.ToString() : "NULL"); + } + + private string MakeHeader(ComposeOptions Options) + { + string header = Version + " " + (int)Number + " " + Text + "\r\n" + + "Server: Delta Web Server\r\n" + //Fri, 30 Oct 2007 14:19:41 GMT" + + "Date: " + DateTime.Now.ToUniversalTime().ToString("r") + "\r\n"; + + + if (Options == ComposeOptions.AllCalculateLength && Message != null) + { + Headers["Content-Length"] = Message.Length.ToString(); + } + + foreach (var kv in Headers) + { + header += kv.Key + ": " + kv.Value + "\r\n"; + } + + // Set-Cookie: ckGeneric=CookieBody; expires=Sun, 30-Dec-2007 21:00:00 GMT; path=/ + // Set-Cookie: ASPSESSIONIDQABBDSQA=IPDPMMMALDGFLMICEJIOCIPM; path=/ + + foreach (var Cookie in Cookies) + { + header += "Set-Cookie: " + Cookie.ToString() + "\r\n"; + } + + + header += "\r\n"; + + return header; + } + + + public bool Compose(ComposeOptions options) + { + List msg = new List(); + + if (options != ComposeOptions.DataOnly) + { + msg.AddRange(Encoding.UTF8.GetBytes(MakeHeader(options))); + } + + if (options != ComposeOptions.SpecifiedHeadersOnly) + { + if (Message != null) + msg.AddRange(Message); + } + + Data = msg.ToArray(); + + return true; + } + + public override bool Compose() + { + return Compose(ComposeOptions.AllDontCalculateLength); + } + + public override long Parse(byte[] data, uint offset, uint ends) + { + string[] sMethod = null; + string[] sLines = null; + + uint headerSize = 0; + + for (uint i = offset; i < ends - 3; i++) + { + if (data[i] == '\r' && data[i + 1] == '\n' + && data[i + 2] == '\r' && data[i + 3] == '\n') + { + sLines = Encoding.ASCII.GetString(data, (int)offset, (int)(i - offset)).Split(new string[] { "\r\n" }, + StringSplitOptions.None); + + headerSize = i + 4; + break; + } + } + + if (headerSize == 0) + return -1; + + //Cookies = new DStringDictionary(); + //Headers = new DStringDictionary(true); + + sMethod = sLines[0].Split(' '); + + if (sMethod.Length == 3) + { + Version = sMethod[0].Trim(); + Number = (ResponseCode)(Convert.ToInt32(sMethod[1].Trim())); + Text = sMethod[2]; + } + + // Read all headers + + for (int i = 1; i < sLines.Length; i++) + { + if (sLines[i] == String.Empty) + { + // Invalid header + return 0; + } + + if (sLines[i].IndexOf(':') == -1) + { + // Invalid header + return 0; + } + + string[] header = sLines[i].Split(new char[] { ':' }, 2); + + header[0] = header[0].ToLower(); + Headers[header[0]] = header[1].Trim(); + + //Set-Cookie: NAME=VALUE; expires=DATE; + + if (header[0] == "set-cookie") + { + string[] cookie = header[1].Split(';'); + + if (cookie.Length >= 1) + { + string[] splitCookie = cookie[0].Split('='); + HTTPCookie c = new HTTPCookie(splitCookie[0], splitCookie[1]); + + for (int j = 1; j < cookie.Length; j++) + { + splitCookie = cookie[j].Split('='); + switch (splitCookie[0].ToLower()) + { + case "domain": + c.Domain = splitCookie[1]; + break; + case "path": + c.Path = splitCookie[1]; + break; + case "httponly": + c.HttpOnly = true; + break; + case "expires": + // Wed, 13-Jan-2021 22:23:01 GMT + c.Expires = DateTime.Parse(splitCookie[1]); + break; + } + } + + } + + } + } + + // Content-Length + + try + { + + uint contentLength = uint.Parse((string)Headers["content-length"]); + + // check limit + if (contentLength > data.Length - headerSize) + { + return contentLength - (data.Length - headerSize); + } + + Message = DC.Clip(data, offset, contentLength); + + return headerSize + contentLength; + + } + catch + { + return 0; + } + } + } +} diff --git a/Esiur/Net/Packets/IIPAuthPacket.cs b/Esiur/Net/Packets/IIPAuthPacket.cs new file mode 100644 index 0000000..171e85a --- /dev/null +++ b/Esiur/Net/Packets/IIPAuthPacket.cs @@ -0,0 +1,382 @@ + using Esiur.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Net.Packets +{ + class IIPAuthPacket : Packet + { + public enum IIPAuthPacketCommand: byte + { + Action = 0, + Declare, + Acknowledge, + Error, + } + + public enum IIPAuthPacketAction: byte + { + // Authenticate + AuthenticateHash, + + + //Challenge, + //CertificateRequest, + //CertificateReply, + //EstablishRequest, + //EstablishReply + + NewConnection = 0x20, + ResumeConnection, + + ConnectionEstablished = 0x28 + } + + + public enum IIPAuthPacketMethod: byte + { + None, + Certificate, + Credentials, + Token + } + + + public IIPAuthPacketCommand Command + { + get; + set; + } + public IIPAuthPacketAction Action + { + get; + set; + } + + public byte ErrorCode { get; set; } + public string ErrorMessage { get; set; } + + public IIPAuthPacketMethod LocalMethod + { + get; + set; + } + + public byte[] SourceInfo + { + get; + set; + } + + public byte[] Hash + { + get; + set; + } + + public byte[] SessionId + { + get; + set; + } + + public IIPAuthPacketMethod RemoteMethod + { + get; + set; + } + + public string Domain + { + get; + set; + } + + public long CertificateId + { + get;set; + } + + public string LocalUsername + { + get; + set; + } + + public string RemoteUsername + { + get; + set; + } + + public byte[] LocalPassword + { + get; + set; + } + public byte[] RemotePassword + { + get; + set; + } + + public byte[] LocalToken + { + get; + set; + } + + public byte[] RemoteToken + { + get; + set; + } + + public byte[] AsymetricEncryptionKey + { + get; + set; + } + + public byte[] LocalNonce + { + get; + set; + } + + public byte[] RemoteNonce + { + 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 = (IIPAuthPacketCommand)(data[offset] >> 6); + + if (Command == IIPAuthPacketCommand.Action) + { + Action = (IIPAuthPacketAction)(data[offset++] & 0x3f); + + if (Action == IIPAuthPacketAction.AuthenticateHash) + { + if (NotEnough(offset, ends, 32)) + return -dataLengthNeeded; + + Hash = data.Clip(offset, 32); + + //var hash = new byte[32]; + //Buffer.BlockCopy(data, (int)offset, hash, 0, 32); + //Hash = hash; + + offset += 32; + } + else if (Action == IIPAuthPacketAction.NewConnection) + { + if (NotEnough(offset, ends, 2)) + return -dataLengthNeeded; + + var length = data.GetUInt16(offset); + + offset += 2; + + if (NotEnough(offset, ends, length)) + return -dataLengthNeeded; + + SourceInfo = data.Clip(offset, length); + + //var sourceInfo = new byte[length]; + //Buffer.BlockCopy(data, (int)offset, sourceInfo, 0, length); + //SourceInfo = sourceInfo; + + offset += 32; + } + else if (Action == IIPAuthPacketAction.ResumeConnection + || Action == IIPAuthPacketAction.ConnectionEstablished) + { + //var sessionId = new byte[32]; + + if (NotEnough(offset, ends, 32)) + return -dataLengthNeeded; + + SessionId = data.Clip(offset, 32); + + //Buffer.BlockCopy(data, (int)offset, sessionId, 0, 32); + //SessionId = sessionId; + + offset += 32; + } + } + else if (Command == IIPAuthPacketCommand.Declare) + { + RemoteMethod = (IIPAuthPacketMethod)((data[offset] >> 4) & 0x3); + LocalMethod = (IIPAuthPacketMethod)((data[offset] >> 2) & 0x3); + var encrypt = ((data[offset++] & 0x2) == 0x2); + + + if (NotEnough(offset, ends, 1)) + return -dataLengthNeeded; + + var domainLength = data[offset++]; + if (NotEnough(offset, ends, domainLength)) + return -dataLengthNeeded; + + var domain = data.GetString(offset, domainLength); + + Domain = domain; + + offset += domainLength; + + + if (RemoteMethod == IIPAuthPacketMethod.Credentials) + { + if (LocalMethod == IIPAuthPacketMethod.None) + { + if (NotEnough(offset, ends, 33)) + return -dataLengthNeeded; + + //var remoteNonce = new byte[32]; + //Buffer.BlockCopy(data, (int)offset, remoteNonce, 0, 32); + //RemoteNonce = remoteNonce; + + RemoteNonce = data.Clip(offset, 32); + + offset += 32; + + var length = data[offset++]; + + if (NotEnough(offset, ends, length)) + return -dataLengthNeeded; + + RemoteUsername = data.GetString(offset, length); + + + offset += length; + } + } + + if (encrypt) + { + if (NotEnough(offset, ends, 2)) + return -dataLengthNeeded; + + var keyLength = data.GetUInt16(offset); + + offset += 2; + + if (NotEnough(offset, ends, keyLength)) + return -dataLengthNeeded; + + //var key = new byte[keyLength]; + //Buffer.BlockCopy(data, (int)offset, key, 0, keyLength); + //AsymetricEncryptionKey = key; + + AsymetricEncryptionKey = data.Clip(offset, keyLength); + + offset += keyLength; + } + } + else if (Command == IIPAuthPacketCommand.Acknowledge) + { + RemoteMethod = (IIPAuthPacketMethod)((data[offset] >> 4) & 0x3); + LocalMethod = (IIPAuthPacketMethod)((data[offset] >> 2) & 0x3); + var encrypt = ((data[offset++] & 0x2) == 0x2); + + if (NotEnough(offset, ends, 1)) + return -dataLengthNeeded; + + + if (RemoteMethod == IIPAuthPacketMethod.Credentials) + { + if (LocalMethod == IIPAuthPacketMethod.None) + { + if (NotEnough(offset, ends, 32)) + return -dataLengthNeeded; + + /* + var remoteNonce = new byte[32]; + Buffer.BlockCopy(data, (int)offset, remoteNonce, 0, 32); + RemoteNonce = remoteNonce; + */ + + RemoteNonce = data.Clip(offset, 32); + offset += 32; + + } + } + + if (encrypt) + { + if (NotEnough(offset, ends, 2)) + return -dataLengthNeeded; + + var keyLength = data.GetUInt16(offset); + + offset += 2; + + if (NotEnough(offset, ends, keyLength)) + return -dataLengthNeeded; + + //var key = new byte[keyLength]; + //Buffer.BlockCopy(data, (int)offset, key, 0, keyLength); + //AsymetricEncryptionKey = key; + + AsymetricEncryptionKey = data.Clip(offset, keyLength); + + offset += keyLength; + } + } + else if (Command == IIPAuthPacketCommand.Error) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + offset++; + ErrorCode = data[offset++]; + + + var cl = data.GetUInt16(offset); + offset += 2; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + ErrorMessage = data.GetString(offset, cl); + offset += cl; + + } + + + return offset - oOffset; + + } + + } + } diff --git a/Esiur/Net/Packets/IIPPacket.cs b/Esiur/Net/Packets/IIPPacket.cs new file mode 100644 index 0000000..d3a33f9 --- /dev/null +++ b/Esiur/Net/Packets/IIPPacket.cs @@ -0,0 +1,550 @@ + using Esiur.Data; +using Esiur.Engine; +using Esiur.Misc; +using Esiur.Net.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Net.Packets +{ + class IIPPacket : Packet + { + public enum IIPPacketCommand : byte + { + Event = 0, + Request, + Reply, + Error, + } + + public enum IIPPacketEvent: byte + { + // Event Manage + ResourceReassigned = 0, + ResourceDestroyed, + + // Event Invoke + PropertyUpdated = 0x10, + EventOccured, + } + + public enum IIPPacketAction : byte + { + // Request Manage + AttachResource = 0, + ReattachResource, + DetachResource, + CreateResource, + DeleteResource, + + // Request Inquire + TemplateFromClassName = 0x8, + TemplateFromClassId, + TemplateFromResourceLink, + TemplateFromResourceId, + ResourceIdFromResourceLink, + + // Request Invoke + InvokeFunction = 0x10, + GetProperty, + GetPropertyIfModified, + SetProperty, + } + + + public IIPPacketCommand Command + { + get; + set; + } + public IIPPacketAction Action + { + get; + set; + } + + public IIPPacketEvent Event + { + get; + set; + } + + + public uint ResourceId { get; set; } + public uint NewResourceId { get; set; } + + public uint ResourceAge { get; set; } + public byte[] Content { get; set; } + public byte ErrorCode { get; set; } + public string ErrorMessage { get; set; } + public string ClassName { get; set; } + public string ResourceLink { get; set; } + public Guid ClassId { get; set; } + public byte MethodIndex { get; set; } + public string MethodName { get; set; } + public uint CallbackId { get; set; } + + private uint dataLengthNeeded; + + public override bool Compose() + { + return base.Compose(); + } + + bool NotEnough(uint offset, uint ends, uint needed) + { + if (offset + needed > ends) + { + dataLengthNeeded = needed - (ends - offset); + return true; + } + else + return false; + } + + public override long Parse(byte[] data, uint offset, uint ends) + { + var oOffset = offset; + + if (NotEnough(offset, ends, 1)) + return -dataLengthNeeded; + + Command = (IIPPacketCommand)(data[offset] >> 6); + + if (Command == IIPPacketCommand.Event) + { + Event = (IIPPacketEvent)(data[offset++] & 0x3f); + + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + } + else + { + Action = (IIPPacketAction)(data[offset++] & 0x3f); + + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + CallbackId = data.GetUInt32(offset); + offset += 4; + } + + if (Command == IIPPacketCommand.Event) + { + if (Event == IIPPacketEvent.ResourceReassigned) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + NewResourceId = data.GetUInt32( offset); + offset += 4; + + } + else if (Event == IIPPacketEvent.ResourceDestroyed) + { + // nothing to parse + } + else if (Event == IIPPacketEvent.PropertyUpdated) + { + if (NotEnough(offset, ends, 2)) + return -dataLengthNeeded; + + MethodIndex = data[offset++]; + + var dt = (DataType)data[offset++]; + var size = dt.Size();// Codec.SizeOf(dt); + + if (size < 0) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + var cl = data.GetUInt32( offset); + offset += 4; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + Content = data.Clip( offset - 5, cl + 5); + offset += cl; + } + else + { + if (NotEnough(offset, ends, (uint)size)) + return -dataLengthNeeded; + + Content = data.Clip(offset - 1, (uint)size + 1); + offset += (uint)size; + } + } + else if (Event == IIPPacketEvent.EventOccured) + { + if (NotEnough(offset, ends, 5)) + return -dataLengthNeeded; + + MethodIndex = data[offset++]; + + var cl = data.GetUInt32( offset); + offset += 4; + + Content = data.Clip( offset, cl); + offset += cl; + + } + } + else if (Command == IIPPacketCommand.Request) + { + if (Action == IIPPacketAction.AttachResource) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + } + else if (Action == IIPPacketAction.ReattachResource) + { + if (NotEnough(offset, ends, 8)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + ResourceAge = data.GetUInt32(offset); + offset += 4; + + } + else if (Action == IIPPacketAction.DetachResource) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + } + else if (Action == IIPPacketAction.CreateResource) + { + if (NotEnough(offset, ends, 1)) + return -dataLengthNeeded; + + var cl = data[offset++]; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + ClassName = data.GetString(offset, cl); + offset += cl; + } + else if (Action == IIPPacketAction.DeleteResource) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + } + else if (Action == IIPPacketAction.TemplateFromClassName) + { + if (NotEnough(offset, ends, 1)) + return -dataLengthNeeded; + + var cl = data[offset++]; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + ClassName = data.GetString(offset, cl); + offset += cl; + + } + else if (Action == IIPPacketAction.TemplateFromClassId) + { + if (NotEnough(offset, ends, 16)) + return -dataLengthNeeded; + + ClassId = data.GetGuid(offset); + offset += 16; + + } + else if (Action == IIPPacketAction.TemplateFromResourceLink) + { + if (NotEnough(offset, ends, 2)) + return -dataLengthNeeded; + + var cl = data.GetUInt16(offset); + offset += 2; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + ResourceLink = data.GetString(offset, cl); + offset += cl; + } + else if (Action == IIPPacketAction.TemplateFromResourceId) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + } + else if (Action == IIPPacketAction.ResourceIdFromResourceLink) + { + if (NotEnough(offset, ends, 2)) + return -dataLengthNeeded; + + var cl = data.GetUInt16(offset); + offset += 2; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + ResourceLink = data.GetString(offset, cl); + offset += cl; + } + else if (Action == IIPPacketAction.InvokeFunction) + { + if (NotEnough(offset, ends, 9)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + MethodIndex = data[offset++]; + + var cl = data.GetUInt32(offset); + offset += 4; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + Content = data.Clip(offset, cl); + offset += cl; + + } + else if (Action == IIPPacketAction.GetProperty) + { + if (NotEnough(offset, ends, 5)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + MethodIndex = data[offset++]; + + } + else if (Action == IIPPacketAction.GetPropertyIfModified) + { + if (NotEnough(offset, ends, 9)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + MethodIndex = data[offset++]; + + ResourceAge = data.GetUInt32(offset); + offset += 4; + + } + else if (Action == IIPPacketAction.SetProperty) + { + if (NotEnough(offset, ends, 6)) + return -dataLengthNeeded; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + MethodIndex = data[offset++]; + + + var dt = (DataType)data[offset++]; + var size = dt.Size();// Codec.SizeOf(dt); + + if (size < 0) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + var cl = data.GetUInt32(offset); + offset += 4; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + Content = data.Clip(offset-5, cl + 5); + offset += cl; + } + else + { + if (NotEnough(offset, ends, (uint)size)) + return -dataLengthNeeded; + + Content = data.Clip(offset-1, (uint)size + 1); + offset += (uint)size; + } + } + } + else if (Command == IIPPacketCommand.Reply) + { + if (Action == IIPPacketAction.AttachResource + || Action == IIPPacketAction.ReattachResource) + { + + if (NotEnough(offset, ends, 26)) + return -dataLengthNeeded; + + ClassId = data.GetGuid(offset); + offset += 16; + + ResourceAge = data.GetUInt32(offset); + offset += 4; + + uint cl = data.GetUInt16(offset); + offset += 2; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + ResourceLink = data.GetString(offset, cl); + offset += cl; + + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + cl = data.GetUInt32(offset); + offset += 4; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + Content = data.Clip(offset, cl); + offset += cl; + } + else if (Action == IIPPacketAction.DetachResource) + { + // nothing to do + } + else if (Action == IIPPacketAction.CreateResource) + { + if (NotEnough(offset, ends, 20)) + return -dataLengthNeeded; + + ClassId = data.GetGuid(offset); + offset += 16; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + } + else if (Action == IIPPacketAction.DetachResource) + { + // nothing to do + } + else if (Action == IIPPacketAction.TemplateFromClassName + || Action == IIPPacketAction.TemplateFromClassId + || Action == IIPPacketAction.TemplateFromResourceLink + || Action == IIPPacketAction.TemplateFromResourceId) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + var cl = data.GetUInt32(offset); + offset += 4; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + Content = data.Clip(offset, cl); + offset += cl; + } + else if (Action == IIPPacketAction.ResourceIdFromResourceLink) + { + if (NotEnough(offset, ends, 24)) + return -dataLengthNeeded; + + ClassId = data.GetGuid(offset); + offset += 16; + + ResourceId = data.GetUInt32(offset); + offset += 4; + + ResourceAge = data.GetUInt32(offset); + offset += 4; + } + else if (Action == IIPPacketAction.InvokeFunction + || Action == IIPPacketAction.GetProperty + || Action == IIPPacketAction.GetPropertyIfModified) + { + if (NotEnough(offset, ends, 1)) + return -dataLengthNeeded; + + var dt = (DataType)data[offset++]; + var size = dt.Size();// Codec.SizeOf(dt); + + if (size < 0) + { + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + var cl = data.GetUInt32(offset); + offset += 4; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + Content = data.Clip(offset - 5, cl + 5); + offset += cl; + } + else + { + if (NotEnough(offset, ends, (uint)size)) + return -dataLengthNeeded; + + Content = data.Clip(offset - 1, (uint)size + 1); + offset += (uint)size; + } + } + else if (Action == IIPPacketAction.SetProperty) + { + // nothing to do + } + } + else if (Command == IIPPacketCommand.Error) + { + // Error + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + CallbackId = data.GetUInt32(offset); + + if (NotEnough(offset, ends, 1)) + return -dataLengthNeeded; + + ErrorCode = data[offset++]; + + if (NotEnough(offset, ends, 4)) + return -dataLengthNeeded; + + var cl = data.GetUInt32(offset); + offset += 4; + + if (NotEnough(offset, ends, cl)) + return -dataLengthNeeded; + + ErrorMessage = data.GetString(offset, cl); + offset += cl; + } + + return offset - oOffset; + } + } +} diff --git a/Esiur/Net/Packets/Packet.cs b/Esiur/Net/Packets/Packet.cs new file mode 100644 index 0000000..b8a0163 --- /dev/null +++ b/Esiur/Net/Packets/Packet.cs @@ -0,0 +1,367 @@ + +/******************************************************************************\ +* Uruky Sniffer Project * +* * +* Copyright (C) 2006 Ahmed Khalaf - ahmed@uruky.com * +* ahmed_baghdad@yahoo.com * +* http://www.uruky.com * +* http://www.dijlh.com * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2, or (at your option) * +* any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software * +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * +* * +* File: Packet.cs * +* Description: Ethernet/ARP/IPv4/TCP/UDP Packet Decoding & Encoding Class * +* Compatibility: .Net Framework 2.0 / Mono 1.1.8 * +* * +\******************************************************************************/ + + + +using System; +using System.Text; +using Esiur.Misc; +using Esiur.Net.DataLink; +using System.Net.NetworkInformation; +using Esiur.Data; + +namespace Esiur.Net.Packets +{ + internal static class Functions + { + public static void AddData(ref byte[] dest, byte[] src) + { + int I = 0; + if (src == null) + { + return; + } + if (dest != null) + { + I = dest.Length; + Array.Resize(ref dest, dest.Length + src.Length); + //dest = (byte[])Resize(dest, dest.Length + src.Length); + } + else + { + dest = new byte[src.Length]; + } + Array.Copy(src, 0, dest, I, src.Length); + } + + /* + public static Array Resize(Array array, int newSize) + { + Type myType = Type.GetType(array.GetType().FullName.TrimEnd('[', ']')); + Array nA = Array.CreateInstance(myType, newSize); + Array.Copy(array, nA, (newSize > array.Length ? array.Length : newSize)); + return nA; + } */ + + //Computes the checksum used in IP, ARP..., ie the + // "The 16 bit one's complement of the one 's complement sum + //of all 16 bit words" as seen in RFCs + // Returns a 4 characters hex string + // data's lenght must be multiple of 4, else zero padding + public static ushort IP_CRC16(byte[] data) + { + ulong Sum = 0; + bool Padding = false; + /// * Padding if needed + if (data.Length % 2 != 0) + { + Array.Resize(ref data, data.Length + 1); + //data = (byte[])Resize(data, data.Length + 1); + Padding = true; + } + int count = data.Length; + ///* add 16-bit words */ + while (count > 0) //1) + { + ///* this is the inner loop */ + Sum += GetInteger(data[count - 2], data[count - 1]); + ///* Fold 32-bit sum to 16-bit */ + while (Sum >> 16 != 0) + { + Sum = (Sum & 0XFFFF) + (Sum >> 16); + } + count -= 2; + } + /// * reverse padding + if (Padding) + { + Array.Resize(ref data, data.Length - 1); + //data = (byte[])Resize(data, data.Length - 1); + } + ///* Return one's compliment of final sum. + //return (ushort)(ushort.MaxValue - (ushort)Sum); + return (ushort)(~Sum); + } + + public static ushort GetInteger(byte B1, byte B2) + { + return BitConverter.ToUInt16(new byte[] { B2, B1 }, 0); + //return System.Convert.ToUInt16("&h" + GetHex(B1) + GetHex(B2)); + } + + public static uint GetLong(byte B1, byte B2, byte B3, byte B4) + { + return BitConverter.ToUInt32(new byte[] { B4, B3, B2, B1 }, 0); + //return System.Convert.ToUInt32("&h" + GetHex(B1) + GetHex(B2) + GetHex(B3) + GetHex(B4)); + } + + public static string GetHex(byte B) + { + return (((B < 15) ? 0 + System.Convert.ToString(B, 16).ToUpper() : System.Convert.ToString(B, 16).ToUpper())); + } + + public static bool GetBit(uint B, byte Pos) + { + //return BitConverter.ToBoolean(BitConverter.GetBytes(B), Pos + 1); + return (B & (uint)(Math.Pow(2, (Pos - 1)))) == (Math.Pow(2, (Pos - 1))); + } + + public static ushort RemoveBit(ushort I, byte Pos) + { + return (ushort)RemoveBit((uint)I, Pos); + } + + public static uint RemoveBit(uint I, byte Pos) + { + if (GetBit(I, Pos)) + { + return I - (uint)(Math.Pow(2, (Pos - 1))); + } + else + { + return I; + } + } + + public static void SplitInteger(ushort I, ref byte BLeft, ref byte BRight) + { + byte[] b = BitConverter.GetBytes(I); + BLeft = b[1]; + BRight = b[0]; + //BLeft = I >> 8; + //BRight = (I << 8) >> 8; + } + + public static void SplitLong(uint I, ref byte BLeft, ref byte BLeftMiddle, ref byte BRightMiddle, ref byte BRight) + { + byte[] b = BitConverter.GetBytes(I); + BLeft = b[3]; + BLeftMiddle = b[2]; + BRightMiddle = b[1]; + BRight = b[0]; + //BLeft = I >> 24; + //BLeftMiddle = (I << 8) >> 24; + //BRightMiddle = (I << 16) >> 24; + //BRight = (I << 24) >> 24; + } + + } + + public class PosixTime + { + ulong seconds; + ulong microseconds; + + PosixTime(ulong Seconds, ulong Microseconds) + { + seconds = Seconds; + microseconds = Microseconds; + } + + public override string ToString() + { + return seconds + "." + microseconds; + } + } + + public class Packet + { + //public EtherServer2.EthernetSource Source; + + public PacketSource Source; + + public DateTime Timestamp; + + public enum PPPType : ushort + { + IP = 0x0021, // Internet Protocol version 4 [RFC1332] + SDTP = 0x0049, // Serial Data Transport Protocol (PPP-SDTP) [RFC1963] + IPv6HeaderCompression = 0x004f, // IPv6 Header Compression + IPv6 = 0x0057, // Internet Protocol version 6 [RFC5072] + W8021dHelloPacket = 0x0201, // 802.1d Hello Packets [RFC3518] + IPv6ControlProtocol = 0x8057, // IPv6 Control Protocol [RFC5072] + } + + public enum ProtocolType : ushort + { + IP = 0x800, // IPv4 + ARP = 0x806, // Address Resolution Protocol + IPv6 = 0x86DD, // IPv6 + FrameRelayARP = 0x0808, // Frame Relay ARP [RFC1701] + VINESLoopback = 0x0BAE, // VINES Loopback [RFC1701] + VINESEcho = 0x0BAF, // VINES ECHO [RFC1701] + TransEtherBridging = 0x6558, // TransEther Bridging [RFC1701] + RawFrameRelay = 0x6559, // Raw Frame Relay [RFC1701] + IEE8021QVLAN = 0x8100, // IEEE 802.1Q VLAN-tagged frames (initially Wellfleet) + SNMP = 0x814C, // SNMP [JKR1] + TCPIP_Compression = 0x876B, // TCP/IP Compression [RFC1144] + IPAutonomousSystems = 0x876C, // IP Autonomous Systems [RFC1701] + SecureData = 0x876D, // Secure Data [RFC1701] + PPP = 0x880B, // PPP [IANA] + MPLS = 0x8847, // MPLS [RFC5332] + MPLS_UpstreamAssignedLabel = 0x8848, // MPLS with upstream-assigned label [RFC5332] + PPPoEDiscoveryStage = 0x8863, // PPPoE Discovery Stage [RFC2516] + PPPoESessionStage = 0x8864, // PPPoE Session Stage [RFC2516] + } + + + /* + public static void GetPacketMACAddresses(Packet packet, out byte[] srcMAC, out byte[] dstMAC) + { + + // get the node address + Packet root = packet.RootPacket; + if (root is TZSPPacket) + { + + TZSPPacket tp = (TZSPPacket)root; + if (tp.Protocol == TZSPPacket.TZSPEncapsulatedProtocol.Ethernet) + { + EthernetPacket ep = (EthernetPacket)tp.SubPacket; + srcMAC = ep.SourceMAC; + dstMAC = ep.DestinationMAC; + } + else if (tp.Protocol == TZSPPacket.TZSPEncapsulatedProtocol.IEEE802_11) + { + W802_11Packet wp = (W802_11Packet)tp.SubPacket; + srcMAC = wp.SA; + dstMAC = wp.DA; + } + else + { + srcMAC = null; + dstMAC = null; + } + } + else if (root is EthernetPacket) + { + EthernetPacket ep = (EthernetPacket)root; + srcMAC = ep.SourceMAC; + dstMAC = ep.DestinationMAC; + } + else if (root is W802_11Packet) + { + W802_11Packet wp = (W802_11Packet)root; + srcMAC = wp.SA; + dstMAC = wp.DA; + } + else + { + srcMAC = null; + dstMAC = null; + } + + } + + + public static void GetPacketAddresses(Packet packet, ref string srcMAC, ref string dstMAC, ref string srcIP, ref string dstIP) + { + + if (packet is TCPv4Packet) + { + if (packet.ParentPacket is IPv4Packet) + { + IPv4Packet ip = (IPv4Packet)packet.ParentPacket; + srcIP = ip.SourceIP.ToString(); + dstIP = ip.DestinationIP.ToString(); + } + } + + // get the node address + Packet root = packet.RootPacket; + if (root is TZSPPacket) + { + + TZSPPacket tp = (TZSPPacket)root; + if (tp.Protocol == TZSPPacket.TZSPEncapsulatedProtocol.Ethernet) + { + EthernetPacket ep = (EthernetPacket)tp.SubPacket; + srcMAC = DC.GetPhysicalAddress(ep.SourceMAC, 0).ToString(); + dstMAC = DC.GetPhysicalAddress(ep.DestinationMAC, 0).ToString(); + } + else if (tp.Protocol == TZSPPacket.TZSPEncapsulatedProtocol.IEEE802_11) + { + W802_11Packet wp = (W802_11Packet)tp.SubPacket; + srcMAC = DC.GetPhysicalAddress(wp.SA, 0).ToString(); + dstMAC = DC.GetPhysicalAddress(wp.DA, 0).ToString(); + } + } + else if (root is EthernetPacket) + { + EthernetPacket ep = (EthernetPacket)root; + srcMAC = DC.GetPhysicalAddress(ep.SourceMAC, 0).ToString(); + dstMAC = DC.GetPhysicalAddress(ep.DestinationMAC, 0).ToString(); + } + else if (root is W802_11Packet) + { + W802_11Packet wp = (W802_11Packet)root; + srcMAC = DC.GetPhysicalAddress(wp.SA, 0).ToString(); + dstMAC = DC.GetPhysicalAddress(wp.DA, 0).ToString(); + } + } + */ + + PosixTime Timeval; + public byte[] Header; + public byte[] Preamble; + //public byte[] Payload; + public byte[] Data; + + public Packet SubPacket; + public Packet ParentPacket; + public virtual long Parse(byte[] data, uint offset, uint ends) { return 0; } + public virtual bool Compose() { return false; } + + public Packet RootPacket + { + get + { + Packet root = this; + while (root.ParentPacket != null) + root = root.ParentPacket; + return root; + } + } + + public Packet LeafPacket + { + get + { + Packet leaf = this; + while (leaf.SubPacket != null) + leaf = leaf.SubPacket; + return leaf; + } + } + } + +} +/************************************ EOF *************************************/ + + diff --git a/Esiur/Net/Packets/WebsocketPacket.cs b/Esiur/Net/Packets/WebsocketPacket.cs new file mode 100644 index 0000000..5eb28d8 --- /dev/null +++ b/Esiur/Net/Packets/WebsocketPacket.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Misc; +using Esiur.Data; + +namespace Esiur.Net.Packets +{ + public class WebsocketPacket : Packet + { + public enum WSOpcode : byte + { + ContinuationFrame = 0x0, // %x0 denotes a continuation frame + + TextFrame = 0x1, // %x1 denotes a text frame + + BinaryFrame = 0x2, // %x2 denotes a binary frame + + // %x3-7 are reserved for further non-control frames + + ConnectionClose = 0x8, // %x8 denotes a connection close + + Ping = 0x9, // %x9 denotes a ping + + Pong = 0xA, // %xA denotes a pong + + //* %xB-F are reserved for further control frames + } + + + public bool FIN; + public bool RSV1; + public bool RSV2; + public bool RSV3; + public WSOpcode Opcode; + public bool Mask; + public long PayloadLength; + // public UInt32 MaskKey; + public byte[] MaskKey; + + public byte[] Message; + + public override string ToString() + { + return "WebsocketPacket" + + "\n\tFIN: " + FIN + + "\n\tOpcode: " + Opcode + + "\n\tPayload: " + PayloadLength + + "\n\tMaskKey: " + MaskKey + + "\n\tMessage: " + (Message != null ? Message.Length.ToString() : "NULL"); + } + + public override bool Compose() + { + var pkt = new List(); + pkt.Add((byte)((FIN ? 0x80 : 0x0) | + (RSV1 ? 0x40 : 0x0) | + (RSV2 ? 0x20 : 0x0) | + (RSV3 ? 0x10 : 0x0) | + (byte)Opcode)); + + // calculate length + if (Message.Length > UInt16.MaxValue) + // 4 bytes + { + pkt.Add((byte)((Mask ? 0x80 : 0x0) | 127)); + pkt.AddRange(DC.ToBytes((UInt64)Message.LongCount())); + } + else if (Message.Length > 125) + // 2 bytes + { + pkt.Add((byte)((Mask ? 0x80 : 0x0) | 126)); + pkt.AddRange(DC.ToBytes((UInt16)Message.Length)); + } + else + { + pkt.Add((byte)((Mask ? 0x80 : 0x0) | Message.Length)); + } + + if (Mask) + { + pkt.AddRange(MaskKey); + } + + pkt.AddRange(Message); + + Data = pkt.ToArray(); + + return true; + } + + public override long Parse(byte[] data, uint offset, uint ends) + { + try + { + long needed = 2; + var length = (ends - offset); + if (length < needed) + { + //Console.WriteLine("stage 1 " + needed); + return length - needed; + } + + uint oOffset = offset; + FIN = ((data[offset] & 0x80) == 0x80); + RSV1 = ((data[offset] & 0x40) == 0x40); + RSV2 = ((data[offset] & 0x20) == 0x20); + RSV3 = ((data[offset] & 0x10) == 0x10); + Opcode = (WSOpcode)(data[offset++] & 0xF); + Mask = ((data[offset] & 0x80) == 0x80); + PayloadLength = (long)(data[offset++] & 0x7F); + + if (Mask) + needed += 4; + + if (PayloadLength == 126) + { + needed += 2; + if (length < needed) + { + //Console.WriteLine("stage 2 " + needed); + return length - needed; + } + PayloadLength = DC.GetUInt16(data, offset); + offset += 2; + } + else if (PayloadLength == 127) + { + needed += 8; + if (length < needed) + { + //Console.WriteLine("stage 3 " + needed); + return length - needed; + } + + PayloadLength = DC.GetInt64(data, offset); + offset += 8; + } + + if (Mask) + { + MaskKey = new byte[4]; + MaskKey[0] = data[offset++]; + MaskKey[1] = data[offset++]; + MaskKey[2] = data[offset++]; + MaskKey[3] = data[offset++]; + + //MaskKey = DC.GetUInt32(data, offset); + //offset += 4; + } + + needed += PayloadLength; + if (length < needed) + { + //Console.WriteLine("stage 4"); + return length - needed; + } + + // if ((int)PayloadLength > (ends - offset)) + // { + // return -((int)PayloadLength - (ends - offset)); + // } + else + { + Message = DC.Clip(data, offset, (uint)PayloadLength); + + if (Mask) + { + //var aMask = BitConverter.GetBytes(MaskKey); + for (int i = 0; i < Message.Length; i++) + { + Message[i] = (byte)(Message[i] ^ MaskKey[i % 4]); + } + } + + return (offset - oOffset) + (int)PayloadLength; + } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + Console.WriteLine(offset + "::" + DC.ToHex(data)); + throw ex; + } + } + } +} diff --git a/Esiur/Net/Sockets/ISocket.cs b/Esiur/Net/Sockets/ISocket.cs new file mode 100644 index 0000000..8fb843a --- /dev/null +++ b/Esiur/Net/Sockets/ISocket.cs @@ -0,0 +1,39 @@ +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.Data; +using Esiur.Misc; +using System.Collections.Concurrent; +using Esiur.Resource; +using Esiur.Engine; + +namespace Esiur.Net.Sockets +{ + public delegate void ISocketReceiveEvent(NetworkBuffer buffer); + public delegate void ISocketConnectEvent(); + public delegate void ISocketCloseEvent(); + + public interface ISocket: IDestructible + { + SocketState State { get; } + + event ISocketReceiveEvent OnReceive; + event ISocketConnectEvent OnConnect; + event ISocketCloseEvent OnClose; + + void Send(byte[] message); + void Send(byte[] message, int offset, int size); + void Close(); + bool Connect(string hostname, ushort port); + bool Begin(); + //ISocket Accept(); + AsyncReply Accept(); + IPEndPoint RemoteEndPoint { get; } + IPEndPoint LocalEndPoint { get; } + } +} diff --git a/Esiur/Net/Sockets/SSLSocket.cs b/Esiur/Net/Sockets/SSLSocket.cs new file mode 100644 index 0000000..ef1e897 --- /dev/null +++ b/Esiur/Net/Sockets/SSLSocket.cs @@ -0,0 +1,310 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; +using Esiur.Misc; +using Esiur.Engine; +using System.Threading; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Esiur.Resource; +using System.Threading.Tasks; +using Esiur.Data; + +namespace Esiur.Net.Sockets +{ + public class SSLSocket : ISocket + { + Socket sock; + byte[] receiveBuffer; + + NetworkBuffer receiveNetworkBuffer = new NetworkBuffer(); + + object sendLock = new object(); + + Queue sendBufferQueue = new Queue(); + + bool asyncSending; + + + SocketState state = SocketState.Initial; + + public event ISocketReceiveEvent OnReceive; + public event ISocketConnectEvent OnConnect; + public event ISocketCloseEvent OnClose; + public event DestroyedEvent OnDestroy; + + SslStream ssl; + X509Certificate2 cert; + bool server; + string hostname; + + private void Connected(Task t) + { + if (server) + { + ssl.AuthenticateAsServerAsync(cert).ContinueWith(Authenticated); + } + else + { + ssl.AuthenticateAsClientAsync(hostname).ContinueWith(Authenticated); + } + } + + public bool Connect(string hostname, ushort port) + { + try + { + this.hostname = hostname; + server = false; + state = SocketState.Connecting; + sock.ConnectAsync(hostname, port).ContinueWith(Connected); + return true; + } + catch + { + return false; + } + } + + private void DataSent(Task task) + { + try + { + + if (sendBufferQueue.Count > 0) + { + byte[] data = sendBufferQueue.Dequeue(); + lock (sendLock) + ssl.WriteAsync(data, 0, data.Length).ContinueWith(DataSent); + } + else + { + asyncSending = false; + } + } + catch (Exception ex) + { + if (state != SocketState.Closed && !sock.Connected) + { + state = SocketState.Terminated; + Close(); + } + + asyncSending = false; + + Global.Log("SSLSocket", LogType.Error, ex.ToString()); + } + } + + + + public IPEndPoint LocalEndPoint + { + get { return (IPEndPoint)sock.LocalEndPoint; } + } + + public SSLSocket() + { + sock = new Socket(AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + receiveBuffer = new byte[sock.ReceiveBufferSize]; + } + + public SSLSocket(IPEndPoint localEndPoint, X509Certificate2 certificate) + { + // create the socket + sock = new Socket(AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + + state = SocketState.Listening; + + // bind + sock.Bind(localEndPoint); + + // start listening + sock.Listen(UInt16.MaxValue); + + cert = certificate; + } + + public IPEndPoint RemoteEndPoint + { + get { return (IPEndPoint)sock.RemoteEndPoint; } + } + + public SocketState State + { + get + { + return state; + } + } + + + + public SSLSocket(Socket Socket, X509Certificate2 certificate, bool authenticateAsServer) + { + cert = certificate; + sock = Socket; + receiveBuffer = new byte[sock.ReceiveBufferSize]; + + ssl = new SslStream(new NetworkStream(sock)); + + server = authenticateAsServer; + + } + + + public void Close() + { + if (state != SocketState.Closed && state != SocketState.Terminated) + state = SocketState.Closed; + + if (sock.Connected) + { + try + { + sock.Shutdown(SocketShutdown.Both); + } + catch + { + state = SocketState.Terminated; + } + } + + sock.Shutdown(SocketShutdown.Both); + + OnClose?.Invoke(); + } + + public void Send(byte[] message) + { + Send(message, 0, message.Length); + } + + public void Send(byte[] message, int offset, int size) + { + lock (sendLock) + { + if (asyncSending) + { + sendBufferQueue.Enqueue(message.Clip((uint)offset, (uint)size)); + } + else + { + asyncSending = true; + ssl.WriteAsync(message, offset, size).ContinueWith(DataSent); + } + } + } + + void Authenticated(Task task) + { + try + { + state = SocketState.Established; + OnConnect?.Invoke(); + + if (!server) + Begin(); + } + catch (Exception ex) + { + state = SocketState.Terminated; + Close(); + Global.Log(ex); + } + } + + private void DataReceived(Task task) + { + try + { + // SocketError err; + + if (state == SocketState.Closed || state == SocketState.Terminated) + return; + + if (task.Result <= 0) + { + Close(); + return; + } + + + receiveNetworkBuffer.Write(receiveBuffer, 0, (uint)task.Result); + OnReceive?.Invoke(receiveNetworkBuffer); + if (state == SocketState.Established) + ssl.ReadAsync(receiveBuffer, 0, receiveBuffer.Length).ContinueWith(DataReceived); + } + catch (Exception ex) + { + if (state != SocketState.Closed && !sock.Connected) + { + state = SocketState.Terminated; + Close(); + } + + Global.Log("SSLSocket", LogType.Error, ex.ToString()); + } + } + + public bool Begin() + { + if (state == SocketState.Established) + { + ssl.ReadAsync(receiveBuffer, 0, receiveBuffer.Length).ContinueWith(DataReceived); + return true; + } + else + return false; + } + + + + public bool Trigger(ResourceTrigger trigger) + { + return true; + } + + public void Destroy() + { + Close(); + OnDestroy?.Invoke(this); + } + + public AsyncReply Accept() + { + var reply = new AsyncReply(); + + try + { + sock.AcceptAsync().ContinueWith((x) => + { + try + { + reply.Trigger(new SSLSocket(x.Result, cert, true)); + } + catch + { + reply.Trigger(null); + } + + }, null); + + } + catch + { + state = SocketState.Terminated; + return null; + } + + return reply; + } + } +} \ No newline at end of file diff --git a/Esiur/Net/Sockets/SocketState.cs b/Esiur/Net/Sockets/SocketState.cs new file mode 100644 index 0000000..85e6a9f --- /dev/null +++ b/Esiur/Net/Sockets/SocketState.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Net.Sockets +{ + public enum SocketState + { + Initial, + Listening, + Connecting, + Established, + Closed, + Terminated + } +} diff --git a/Esiur/Net/Sockets/TCPSocket.cs b/Esiur/Net/Sockets/TCPSocket.cs new file mode 100644 index 0000000..c37b196 --- /dev/null +++ b/Esiur/Net/Sockets/TCPSocket.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; +using Esiur.Misc; +using Esiur.Engine; +using System.Threading; +using Esiur.Resource; +using System.Threading.Tasks; +using Esiur.Data; + +namespace Esiur.Net.Sockets +{ + public class TCPSocket : ISocket + { + Socket sock; + byte[] receiveBuffer; + + ArraySegment receiveBufferSegment; + + NetworkBuffer receiveNetworkBuffer = new NetworkBuffer(); + + object sendLock = new object(); + + Queue sendBufferQueue = new Queue(); + + bool asyncSending; + + + SocketState state = SocketState.Initial; + + public event ISocketReceiveEvent OnReceive; + public event ISocketConnectEvent OnConnect; + public event ISocketCloseEvent OnClose; + public event DestroyedEvent OnDestroy; + + + private void Connected(Task t) + { + state = SocketState.Established; + OnConnect?.Invoke(); + Begin(); + } + + public bool Begin() + { + sock.ReceiveAsync(receiveBufferSegment, SocketFlags.None).ContinueWith(DataReceived); + return true; + } + + public bool Connect(string hostname, ushort port) + { + try + { + state = SocketState.Connecting; + sock.ConnectAsync(hostname, port).ContinueWith(Connected); + return true; + } + catch + { + return false; + } + } + + + private void DataReceived(Task task) + { + try + { + // SocketError err; + + if (state == SocketState.Closed || state == SocketState.Terminated) + return; + + if (task.Result <= 0) + { + Close(); + return; + } + + + receiveNetworkBuffer.Write(receiveBuffer, 0, (uint)task.Result); + OnReceive?.Invoke(receiveNetworkBuffer); + if (state == SocketState.Established) + sock.ReceiveAsync(receiveBufferSegment, SocketFlags.None).ContinueWith(DataReceived); + } + catch (Exception ex) + { + if (state != SocketState.Closed && !sock.Connected) + { + state = SocketState.Terminated; + Close(); + } + + Global.Log("TCPSocket", LogType.Error, ex.ToString()); + } + } + + + public IPEndPoint LocalEndPoint + { + get { return (IPEndPoint)sock.LocalEndPoint; } + } + + public TCPSocket() + { + sock = new Socket(AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + receiveBuffer = new byte[sock.ReceiveBufferSize]; + receiveBufferSegment = new ArraySegment(receiveBuffer); + + } + + public TCPSocket(string hostname, ushort port) + { + // create the socket + sock = new Socket(AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + + receiveBuffer = new byte[sock.ReceiveBufferSize]; + receiveBufferSegment = new ArraySegment(receiveBuffer); + + Connect(hostname, port); + + } + + private void DataSent(Task task) + { + try + { + + if (sendBufferQueue.Count > 0) + { + byte[] data = sendBufferQueue.Dequeue(); + lock (sendLock) + sock.SendAsync(new ArraySegment(data), SocketFlags.None).ContinueWith(DataSent); + } + else + { + asyncSending = false; + } + } + catch (Exception ex) + { + if (state != SocketState.Closed && !sock.Connected) + { + state = SocketState.Terminated; + Close(); + } + + asyncSending = false; + + Global.Log("TCPSocket", LogType.Error, ex.ToString()); + } + } + + public TCPSocket(IPEndPoint localEndPoint) + { + // create the socket + sock = new Socket(AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + + receiveBuffer = new byte[sock.ReceiveBufferSize]; + + state = SocketState.Listening; + + + // bind + sock.Bind(localEndPoint); + + // start listening + sock.Listen(UInt16.MaxValue); + + + } + + + + + public IPEndPoint RemoteEndPoint + { + get { return (IPEndPoint)sock.RemoteEndPoint; } + } + + public SocketState State + { + get + { + return state; + } + } + + + public TCPSocket(Socket socket) + { + sock = socket; + receiveBuffer = new byte[sock.ReceiveBufferSize]; + receiveBufferSegment = new ArraySegment(receiveBuffer); + if (socket.Connected) + state = SocketState.Established; + } + + public void Close() + { + if (state != SocketState.Closed && state != SocketState.Terminated) + state = SocketState.Closed; + + if (sock.Connected) + { + try + { + sock.Shutdown(SocketShutdown.Both); + } + catch + { + state = SocketState.Terminated; + } + + sock.Shutdown(SocketShutdown.Both);// Close(); + OnClose?.Invoke(); + } + + } + + public void Send(byte[] message) + { + Send(message, 0, message.Length); + } + + public void Send(byte[] message, int offset, int size) + { + lock (sendLock) + { + if (asyncSending) + { + sendBufferQueue.Enqueue(message.Clip((uint)offset, (uint)size)); + } + else + { + asyncSending = true; + sock.SendAsync(new ArraySegment(message, offset, size), SocketFlags.None).ContinueWith(DataSent); + } + } + } + + + + public bool Trigger(ResourceTrigger trigger) + { + return true; + } + + public void Destroy() + { + Close(); + OnDestroy?.Invoke(this); + } + + public AsyncReply Accept() + { + var reply = new AsyncReply(); + + try + { + sock.AcceptAsync().ContinueWith((x) => + { + try + { + reply.Trigger(new TCPSocket(x.Result)); + } + catch + { + reply.Trigger(null); + } + }); + } + catch + { + state = SocketState.Terminated; + return null; + } + + return reply; + } + } +} \ No newline at end of file diff --git a/Esiur/Net/Sockets/WSSocket.cs b/Esiur/Net/Sockets/WSSocket.cs new file mode 100644 index 0000000..6de470b --- /dev/null +++ b/Esiur/Net/Sockets/WSSocket.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; +using Esiur.Net.Packets; +using Esiur.Misc; +using System.IO; +using Esiur.Engine; +using Esiur.Resource; + +namespace Esiur.Net.Sockets +{ + public class WSSocket : ISocket + { + WebsocketPacket pkt_receive = new WebsocketPacket(); + WebsocketPacket pkt_send = new WebsocketPacket(); + + ISocket sock; + NetworkBuffer receiveNetworkBuffer = new NetworkBuffer(); + object sendLock = new object(); + + public event ISocketReceiveEvent OnReceive; + public event ISocketConnectEvent OnConnect; + public event ISocketCloseEvent OnClose; + public event DestroyedEvent OnDestroy; + + long totalSent, totalReceived; + + public IPEndPoint LocalEndPoint + { + get { return (IPEndPoint)sock.LocalEndPoint; } + } + + + + public IPEndPoint RemoteEndPoint + { + get { return sock.RemoteEndPoint; } + } + + + public SocketState State + { + get + { + return sock.State; + } + } + + + public WSSocket(ISocket socket) + { + pkt_send.FIN = true; + pkt_send.Mask = false; + pkt_send.Opcode = WebsocketPacket.WSOpcode.BinaryFrame; + sock = socket; + sock.OnClose += Sock_OnClose; + sock.OnConnect += Sock_OnConnect; + sock.OnReceive += Sock_OnReceive; + } + + private void Sock_OnReceive(NetworkBuffer buffer) + { + + if (sock.State == SocketState.Closed || sock.State == SocketState.Terminated) + return; + + if (buffer.Protected) + return; + + var msg = buffer.Read(); + + var wsPacketLength = pkt_receive.Parse(msg, 0, (uint)msg.Length); + + if (wsPacketLength < 0) + { + buffer.Protect(msg, 0, (uint)msg.Length + (uint)-wsPacketLength); + return; + } + + uint offset = 0; + + while (wsPacketLength > 0) + { + if (pkt_receive.Opcode == WebsocketPacket.WSOpcode.ConnectionClose) + { + Close(); + return; + } + else if (pkt_receive.Opcode == WebsocketPacket.WSOpcode.Ping) + { + var pkt_pong = new WebsocketPacket(); + + pkt_pong.FIN = true; + pkt_pong.Mask = false; + pkt_pong.Opcode = WebsocketPacket.WSOpcode.Pong; + pkt_pong.Message = pkt_receive.Message; + offset += (uint)wsPacketLength; + + Send(pkt_pong); + } + else if (pkt_receive.Opcode == WebsocketPacket.WSOpcode.Pong) + { + offset += (uint)wsPacketLength; + } + else if (pkt_receive.Opcode == WebsocketPacket.WSOpcode.BinaryFrame + || pkt_receive.Opcode == WebsocketPacket.WSOpcode.TextFrame + || pkt_receive.Opcode == WebsocketPacket.WSOpcode.ContinuationFrame) + { + totalReceived += pkt_receive.Message.Length; + //Console.WriteLine("RX " + pkt_receive.Message.Length + "/" + totalReceived);// + " " + DC.ToHex(message, 0, (uint)size)); + + receiveNetworkBuffer.Write(pkt_receive.Message); + offset += (uint)wsPacketLength; + } + else + Console.WriteLine("Unknown WS opcode:" + pkt_receive.Opcode); + + if (offset == msg.Length) + { + OnReceive?.Invoke(receiveNetworkBuffer); + return; + } + + wsPacketLength = pkt_receive.Parse(msg, offset, (uint)msg.Length); + } + + if (wsPacketLength < 0)//(offset < msg.Length) && (offset > 0)) + { + //receiveNetworkBuffer.HoldFor(msg, offset, (uint)(msg.Length - offset), (uint)msg.Length + (uint)-wsPacketLength); + // save the incomplete packet to the heldBuffer queue + + receiveNetworkBuffer.HoldFor(msg, offset, (uint)(msg.Length - offset), (uint)(msg.Length - offset) + (uint)-wsPacketLength); + + } + + OnReceive?.Invoke(receiveNetworkBuffer); + } + + private void Sock_OnConnect() + { + OnConnect?.Invoke(); + } + + private void Sock_OnClose() + { + OnClose?.Invoke(); + } + + public void Send(WebsocketPacket packet) + { + lock(sendLock) + if (packet.Compose()) + sock.Send(packet.Data); + } + + public void Send(byte[] message) + { + lock(sendLock) + { + totalSent += message.Length; + //Console.WriteLine("TX " + message.Length +"/"+totalSent);// + " " + DC.ToHex(message, 0, (uint)size)); + + pkt_send.Message = message; + if (pkt_send.Compose()) + sock.Send(pkt_send.Data); + } + } + + + public void Send(byte[] message, int offset, int size) + { + lock (sendLock) + { + totalSent += size; + //Console.WriteLine("TX " + size + "/"+totalSent);// + " " + DC.ToHex(message, 0, (uint)size)); + + pkt_send.Message = new byte[size]; + Buffer.BlockCopy(message, offset, pkt_send.Message, 0, size); + if (pkt_send.Compose()) + sock.Send(pkt_send.Data); + } + } + + + public void Close() + { + sock.Close(); + } + + public bool Connect(string hostname, ushort port) + { + throw new NotImplementedException(); + } + + + public bool Begin() + { + return sock.Begin(); + } + + public bool Trigger(ResourceTrigger trigger) + { + return true; + } + + public void Destroy() + { + Close(); + OnDestroy?.Invoke(this); + } + + public AsyncReply Accept() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Esiur/Net/TCP/TCPConnection.cs b/Esiur/Net/TCP/TCPConnection.cs new file mode 100644 index 0000000..f2d93c1 --- /dev/null +++ b/Esiur/Net/TCP/TCPConnection.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Net.Sockets; +using System.Net; +using System.Collections; +using Esiur.Misc; +using Esiur.Data; + +namespace Esiur.Net.TCP +{ + public class TCPConnection: NetworkConnection + { + + private KeyList variables = new KeyList(); + + + public KeyList Variables + { + get + { + return variables; + } + } + } +} diff --git a/Esiur/Net/TCP/TCPFilter.cs b/Esiur/Net/TCP/TCPFilter.cs new file mode 100644 index 0000000..82a3c80 --- /dev/null +++ b/Esiur/Net/TCP/TCPFilter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using Esiur.Data; +using Esiur.Net.Sockets; +using Esiur.Engine; +using Esiur.Resource; + +namespace Esiur.Net.TCP +{ + public abstract class TCPFilter: IResource + { + public Instance Instance + { + get; + set; + } + + public event DestroyedEvent OnDestroy; + + public abstract AsyncReply Trigger(ResourceTrigger trigger); + + public virtual bool Connected(TCPConnection sender) + { + return false; + } + + public virtual bool Disconnected(TCPConnection sender) + { + return false; + } + + public abstract bool Execute(byte[] msg, NetworkBuffer data, TCPConnection sender); + + public void Destroy() + { + throw new NotImplementedException(); + } + } +} diff --git a/Esiur/Net/TCP/TCPServer.cs b/Esiur/Net/TCP/TCPServer.cs new file mode 100644 index 0000000..af23844 --- /dev/null +++ b/Esiur/Net/TCP/TCPServer.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Net.Sockets; +using Esiur.Misc; +using System.Threading; +using Esiur.Data; +using Esiur.Engine; +using System.Net; +using Esiur.Resource; + +namespace Esiur.Net.TCP +{ + public class TCPServer : NetworkServer, IResource + { + + [Storable] + string ip + { + get; + set; + } + [Storable] + ushort port + { + get; + set; + } + [Storable] + uint timeout + { + get; + set; + } + [Storable] + uint clock + { + get; + set; + } + public Instance Instance { get; set; } + + public AsyncReply Trigger(ResourceTrigger trigger) + { + if (trigger == ResourceTrigger.Initialize) + { + TCPSocket listener; + + if (ip != null) + listener =new TCPSocket(new IPEndPoint(IPAddress.Parse(ip), port)); + else + listener = new TCPSocket(new IPEndPoint(IPAddress.Any, port)); + + Start(listener, timeout, clock); + } + else if (trigger == ResourceTrigger.Terminate) + { + Stop(); + } + else if (trigger == ResourceTrigger.SystemReload) + { + Trigger(ResourceTrigger.Terminate); + Trigger(ResourceTrigger.Initialize); + } + + return new AsyncReply(true); + } + + + + protected override void DataReceived(TCPConnection sender, NetworkBuffer data) + { + //throw new NotImplementedException(); + var msg = data.Read(); + + foreach (Instance instance in Instance.Children) + { + var f = instance.Resource as TCPFilter; + if (f.Execute(msg, data, sender)) + return; + } + } + + private void SessionModified(TCPConnection Session, string Key, object NewValue) + { + + } + + /* + public TCPServer(string IP, int Port, int Timeout, int Clock) + : base(IP, Port, Timeout, Clock) + { + if (Timeout > 0 && Clock > 0) + { + mTimer = new Timer(OnlineThread, null, 0, Clock * 1000);// TimeSpan.FromSeconds(Clock)); + mTimeout = Timeout; + } + } + */ + + /* + private void OnlineThread(object state) + { + List ToBeClosed = null; + //Console.WriteLine("Minute Thread"); + + if (Connections.Count > 0) + { + Global.Log("TCPServer:OnlineThread", LogType.Debug, + //"Tick:" + DateTime.Now.Subtract(Connections[0].LastAction).TotalSeconds + ":" + mTimeout + ":" + + "Tick | Connections: " + Connections.Count + " Threads:" + System.Diagnostics.Process.GetCurrentProcess().Threads.Count); + } + + + try + { + foreach (TCPConnection c in Connections)//.Values) + { + if (DateTime.Now.Subtract(c.LastAction).TotalSeconds >= mTimeout) + { + if (ToBeClosed == null) + ToBeClosed = new List(); + ToBeClosed.Add(c); + } + } + + if (ToBeClosed != null) + { + + Global.Log("TCPServer:OnlineThread", LogType.Debug, "Inactive Closed:" + ToBeClosed.Count); + + foreach (TCPConnection c in ToBeClosed) + c.Close(); + + ToBeClosed.Clear(); + ToBeClosed = null; + + + } + } + catch (Exception ex) + { + Global.Log("TCPServer:OnlineThread", LogType.Debug, ex.ToString()); + } + } + */ + + //~TCPServer() + //{ + // StopServer(); + //} + + + + + protected override void ClientConnected(TCPConnection sender) + { + //Console.WriteLine("TCP Client Connected"); + + // Global.Log("TCPServer", + // LogType.Debug, + // "Connected:" + Connections.Count + // + ":" + sender.RemoteEndPoint.ToString()); + + foreach (Instance instance in Instance.Children) + { + var f = instance.Resource as TCPFilter; + f.Connected(sender); + } + } + + protected override void ClientDisconnected(TCPConnection sender) + { + //Console.WriteLine("TCP Client Disconnected"); + + // Global.Log("TCPServer", LogType.Debug, "Disconnected:" + Connections.Count);// + ":" + sender.RemoteEndPoint.ToString()); + + foreach (Instance instance in Instance.Children) + { + try + { + var f = instance.Resource as TCPFilter; + f.Disconnected(sender); + } + catch(Exception ex) + { + Global.Log(ex); + } + } + } + + + } +} diff --git a/Esiur/Net/TCP/TCPSession.cs b/Esiur/Net/TCP/TCPSession.cs new file mode 100644 index 0000000..7bca931 --- /dev/null +++ b/Esiur/Net/TCP/TCPSession.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Net.TCP +{ + public class TCPSession : NetworkSession + { + + } +} diff --git a/Esiur/Net/UDP/UDPFilter.cs b/Esiur/Net/UDP/UDPFilter.cs new file mode 100644 index 0000000..06f6a67 --- /dev/null +++ b/Esiur/Net/UDP/UDPFilter.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using System.Net; +using Esiur.Data; +using Esiur.Engine; +using Esiur.Resource; + +namespace Esiur.Net.UDP +{ + public abstract class UDPFilter : IResource + { + public Instance Instance + { + get; + set; + } + + + public event DestroyedEvent OnDestroy; + + public abstract AsyncReply Trigger(ResourceTrigger trigger); + + public abstract bool Execute(byte[] data, IPEndPoint sender); + + public void Destroy() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Esiur/Net/UDP/UDPServer.cs b/Esiur/Net/UDP/UDPServer.cs new file mode 100644 index 0000000..ea461b9 --- /dev/null +++ b/Esiur/Net/UDP/UDPServer.cs @@ -0,0 +1,173 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Text; +using System.Collections; +using Esiur.Data; +using Esiur.Misc; +using Esiur.Engine; +using Esiur.Resource; + +namespace Esiur.Net.UDP +{ + + /* public class IIPConnection + { + public EndPoint SenderPoint; + public + }*/ + public class UDPServer : IResource + { + Thread Receiver; + UdpClient Udp; + + public event DestroyedEvent OnDestroy; + + public Instance Instance + { + get; + set; + } + + [Storable] + string ip + { + get; + set; + } + + [Storable] + ushort port + { + get; + set; + } + + public bool Trigger(ResourceTrigger trigger) + { + if (trigger == ResourceTrigger.Initialize) + { + var address = ip == null ? IPAddress.Any : IPAddress.Parse(ip); + + Udp = new UdpClient(new IPEndPoint(address, (int)port)); + Receiver = new Thread(Receiving); + Receiver.Start(); + } + else if (trigger == ResourceTrigger.Terminate) + { + if (Receiver != null) + Receiver.Abort(); + } + return true; + } + + private void Receiving() + { + IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0); + + + while (true) + { + byte[] b = Udp.Receive(ref ep); + + foreach(var child in Instance.Children) + { + var f = child as UDPFilter; + + try + { + if (f.Execute(b, ep)) + { + break; + } + } + catch (Exception ex) + { + Global.Log("UDPServer", LogType.Error, ex.ToString()); + //Console.WriteLine(ex.ToString()); + } + } + } + } + + public bool Send(byte[] Data, int Count, IPEndPoint EP) + { + try + { + Udp.Send(Data, Count, EP); + return true; + } + catch + { + return false; + } + } + public bool Send(byte[] Data, IPEndPoint EP) + { + try + { + Udp.Send(Data, Data.Length, EP); + return true; + } + catch + { + return false; + } + } + public bool Send(byte[] Data, int Count, string Host, int Port) + { + try + { + Udp.Send(Data, Count, Host, Port); + return true; + } + catch + { + return false; + } + } + public bool Send(byte[] Data, string Host, int Port) + { + try + { + Udp.Send(Data, Data.Length, Host, Port); + return true; + } + catch + { + return false; + } + } + public bool Send(string Data, IPEndPoint EP) + { + try + { + Udp.Send(Encoding.Default.GetBytes(Data), Data.Length, EP); + return true; + } + catch + { + return false; + } + } + public bool Send(string Data, string Host, int Port) + { + try + { + Udp.Send(Encoding.Default.GetBytes(Data), Data.Length, Host, Port); + return true; + } + catch + { + return false; + } + } + + public void Destroy() + { + Udp.Close(); + OnDestroy?.Invoke(this); + } + } +} \ No newline at end of file diff --git a/Esiur/Resource/IResource.cs b/Esiur/Resource/IResource.cs new file mode 100644 index 0000000..3ee0efa --- /dev/null +++ b/Esiur/Resource/IResource.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Esiur.Data; +using Esiur.Engine; + +namespace Esiur.Resource +{ + public delegate bool QueryFilter(T value); + + public interface IResource : IDestructible + { + + AsyncReply Trigger(ResourceTrigger trigger); + + Instance Instance + { + get; + set; + } + } +} diff --git a/Esiur/Resource/IStore.cs b/Esiur/Resource/IStore.cs new file mode 100644 index 0000000..894b1cb --- /dev/null +++ b/Esiur/Resource/IStore.cs @@ -0,0 +1,17 @@ +using Esiur.Engine; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource +{ + public interface IStore:IResource + { + AsyncReply Get(string path); + AsyncReply Retrieve(uint iid); + bool Put(IResource resource); + string Link(IResource resource); + } +} diff --git a/Esiur/Resource/Instance.cs b/Esiur/Resource/Instance.cs new file mode 100644 index 0000000..b6bfaa7 --- /dev/null +++ b/Esiur/Resource/Instance.cs @@ -0,0 +1,427 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Esiur.Data; +using System.Runtime.CompilerServices; +using System.Reflection; +using Esiur.Net.IIP; +using Esiur.Misc; +using Esiur.Security.Permissions; +using Esiur.Resource.Template; + +namespace Esiur.Resource +{ + public class Instance + { + string name; + + AutoList children;// = new AutoList(); + IResource resource; + IStore store; + AutoList parents;// = new AutoList(); + bool inherit; + ResourceTemplate template; + + AutoList managers;// = new AutoList(); + + public delegate void ResourceModifiedEvent(IResource resource, string propertyName, object newValue, object oldValue); + public delegate void ResourceEventOccurredEvent(IResource resource, string eventName, string[] receivers, object[] args); + public delegate void ResourceDestroyedEvent(IResource resource); + + public event ResourceModifiedEvent ResourceModified; + public event ResourceEventOccurredEvent ResourceEventOccured; + public event ResourceDestroyedEvent ResourceDestroyed; + + KeyList attributes = new KeyList(); + + List ages = new List(); + private uint age; + + uint id; + + + /// + /// Instance attributes are custom properties associated with the instance, a place to store information by IStore. + /// + public KeyList Attributes + { + get + { + return attributes; + } + } + + /// + /// Get the age of a given property index. + /// + /// Zero-based property index. + /// Age. + public uint GetAge(byte index) + { + if (index < ages.Count) + return ages[index]; + else + return 0; + } + + /// + /// Age of the instance, increments by 1 in every modification. + /// + public uint Age + { + get { return age; } + internal set { age = value; } + } + + /// + /// Instance Id. + /// + public uint Id + { + get { return id; } + } + + /// + /// Import properties from bytes array. + /// + /// + /// + public bool Deserialize(object[] properties) + { + foreach (var pt in template.Properties) + { +#if NETSTANDARD1_5 + var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name); +#else + var pi = resource.GetType().GetProperty(pt.Name); +#endif + if (!(properties[pt.Index] is NotModified)) + pi.SetValue(resource, properties[pt.Index]); + } + return true; + } + + /// + /// Export all properties with ResourceProperty attributed as bytes array. + /// + /// + public object[] Serialize() + { + List props = new List(); + + foreach (var pt in template.Properties) + { +#if NETSTANDARD1_5 + var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name); +#else + var pi = resource.GetType().GetProperty(pt.Name); +#endif + var rt = pi.GetValue(resource, null); + props.Add(rt); + } + + return props.ToArray(); + } + /* + public bool Deserialize(byte[] data, uint offset, uint length) + { + + var props = Codec.ParseValues(data, offset, length); + Deserialize(props); + return true; + } + */ + /* + public byte[] Serialize(bool includeLength = false, DistributedConnection sender = null) + { + + //var bl = new BinaryList(); + List props = new List(); + + foreach (var pt in template.Properties) + { + + var pi = resource.GetType().GetProperty(pt.Name); + + var rt = pi.GetValue(resource, null); + + // this is a cool hack to let the property know the sender + if (rt is Func) + rt = (rt as Func)(sender); + + props.Add(rt); + + } + + if (includeLength) + { + return Codec.Compose(props.ToArray(), false); + } + else + { + var rt = Codec.Compose(props.ToArray(), false); + return DC.Clip(rt, 4, (uint)(rt.Length - 4)); + } + } + + public byte[] StorageSerialize() + { + + var props = new List(); + + foreach(var pt in template.Properties) + { + if (!pt.Storable) + continue; + + var pi = resource.GetType().GetProperty(pt.Name); + + if (!pi.CanWrite) + continue; + + var rt = pi.GetValue(resource, null); + + props.Add(rt); + + } + + return Codec.Compose(props.ToArray(), false); + } + */ + + /// + /// If True, the instance can be stored to disk. + /// + /// + public bool IsStorable() + { +#if NETSTANDARD1_5 + var attrs = resource.GetType().GetTypeInfo().GetCustomAttributes(typeof(Storable), true).ToArray(); +#else + var attrs = resource.GetType().GetCustomAttributes(typeof(Storable), true); +#endif + return attrs.Length > 0; + + } + + + /// + /// Notify listeners that a property was modified. + /// + /// + /// + /// + public void Modified([CallerMemberName] string propertyName = "", object newValue = null, object oldValue = null) + { + if (newValue == null) + { + object val; + if (GetPropertyValue(propertyName, out val)) + ResourceModified?.Invoke(resource, propertyName, val, oldValue); + } + else + ResourceModified?.Invoke(resource, propertyName, newValue, oldValue); + } + + internal void EmitResourceEvent(string name, string[] receivers, object[] args) + { + ResourceEventOccured?.Invoke(resource, name, receivers, args); + } + + /// + /// Get the value of a given property by name. + /// + /// Property name + /// Output value + /// True, if the resource has the property. + public bool GetPropertyValue(string name, out object value) + { +#if NETSTANDARD1_5 + PropertyInfo pi = resource.GetType().GetTypeInfo().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + +#else + PropertyInfo pi = resource.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); +#endif + + if (pi != null) + { + +#if NETSTANDARD1_5 + object[] ca = pi.GetCustomAttributes(typeof(ResourceProperty), false).ToArray(); + +#else + object[] ca = pi.GetCustomAttributes(typeof(ResourceProperty), false); +#endif + + if (ca.Length > 0) + { + value = pi.GetValue(resource, null); + //if (value is Func) + // value = (value as Func)(sender); + return true; + } + } + + value = null; + return false; + } + + + public bool Inherit + { + get { return inherit; } + } + + /// + /// List of parents. + /// + public AutoList Parents + { + get { return parents; } + } + + /// + /// Store responsible for creating and keeping the resource. + /// + public IStore Store + { + get { return store; } + } + + /// + /// List of children. + /// + public AutoList Children + { + get { return children; } + } + + /// + /// The unique and permanent link to the resource. + /// + public string Link + { + get + { + if (this.store != null) + return this.store.Link(this.resource); + else + { + var l = new List(); + //l.Add(name); + + var p = this.resource; // parents.First(); + + while (true) + { + l.Insert(0, p.Instance.name); + + if (p.Instance.parents.Count == 0) + break; + + p = p.Instance.parents.First(); + } + + return String.Join("/", l.ToArray()); + } + } + } + + /// + /// Instance name. + /// + public string Name + { + get { return name; } + } + + + /// + /// Resource managed by this instance. + /// + public IResource Resource + { + get { return resource; } + } + + /// + /// Resource template describes the properties, functions and events of the resource. + /// + public ResourceTemplate Template + { + get { return template; } + } + + /// + /// Create new instance. + /// + /// Instance Id. + /// Name of the instance. + /// Resource to manage. + /// Store responsible for the resource. + public Instance(uint id, string name, IResource resource, IStore store) + { + this.store = store; + this.resource = resource; + this.id = id; + this.name = name; + + children = new AutoList(this); + parents = new AutoList(this); + managers = new AutoList(this); + children.OnAdd += Children_OnAdd; + children.OnRemoved += Children_OnRemoved; + + resource.OnDestroy += Resource_OnDestroy; + + template = Warehouse.GetTemplate(resource.GetType()); + + + // set ages + for (byte i = 0; i < template.Properties.Length; i++) + ages.Add(0); + + + // connect events + Type t = resource.GetType(); + +#if NETSTANDARD1_5 + var events = t.GetTypeInfo().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + +#else + var events = t.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); +#endif + + foreach (var evt in events) + { + if (evt.EventHandlerType != typeof(ResourceEventHanlder)) + continue; + + var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); + + if (ca.Length == 0) + continue; + + ResourceEventHanlder proxyDelegate = (receivers, args) => EmitResourceEvent(evt.Name, receivers, args); + evt.AddEventHandler(resource, proxyDelegate); + } + } + + private void Children_OnRemoved(Instance parent, IResource value) + { + value.Instance.parents.Remove(resource); + } + + private void Children_OnAdd(Instance parent, IResource value) + { + value.Instance.parents.Add(resource); + } + + private void Resource_OnDestroy(object sender) + { + ResourceDestroyed?.Invoke((IResource)sender); + } + } +} diff --git a/Esiur/Resource/ResourceEvent.cs b/Esiur/Resource/ResourceEvent.cs new file mode 100644 index 0000000..b50d4e3 --- /dev/null +++ b/Esiur/Resource/ResourceEvent.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource +{ + + [AttributeUsage(AttributeTargets.Event)] + public class ResourceEvent : System.Attribute + { + + string expansion; + + public string Expansion + { + get + { + return expansion; + } + } + + + public ResourceEvent(string expansion = null) + { + this.expansion = expansion; + } + } +} diff --git a/Esiur/Resource/ResourceEventHandler.cs b/Esiur/Resource/ResourceEventHandler.cs new file mode 100644 index 0000000..3eec4a1 --- /dev/null +++ b/Esiur/Resource/ResourceEventHandler.cs @@ -0,0 +1,13 @@ +using Esiur.Data; +using Esiur.Engine; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource +{ + public delegate void ResourceEventHanlder(string[] receivers, params object[] args); + +} diff --git a/Esiur/Resource/ResourceFunction.cs b/Esiur/Resource/ResourceFunction.cs new file mode 100644 index 0000000..af524bf --- /dev/null +++ b/Esiur/Resource/ResourceFunction.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource +{ + [AttributeUsage(AttributeTargets.Method)] + public class ResourceFunction : System.Attribute + { + private string expansion = null; + + public string Expansion + { + get + { + return expansion; + } + } + + + public ResourceFunction(string expansion = null) + { + this.expansion = expansion; + } + } +} diff --git a/Esiur/Resource/ResourceProperty.cs b/Esiur/Resource/ResourceProperty.cs new file mode 100644 index 0000000..686436d --- /dev/null +++ b/Esiur/Resource/ResourceProperty.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource +{ + + [AttributeUsage(AttributeTargets.Property)] + public class ResourceProperty : System.Attribute + { + string readExpansion; + string writeExpansion; + + + public string ReadExpansion + { + get + { + return readExpansion; + } + } + + public string WriteExpansion + { + get + { + return writeExpansion; + } + } + + public ResourceProperty(string readExpansion = null, string writeExpansion = null) + { + this.readExpansion = readExpansion; + this.writeExpansion = writeExpansion; + } + } +} diff --git a/Esiur/Resource/ResourceTrigger.cs b/Esiur/Resource/ResourceTrigger.cs new file mode 100644 index 0000000..b90e6aa --- /dev/null +++ b/Esiur/Resource/ResourceTrigger.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource +{ + public enum ResourceTrigger : int + { + Loaded = 0, + Initialize, + Terminate, + Configure, + SystemInitialized, + SystemTerminated, + SystemReload, + } +} diff --git a/Esiur/Resource/Storable.cs b/Esiur/Resource/Storable.cs new file mode 100644 index 0000000..de8f15a --- /dev/null +++ b/Esiur/Resource/Storable.cs @@ -0,0 +1,48 @@ +using Esiur.Data; +using Esiur.Engine; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource +{ + [AttributeUsage(AttributeTargets.All)] + public class Storable : global::System.Attribute + { + public delegate object SerializerFunction(object value); + public delegate object DeserializerFunction(object data); + + SerializerFunction serializer; + DeserializerFunction deserializer; + DataType type; + + public Storable() + { + type = DataType.Void; + } + + public DeserializerFunction Deserializer + { + get { return deserializer; } + } + + public SerializerFunction Serializer + { + get { return serializer; } + } + + public Storable(DataType type) + { + this.type = type; + } + + public Storable(DataType type, SerializerFunction serializer, DeserializerFunction deserializer) + { + this.type = type; + this.serializer = serializer; + this.deserializer = deserializer; + } + } +} diff --git a/Esiur/Resource/Template/EventTemplate.cs b/Esiur/Resource/Template/EventTemplate.cs new file mode 100644 index 0000000..48afc6e --- /dev/null +++ b/Esiur/Resource/Template/EventTemplate.cs @@ -0,0 +1,33 @@ +using Esiur.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource.Template +{ + public class EventTemplate : MemberTemplate + { + public string Expansion + { + get; + set; + } + + public override byte[] Compose() + { + var name = base.Compose(); + + if (Expansion != null) + { + var exp = DC.ToBytes(Expansion); + return BinaryList.ToBytes((byte)0x50, exp.Length, exp, (byte)name.Length, name); + } + else + return BinaryList.ToBytes((byte)0x40, (byte)name.Length, name); + } + + public EventTemplate() { Type = MemberType.Event; } + } +} diff --git a/Esiur/Resource/Template/FunctionTemplate.cs b/Esiur/Resource/Template/FunctionTemplate.cs new file mode 100644 index 0000000..8fa2389 --- /dev/null +++ b/Esiur/Resource/Template/FunctionTemplate.cs @@ -0,0 +1,42 @@ +using Esiur.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource.Template +{ + public class FunctionTemplate : MemberTemplate + { + + public string Expansion + { + get; + set; + } + + public bool IsVoid + { + get; + set; + } + + + public override byte[] Compose() + { + var name = base.Compose(); + + if (Expansion != null) + { + var exp = DC.ToBytes(Expansion); + return BinaryList.ToBytes((byte)(0x10 | (IsVoid ? 0x8 : 0x0)), exp.Length, exp, (byte)name.Length, name); + } + else + return BinaryList.ToBytes((byte)(IsVoid ? 0x8 : 0x0), (byte)name.Length, name); + } + + + public FunctionTemplate() { Type = MemberType.Function; } + } +} diff --git a/Esiur/Resource/Template/MemberTemplate.cs b/Esiur/Resource/Template/MemberTemplate.cs new file mode 100644 index 0000000..a24b5d8 --- /dev/null +++ b/Esiur/Resource/Template/MemberTemplate.cs @@ -0,0 +1,29 @@ +using Esiur.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource.Template +{ + public class MemberTemplate + { + public enum MemberType + { + Function = 0, + Property = 1, + Event = 2, + } + + public byte Index { get; set; } + public string Name { get; set; } + public MemberType Type { get; set; } + + public virtual byte[] Compose() + { + return DC.ToBytes(Name); + } + } + +} diff --git a/Esiur/Resource/Template/PropertyTemplate.cs b/Esiur/Resource/Template/PropertyTemplate.cs new file mode 100644 index 0000000..96cf427 --- /dev/null +++ b/Esiur/Resource/Template/PropertyTemplate.cs @@ -0,0 +1,71 @@ +using Esiur.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource.Template +{ + public class PropertyTemplate : MemberTemplate + { + public enum PropertyPermission:byte + { + Read = 1, + Write, + ReadWrite + } + + //bool ReadOnly; + //IIPTypes::DataType ReturnType; + public PropertyPermission Permission { + get; + set; + } + + public string ReadExpansion + { + get; + set; + } + + public string WriteExpansion + { + get; + set; + } + + public bool Storable + { + get; + set; + } + + + public override byte[] Compose() + { + var name = base.Compose(); + + if (WriteExpansion != null && ReadExpansion != null) + { + var rexp = DC.ToBytes(ReadExpansion); + var wexp = DC.ToBytes(WriteExpansion); + return BinaryList.ToBytes((byte)(0x38 | (byte)Permission), wexp.Length, wexp, rexp.Length, rexp, (byte)name.Length, name); + } + else if (WriteExpansion != null) + { + var wexp = DC.ToBytes(WriteExpansion); + return BinaryList.ToBytes((byte)(0x30 | (byte)Permission), wexp.Length, wexp, (byte)name.Length, name); + } + else if (ReadExpansion != null) + { + var rexp = DC.ToBytes(ReadExpansion); + return BinaryList.ToBytes((byte)(0x28 | (byte)Permission), rexp.Length, rexp, (byte)name.Length, name); + } + else + return BinaryList.ToBytes((byte)(0x20 | (byte)Permission), (byte)name.Length, name); + } + + public PropertyTemplate() { Type = MemberType.Property; } + } +} diff --git a/Esiur/Resource/Template/ResourceTemplate.cs b/Esiur/Resource/Template/ResourceTemplate.cs new file mode 100644 index 0000000..9fa713c --- /dev/null +++ b/Esiur/Resource/Template/ResourceTemplate.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using Esiur.Misc; +using Esiur.Data; +using Esiur.Engine; +using System.Security.Cryptography; + +namespace Esiur.Resource.Template +{ + public class ResourceTemplate + { + Guid classId; + string className; + List members = new List(); + List functions = new List(); + List events = new List(); + List properties = new List(); + int version; + //bool isReady; + + byte[] content; + + public byte[] Content + { + get { return content; } + } + + public MemberTemplate GetMemberTemplate(MemberInfo member) + { + if (member is MethodInfo) + return GetFunctionTemplate(member.Name); + else if (member is EventInfo) + return GetEventTemplate(member.Name); + else if (member is PropertyInfo) + return GetPropertyTemplate(member.Name); + else + return null; + } + + public EventTemplate GetEventTemplate(string eventName) + { + foreach (var i in events) + if (i.Name == eventName) + return i; + return null; + } + + public EventTemplate GetEventTemplate(byte index) + { + foreach (var i in events) + if (i.Index == index) + return i; + return null; + } + + public FunctionTemplate GetFunctionTemplate(string functionName) + { + foreach (var i in functions) + if (i.Name == functionName) + return i; + return null; + } + public FunctionTemplate GetFunctionTemplate(byte index) + { + foreach (var i in functions) + if (i.Index == index) + return i; + return null; + } + + public PropertyTemplate GetPropertyTemplate(byte index) + { + foreach (var i in properties) + if (i.Index == index) + return i; + return null; + } + + public PropertyTemplate GetPropertyTemplate(string propertyName) + { + foreach (var i in properties) + if (i.Name == propertyName) + return i; + return null; + } + + public Guid ClassId + { + get { return classId; } + } + public string ClassName + { + get { return className; } + } + + public MemberTemplate[] Methods + { + get{return members.ToArray();} + } + + public FunctionTemplate[] Functions + { + get { return functions.ToArray(); } + } + + public EventTemplate[] Events + { + get { return events.ToArray(); } + } + + public PropertyTemplate[] Properties + { + get { return properties.ToArray(); } + } + + + + public ResourceTemplate() + { + + } + + + public ResourceTemplate(Type type) + { + // set guid + + var typeName = Encoding.UTF8.GetBytes(type.FullName); + var hash = SHA256.Create().ComputeHash(typeName).Clip(0, 16); + + classId = new Guid(hash); + className = type.FullName; + + +#if NETSTANDARD1_5 + PropertyInfo[] propsInfo = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + EventInfo[] eventsInfo = type.GetTypeInfo().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + MethodInfo[] methodsInfo = type.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + +#else + PropertyInfo[] propsInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + EventInfo[] eventsInfo = type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + MethodInfo[] methodsInfo = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); +#endif + + //byte currentIndex = 0; + + byte i = 0; + + foreach (var pi in propsInfo) + { + var ps = (ResourceProperty[])pi.GetCustomAttributes(typeof(ResourceProperty), true); + if (ps.Length > 0) + { + var pt = new PropertyTemplate(); + pt.Name = pi.Name; + pt.Index = i++; + pt.ReadExpansion = ps[0].ReadExpansion; + pt.WriteExpansion = ps[0].WriteExpansion; + properties.Add(pt); + } + } + + i = 0; + + foreach (var ei in eventsInfo) + { + var es = (ResourceEvent[])ei.GetCustomAttributes(typeof(ResourceEvent), true); + if (es.Length > 0) + { + var et = new EventTemplate(); + et.Name = ei.Name; + et.Index = i++; + et.Expansion = es[0].Expansion; + events.Add(et); + } + } + + i = 0; + foreach (MethodInfo mi in methodsInfo) + { + var fs = (ResourceFunction[])mi.GetCustomAttributes(typeof(ResourceFunction), true); + if (fs.Length > 0) + { + var ft = new FunctionTemplate(); + ft.Name = mi.Name; + ft.Index = i++; + ft.IsVoid = mi.ReturnType == typeof(void); + ft.Expansion = fs[0].Expansion; + functions.Add(ft); + } + } + + // append signals + for (i = 0; i < events.Count; i++) + members.Add(events[i]); + // append slots + for (i = 0; i < functions.Count; i++) + members.Add(functions[i]); + // append properties + for (i = 0; i < properties.Count; i++) + members.Add(properties[i]); + + // bake it binarily + var b = new BinaryList(); + b.Append(classId); + b.Append((byte)className.Length, className); + b.Append(version); + b.Append((ushort)members.Count); + foreach (var ft in functions) + b.Append(ft.Compose()); + foreach (var pt in properties) + b.Append(pt.Compose()); + foreach (var et in events) + b.Append(et.Compose()); + content = b.ToArray(); + } + + public static ResourceTemplate Parse(byte[] data) + { + return Parse(data, 0, (uint)data.Length); + } + + + public static ResourceTemplate Parse(byte[] data, uint offset, uint contentLength) + { + + uint ends = offset + contentLength; + + uint oOffset = offset; + + // start parsing... + + var od = new ResourceTemplate(); + od.content = data.Clip(offset, contentLength); + + od.classId = data.GetGuid(offset); + offset += 16; + od.className = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); + offset += (uint)data[offset] + 1; + + od.version = data.GetInt32(offset); + offset += 4; + + ushort methodsCount = data.GetUInt16(offset); + offset += 2; + + byte functionIndex = 0; + byte propertyIndex = 0; + byte eventIndex = 0; + + for (int i = 0; i < methodsCount; i++) + { + var type = data[offset] >> 5; + + if (type == 0) // function + { + var ft = new FunctionTemplate(); + ft.Index = functionIndex++; + var expansion = ((data[offset] & 0x10) == 0x10); + ft.IsVoid = ((data[offset++] & 0x08) == 0x08); + ft.Name = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); + offset += (uint)data[offset] + 1; + + if (expansion) // expansion ? + { + var cs = data.GetUInt32(offset); + offset += 4; + ft.Expansion = data.GetString(offset, cs); + offset += cs; + } + + od.functions.Add(ft); + } + else if (type == 1) // property + { + + var pt = new PropertyTemplate(); + pt.Index = propertyIndex++; + var readExpansion = ((data[offset] & 0x8) == 0x8); + var writeExpansion = ((data[offset] & 0x10) == 0x10); + pt.Permission = (PropertyTemplate.PropertyPermission)((data[offset++] >> 1) & 0x3); + pt.Name = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); + offset += (uint)data[offset] + 1; + + if (readExpansion) // expansion ? + { + var cs = data.GetUInt32(offset); + offset += 4; + pt.ReadExpansion = data.GetString(offset, cs); + offset += cs; + } + + if (writeExpansion) // expansion ? + { + var cs = data.GetUInt32(offset); + offset += 4; + pt.WriteExpansion = data.GetString(offset, cs); + offset += cs; + } + + od.properties.Add(pt); + } + else if (type == 2) // Event + { + var et = new EventTemplate(); + et.Index = eventIndex++; + var expansion = ((data[offset++] & 0x10) == 0x10); + + et.Name = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, (int)data[offset]); + offset += (uint)data[offset] + 1; + + if (expansion) // expansion ? + { + var cs = data.GetUInt32(offset); + offset += 4; + et.Expansion = data.GetString(offset, cs); + offset += cs; + } + + od.events.Add(et); + + } + } + + // append signals + for (int i = 0; i < od.events.Count; i++) + od.members.Add(od.events[i]); + // append slots + for (int i = 0; i < od.functions.Count; i++) + od.members.Add(od.functions[i]); + // append properties + for (int i = 0; i < od.properties.Count; i++) + od.members.Add(od.properties[i]); + + + //od.isReady = true; + /* + var oo = owner.Socket.Engine.GetObjectDescription(od.GUID); + if (oo != null) + { + Console.WriteLine("Already there ! description"); + return oo; + } + else + { + owner.Socket.Engine.AddObjectDescription(od); + return od; + } + */ + + return od; + } + } + +} diff --git a/Esiur/Resource/Warehouse.cs b/Esiur/Resource/Warehouse.cs new file mode 100644 index 0000000..a408ba0 --- /dev/null +++ b/Esiur/Resource/Warehouse.cs @@ -0,0 +1,258 @@ +using Esiur.Data; +using Esiur.Engine; +using Esiur.Resource.Template; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Resource +{ + // Centeral Resource Issuer + public static class Warehouse + { + //static byte prefixCounter; + + static List stores = new List(); + static Dictionary resources = new Dictionary(); + static uint resourceCounter = 0; + + static KeyList templates = new KeyList(); + + + /// + /// Get a store by its name. + /// + /// Store instance name + /// + public static IStore GetStore(string name) + { + foreach (var s in stores) + if (s.Instance.Name == name) + return s; + return null; + } + + /// + /// Get a resource by instance Id. + /// + /// Instance Id + /// + public static AsyncReply Get(uint id) + { + if (resources.ContainsKey(id)) + return new AsyncReply(resources[id]); + else + return null; + } + + /// + /// Open the warehouse. + /// This function issues the initialize trigger to all stores and resources. + /// + /// True, if no problem occurred. + public static AsyncReply Open() + { + var bag = new AsyncBag(); + + foreach (var store in stores) + bag.Add(store.Trigger(ResourceTrigger.Initialize)); + + foreach (var store in stores) + bag.Add(store.Trigger(ResourceTrigger.SystemInitialized)); + + bag.Seal(); + + var rt = new AsyncReply(); + bag.Then((x) => + { + foreach(var b in x) + if (!b) + { + rt.Trigger(false); + return; + } + + rt.Trigger(true); + }); + + return rt; + } + + /// + /// Close the warehouse. + /// This function issues terminate trigger to all resources and stores. + /// + /// True, if no problem occurred. + public static AsyncReply Close() + { + + var bag = new AsyncBag(); + + foreach (var resource in resources.Values) + if (!(resource is IStore)) + bag.Add(resource.Trigger(ResourceTrigger.Terminate)); + + foreach (var store in stores) + bag.Add(store.Trigger(ResourceTrigger.Terminate)); + + foreach (var resource in resources.Values) + if (!(resource is IStore)) + bag.Add(resource.Trigger(ResourceTrigger.SystemTerminated)); + + foreach (var store in stores) + bag.Add(store.Trigger(ResourceTrigger.SystemTerminated)); + + bag.Seal(); + + var rt = new AsyncReply(); + bag.Then((x) => + { + foreach (var b in x) + if (!b) + { + rt.Trigger(false); + return; + } + + rt.Trigger(true); + }); + + return rt; + } + + /// + /// Get a resource by its path. + /// Resource path is sperated by '/' character, e.g. "system/http". + /// + /// + /// Resource instance. + public static AsyncReply Get(string path) + { + + var p = path.Split('/'); + IResource res; + + foreach(IStore d in stores) + if (p[0] == d.Instance.Name) + { + var i = 1; + res = d; + while(p.Length > i) + { + var si = i; + + foreach (IResource r in res.Instance.Children) + if (r.Instance.Name == p[i]) + { + i++; + res = r; + break; + } + + if (si == i) + // not found, ask the store + return d.Get(path.Substring(p[0].Length + 1)); + } + + return new AsyncReply(res); + } + + return new AsyncReply(null); + } + + /// + /// Put a resource in the warehouse. + /// + /// Resource instance. + /// Resource name. + /// IStore that manages the resource. Can be null if the resource is a store. + /// Parent resource. if not presented the store becomes the parent for the resource. + public static void Put(IResource resource, string name, IStore store = null, IResource parent = null) + { + resource.Instance = new Instance(resourceCounter++, name, resource, store); + + if (store == parent) + parent = null; + + if (parent == null) + { + if (!(resource is IStore)) + store.Instance.Children.Add(resource); + } + else + parent.Instance.Children.Add(resource); + + + if (resource is IStore) + stores.Add(resource as IStore); + else + store.Put(resource); + + resources.Add(resource.Instance.Id, resource); + } + + public static T New(string name, IStore store = null, IResource parent = null) + { + var res = Activator.CreateInstance(typeof(T)) as IResource; + Put(res, name, store, parent); + return (T)res; + } + + /// + /// Put a resource template in the templates warehouse. + /// + /// Resource template. + public static void PutTemplate(ResourceTemplate template) + { + if (templates.ContainsKey(template.ClassId)) + templates.Add(template.ClassId, template); + } + + + /// + /// Get a template by type from the templates warehouse. If not in the warehouse, a new ResourceTemplate is created and added to the warehouse. + /// + /// .Net type. + /// Resource template. + public static ResourceTemplate GetTemplate(Type type) + { + // loaded ? + foreach (var t in templates.Values) + if (t.ClassName == type.FullName) + return t; + + var template = new ResourceTemplate(type); + templates.Add(template.ClassId, template); + + return template; + } + + /// + /// Get a template by class Id from the templates warehouse. If not in the warehouse, a new ResourceTemplate is created and added to the warehouse. + /// + /// Class Id. + /// Resource template. + public static AsyncReply GetTemplate(Guid classId) + { + if (templates.ContainsKey(classId)) + return new AsyncReply(templates[classId]); + return null; + } + + /// + /// Get a template by class name from the templates warehouse. If not in the warehouse, a new ResourceTemplate is created and added to the warehouse. + /// + /// Class name. + /// Resource template. + public static AsyncReply GetTemplate(string className) + { + foreach (var t in templates.Values) + if (t.ClassName == className) + return new AsyncReply(t); + + return null; + } + } +} diff --git a/Esiur/Security/Authority/AlienAuthentication.cs b/Esiur/Security/Authority/AlienAuthentication.cs new file mode 100644 index 0000000..739ee97 --- /dev/null +++ b/Esiur/Security/Authority/AlienAuthentication.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class AlienAuthentication : Authentication + { + public AlienAuthentication(Certificate certificate, AuthenticationState state) : + base(certificate, state, AuthenticationType.Alien) + { + + } + } +} diff --git a/Esiur/Security/Authority/Authentication.cs b/Esiur/Security/Authority/Authentication.cs new file mode 100644 index 0000000..b95b00b --- /dev/null +++ b/Esiur/Security/Authority/Authentication.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class Authentication + { + Certificate certificate; + AuthenticationState state; + AuthenticationType type; + + public Certificate Certificate + { + get { return certificate; } + } + + public AuthenticationState State + { + get { return state; } + } + + public AuthenticationType Type + { + get { return type; } + } + + public Authentication(Certificate certificate, AuthenticationState state, AuthenticationType type) + { + + } + } +} diff --git a/Esiur/Security/Authority/AuthenticationState.cs b/Esiur/Security/Authority/AuthenticationState.cs new file mode 100644 index 0000000..58b84d6 --- /dev/null +++ b/Esiur/Security/Authority/AuthenticationState.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public enum AuthenticationState : int + { + Denied = 0x1, + Succeeded = 0x2, + Blocked = 0x4, + Rejected = 0x8, + NeedsUpdate = 0x10, + NotFound = 0x20 + } +} diff --git a/Esiur/Security/Authority/AuthenticationType.cs b/Esiur/Security/Authority/AuthenticationType.cs new file mode 100644 index 0000000..5dd10d9 --- /dev/null +++ b/Esiur/Security/Authority/AuthenticationType.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public enum AuthenticationType + { + Host, + CoHost, + Client, + Alien + } +} diff --git a/Esiur/Security/Authority/CACertificate.cs b/Esiur/Security/Authority/CACertificate.cs new file mode 100644 index 0000000..2115e0a --- /dev/null +++ b/Esiur/Security/Authority/CACertificate.cs @@ -0,0 +1,163 @@ +using Esiur.Data; +using Esiur.Engine; +using Esiur.Misc; +using Esiur.Security.Cryptography; +using Esiur.Security.Integrity; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class CACertificate : Certificate + { + + string name; + + public string Name + { + get { return name; } + } + + public CACertificate(byte[] data, uint offset, uint length, bool privateKeyIncluded = false) + :base(0, DateTime.MinValue, DateTime.MinValue, HashFunctionType.MD5) + { + + uint oOffset = offset; + + this.id = DC.GetUInt64(data, offset); + offset += 8; + this.issueDate = DC.GetDateTime(data, offset); + offset += 8; + this.expireDate = DC.GetDateTime(data, offset); + offset += 8; + this.hashFunction = (HashFunctionType)(data[offset++] >> 4); + + + this.name = (Encoding.ASCII.GetString(data, (int)offset + 1, data[offset])); + offset += (uint)data[offset] + 1; + + + var aea = (AsymetricEncryptionAlgorithmType)(data[offset] >> 5); + + if (aea == AsymetricEncryptionAlgorithmType.RSA) + { + var key = new RSAParameters(); + uint exponentLength = (uint)data[offset++] & 0x1F; + + key.Exponent = DC.Clip(data, offset, exponentLength); + + offset += exponentLength; + + uint keySize = DC.GetUInt16(data, offset); + offset += 2; + + key.Modulus = DC.Clip(data, offset, keySize); + + offset += keySize; + + // copy cert data + this.publicRawData = new byte[offset - oOffset]; + Buffer.BlockCopy(data, (int)oOffset, publicRawData, 0, publicRawData.Length); + + if (privateKeyIncluded) + { + uint privateKeyLength = (keySize * 3) + (keySize / 2); + uint halfKeySize = keySize / 2; + + privateRawData = DC.Clip(data, offset, privateKeyLength); + + key.D = DC.Clip(data, offset, keySize); + offset += keySize; + + key.DP = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + + key.DQ = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + + + key.InverseQ = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + + key.P = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + + key.Q = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + + } + + // setup rsa + this.rsa = RSA.Create();// new RSACryptoServiceProvider(); + this.rsa.ImportParameters(key); + } + } + + public CACertificate(ulong id, string authorityName, DateTime issueDate, DateTime expireDate, + HashFunctionType hashFunction = HashFunctionType.SHA1, uint ip = 0, byte[] ip6 = null) + : base(id, issueDate, expireDate, hashFunction) + { + // assign type + + BinaryList cr = new BinaryList(); + + // make header + + cr.Append(id, issueDate, expireDate); + + // hash function + cr.Append((byte)((byte)hashFunction << 4)); + this.hashFunction = hashFunction; + + // CA Name + this.name = authorityName; + cr.Append((byte)(authorityName.Length), Encoding.ASCII.GetBytes(authorityName)); + + // public key + rsa = RSA.Create();// new RSACryptoServiceProvider(2048); + rsa.KeySize = 2048; + RSAParameters dRSAKey = rsa.ExportParameters(true); + + + cr.Append((byte)dRSAKey.Exponent.Length, dRSAKey.Exponent, (ushort)dRSAKey.Modulus.Length, dRSAKey.Modulus); + + + publicRawData = cr.ToArray(); + + privateRawData = DC.Merge(dRSAKey.D, dRSAKey.DP, dRSAKey.DQ, dRSAKey.InverseQ, dRSAKey.P, dRSAKey.Q); + + + } + + public override bool Save(string filename, bool includePrivate = false) + { + try + { + if (includePrivate) + File.WriteAllBytes(filename, BinaryList.ToBytes((byte)CertificateType.CAPrivate, publicRawData, privateRawData)); + else + File.WriteAllBytes(filename, BinaryList.ToBytes((byte)CertificateType.CAPublic, publicRawData)); + + return true; + } + catch + { + return false; + } + } + + public override byte[] Serialize(bool includePrivate = false) + { + if (includePrivate) + return BinaryList.ToBytes(publicRawData, privateRawData); + else + return publicRawData; + } + + } +} diff --git a/Esiur/Security/Authority/Certificate.cs b/Esiur/Security/Authority/Certificate.cs new file mode 100644 index 0000000..0d5e8a2 --- /dev/null +++ b/Esiur/Security/Authority/Certificate.cs @@ -0,0 +1,198 @@ +using Esiur.Data; +using Esiur.Engine; +using Esiur.Misc; +using Esiur.Security.Cryptography; +using Esiur.Security.Integrity; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public abstract class Certificate + { + protected DateTime issueDate, expireDate; + protected RSA rsa; + protected Aes aes; + + protected byte[] publicRawData; + protected byte[] privateRawData; + protected ulong id; + protected HashFunctionType hashFunction; + + public Certificate(ulong id, DateTime issueDate, DateTime expireDate, HashFunctionType hashFunction) + { + this.id = id; + this.issueDate = issueDate; + this.expireDate = expireDate; + this.hashFunction = hashFunction; + } + + public ulong Id + { + get { return id; } + } + + public AsymetricEncryptionAlgorithmType AsymetricEncryptionAlgorithm + { + get { return AsymetricEncryptionAlgorithmType.RSA; } + } + + public byte[] AsymetricEncrypt(byte[] message) + { + return rsa.Encrypt(message, RSAEncryptionPadding.OaepSHA512); + } + + + public byte[] AsymetricEncrypt(byte[] message, uint offset, uint length) + { + if (message.Length != length) + return rsa.Encrypt(DC.Clip(message, offset, length), RSAEncryptionPadding.OaepSHA512); + else + return rsa.Encrypt(message, RSAEncryptionPadding.OaepSHA512); + } + + public byte[] AsymetricDecrypt(byte[] message) + { + try + { + return rsa.Decrypt(message, RSAEncryptionPadding.OaepSHA512); + } + catch (Exception ex) + { + Global.Log("Certificate", LogType.Error, ex.ToString()); + return null; + } + } + + public byte[] AsymetricDecrypt(byte[] message, uint offset, uint length) + { + try + { + if (message.Length != length) + return rsa.Decrypt(DC.Clip(message, offset, length), RSAEncryptionPadding.OaepSHA512); + else + return rsa.Decrypt(message, RSAEncryptionPadding.OaepSHA512); + + } + catch (Exception ex) + { + Global.Log("Certificate", LogType.Error, ex.ToString()); + return null; + } + } + + public byte[] SymetricEncrypt(byte[] message, uint offset, uint length) + { + byte[] rt = null; + + using (var ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write)) + cs.Write(message, (int)offset, (int)length); + + rt = ms.ToArray(); + } + + return rt; + } + + public byte[] SymetricEncrypt(byte[] message) + { + return SymetricEncrypt(message, 0, (uint)message.Length); + } + + public byte[] SymetricDecrypt(byte[] message, uint offset, uint length) + { + byte[] rt = null; + + using (var ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write)) + cs.Write(message, (int)offset, (int)length); + + rt = ms.ToArray(); + } + + return rt; + } + + public byte[] SymetricDecrypt(byte[] message) + { + return SymetricDecrypt(message, 0, (uint)message.Length); + } + + public byte[] Sign(byte[] message) + { + return Sign(message, 0, (uint)message.Length); + } + + public byte[] Sign(byte[] message, uint offset, uint length) + { + if (hashFunction == HashFunctionType.SHA1) + return rsa.SignData(message, (int)offset, (int)length, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + else if (hashFunction == HashFunctionType.MD5) + return rsa.SignData(message, (int)offset, (int)length, HashAlgorithmName.MD5, RSASignaturePadding.Pkcs1); + else if (hashFunction == HashFunctionType.SHA256) + return rsa.SignData(message, (int)offset, (int)length, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + else if (hashFunction == HashFunctionType.SHA384) + return rsa.SignData(message, (int)offset, (int)length, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1); + else if (hashFunction == HashFunctionType.SHA512) + return rsa.SignData(message, (int)offset, (int)length, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); + + return null; + } + + public bool InitializeSymetricCipher(SymetricEncryptionAlgorithmType algorithm, int keyLength, byte[] key, byte[] iv) + { + if (algorithm == SymetricEncryptionAlgorithmType.AES) + { + if (keyLength == 0) // 128 bit + { + aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + aes.Key = key; + aes.IV = iv; + + return true; + } + } + + return false; + } + + + public abstract bool Save(string filename, bool includePrivate = false); + public abstract byte[] Serialize(bool includePrivate = false); + + public static Certificate Load(string filename) + { + byte[] ar = File.ReadAllBytes(filename); + var t = (CertificateType)ar[0]; + + switch (t) + { + case CertificateType.CAPublic: + return new CACertificate(ar, 1, (uint)ar.Length - 1); + case CertificateType.CAPrivate: + return new CACertificate(ar, 1, (uint)ar.Length - 1, true); + case CertificateType.DomainPublic: + return new DomainCertificate(ar, 1, (uint)ar.Length - 1); + case CertificateType.DomainPrivate: + return new DomainCertificate(ar, 1, (uint)ar.Length - 1, true); + case CertificateType.UserPublic: + return new UserCertificate(ar, 1, (uint)ar.Length - 1); + case CertificateType.UserPrivate: + return new UserCertificate(ar, 1, (uint)ar.Length - 1, true); + } + + return null; + } + } + +} diff --git a/Esiur/Security/Authority/CertificateType.cs b/Esiur/Security/Authority/CertificateType.cs new file mode 100644 index 0000000..99cb85a --- /dev/null +++ b/Esiur/Security/Authority/CertificateType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public enum CertificateType + { + CAPublic = 0, + CAPrivate, + DomainPublic, + DomainPrivate, + UserPublic, + UserPrivate + } +} diff --git a/Esiur/Security/Authority/ClientAuthentication.cs b/Esiur/Security/Authority/ClientAuthentication.cs new file mode 100644 index 0000000..b4a57c6 --- /dev/null +++ b/Esiur/Security/Authority/ClientAuthentication.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class ClientAuthentication : Authentication + { + public ClientAuthentication(byte[] credentials, UserCertificate certificate, AuthenticationState state) + : base(certificate, state, AuthenticationType.Client) + { + + } + } +} diff --git a/Esiur/Security/Authority/CoHostAuthentication.cs b/Esiur/Security/Authority/CoHostAuthentication.cs new file mode 100644 index 0000000..a429e64 --- /dev/null +++ b/Esiur/Security/Authority/CoHostAuthentication.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class CoHostAuthentication : Authentication + { + public CoHostAuthentication(DomainCertificate certificate, AuthenticationState state) + : base(certificate, state, AuthenticationType.CoHost) + { + + } + } +} diff --git a/Esiur/Security/Authority/DomainCertificate.cs b/Esiur/Security/Authority/DomainCertificate.cs new file mode 100644 index 0000000..caadbbc --- /dev/null +++ b/Esiur/Security/Authority/DomainCertificate.cs @@ -0,0 +1,220 @@ +using Esiur.Data; +using Esiur.Misc; +using Esiur.Security.Cryptography; +using Esiur.Security.Integrity; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class DomainCertificate : Certificate + { + uint ip; + byte[] ip6; + string domain; + + //CACertificate ca; + string caName; + ulong caId; + byte[] signature; + + string authorityName; + + public string AuthorityName + { + get { return authorityName; } + } + + public string Domain + { + get { return domain; } + } + + public byte[] Signature + { + get { return signature; } + } + + public uint IPAddress + { + get { return ip; } + } + + public byte[] IPv6Address + { + get { return ip6; } + } + + public DomainCertificate(byte[] data, uint offset, uint length, bool privateKeyIncluded = false) + :base(0, DateTime.MinValue, DateTime.MinValue, HashFunctionType.MD5) + { + var oOffset = offset; + + this.id = DC.GetUInt64(data, offset); + offset += 8; + + // load IPs + this.ip = DC.GetUInt32(data, offset); + offset += 4; + this.ip6 = DC.Clip(data, offset, 16); + + offset += 16; + + this.issueDate = DC.GetDateTime(data, offset); + offset += 8; + this.expireDate = DC.GetDateTime(data, offset); + offset += 8; + + this.domain = Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); + offset += (uint)data[offset] + 1; + + this.authorityName = (Encoding.ASCII.GetString(data, (int)offset + 1, data[offset])); + offset += (uint)data[offset] + 1; + + caId = DC.GetUInt64(data, offset); + offset += 8; + + var aea = (AsymetricEncryptionAlgorithmType)(data[offset] >> 5); + + if (aea == AsymetricEncryptionAlgorithmType.RSA) + { + var key = new RSAParameters(); + uint exponentLength = (uint)data[offset++] & 0x1F; + + key.Exponent = DC.Clip(data, offset, exponentLength); + offset += exponentLength; + + uint keySize = DC.GetUInt16(data, offset); + offset += 2; + + key.Modulus = DC.Clip(data, offset, keySize); + + offset += keySize; + + // copy cert data + publicRawData = new byte[offset - oOffset]; + Buffer.BlockCopy(data, (int)oOffset, publicRawData, 0, publicRawData.Length); + + if (privateKeyIncluded) + { + + uint privateKeyLength = (keySize * 3) + (keySize / 2); + privateRawData = DC.Clip(data, offset, privateKeyLength); + + uint halfKeySize = keySize / 2; + + key.D = DC.Clip(data, offset, keySize); + offset += keySize; + key.DP = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + key.DQ = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + key.InverseQ = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + key.P = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + key.Q = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + + } + + // setup rsa + rsa = RSA.Create();// new RSACryptoServiceProvider(); + rsa.ImportParameters(key); + + this.signature = DC.Clip(data, offset, length - (offset - oOffset)); + + } + + } + + public DomainCertificate(ulong id, string domain, CACertificate authority, DateTime issueDate, + DateTime expireDate, HashFunctionType hashFunction = HashFunctionType.SHA1, uint ip = 0, byte[] ip6 = null) + : base (id, issueDate, expireDate, hashFunction) + { + // assign type + + var cr = new BinaryList(); + + // id + cr.Append(id); + + // ip + this.ip = ip; + this.ip6 = ip6; + + cr.Append(ip); + + + if (ip6?.Length == 16) + cr.Append(ip6); + else + cr.Append(new byte[16]); + + + cr.Append(issueDate, expireDate); + + // domain + this.domain = domain; + cr.Append((byte)(domain.Length), Encoding.ASCII.GetBytes(domain)); + + // CA + this.caName = authority.Name; + cr.Append((byte)(authority.Name.Length), Encoding.ASCII.GetBytes(authority.Name)); + + this.authorityName = authority.Name; + + // CA Index + //co.KeyIndex = authority.KeyIndex; + this.caId = authority.Id; + cr.Append(caId); + + + // public key + rsa = RSA.Create();// new RSACryptoServiceProvider(2048); + rsa.KeySize = 2048; + RSAParameters dRSAKey = rsa.ExportParameters(true); + cr.Append((byte)dRSAKey.Exponent.Length, dRSAKey.Exponent, (ushort)dRSAKey.Modulus.Length, dRSAKey.Modulus, AsymetricEncryptionAlgorithmType.RSA); + + + publicRawData = cr.ToArray(); + + // private key + this.privateRawData = DC.Merge(dRSAKey.D, dRSAKey.DP, dRSAKey.DQ, dRSAKey.InverseQ, dRSAKey.P, dRSAKey.Q); + + this.signature = authority.Sign(publicRawData); + + } + + public override bool Save(string filename, bool includePrivate = false) + { + try + { + if (includePrivate) + File.WriteAllBytes(filename, BinaryList.ToBytes((byte)CertificateType.DomainPrivate, publicRawData, signature, privateRawData)); + else + File.WriteAllBytes(filename, BinaryList.ToBytes((byte)CertificateType.DomainPublic, publicRawData, signature)); + + return true; + } + catch + { + return false; + } + } + + public override byte[] Serialize(bool includePrivate = false) + { + if (includePrivate) + return BinaryList.ToBytes(publicRawData, signature, privateRawData); + else + return BinaryList.ToBytes(publicRawData, signature); + } + + } +} diff --git a/Esiur/Security/Authority/HostAuthentication.cs b/Esiur/Security/Authority/HostAuthentication.cs new file mode 100644 index 0000000..eab8530 --- /dev/null +++ b/Esiur/Security/Authority/HostAuthentication.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class HostAuthentication : Authentication + { + public HostAuthentication(DomainCertificate certificate, AuthenticationState state) + : base(certificate, state, AuthenticationType.Host) + { + + } + } +} diff --git a/Esiur/Security/Authority/Session.cs b/Esiur/Security/Authority/Session.cs new file mode 100644 index 0000000..5f88ee1 --- /dev/null +++ b/Esiur/Security/Authority/Session.cs @@ -0,0 +1,23 @@ +using Esiur.Data; +using Esiur.Engine; +using Esiur.Net; +using Esiur.Resource; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class Session + { + Authentication Authentication { get; } + Source Source { get; } + string Id { get; } + DateTime Creation { get; } + DateTime Modification { get; } + //KeyList Variables { get; } + //IStore Store { get; } + } +} diff --git a/Esiur/Security/Authority/Source.cs b/Esiur/Security/Authority/Source.cs new file mode 100644 index 0000000..f89e3e4 --- /dev/null +++ b/Esiur/Security/Authority/Source.cs @@ -0,0 +1,30 @@ +using Esiur.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class Source + { + + string id; + KeyList attributes; + + string Id { get { return id; } } + + KeyList Attributes + { + get { return attributes; } + } + + public Source(string id, KeyList attributes) + { + this.id = id; + this.attributes = attributes; + } + + } +} diff --git a/Esiur/Security/Authority/SourceAttributeType.cs b/Esiur/Security/Authority/SourceAttributeType.cs new file mode 100644 index 0000000..45c66b4 --- /dev/null +++ b/Esiur/Security/Authority/SourceAttributeType.cs @@ -0,0 +1,53 @@ +using Esiur.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public enum SourceAttributeType + { + Mobility, // Stationary/Mobile + CPU, // Arc, Speed, Cores + IP, // IPv4, IPv6 Address + Route, // Trace Root + Location, // Lon, Lat, Alt, Accuracy + OS, // OS name, version, distro, kernel + Application, // lib version, app version + Network, // Bandwidth, MAC, IP, Route + Display, // Screen WxH + Media, // AudioIn, AudioOut, VideoIn, + Identity, // IMEI, IMSI, Manufacture + } + /* + public class SourceAttribute + { + SourceAttributeType type; + Structure value; + + public SourceAttributeType Type + { + get + { + return type; + } + } + + public Structure Value + { + get + { + return value; + } + } + + public SourceAttribute(SourceAttributeType type, Structure value) + { + this.type = type; + this.value = value; + } + } + */ +} diff --git a/Esiur/Security/Authority/UserCertificate.cs b/Esiur/Security/Authority/UserCertificate.cs new file mode 100644 index 0000000..874b947 --- /dev/null +++ b/Esiur/Security/Authority/UserCertificate.cs @@ -0,0 +1,230 @@ +using Esiur.Data; +using Esiur.Security.Cryptography; +using Esiur.Security.Integrity; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Authority +{ + public class UserCertificate : Certificate + { + uint ip; + byte[] ip6; + byte[] signature; + string domain; + string username; + ulong domainId; + + public ulong DomainId + { + get { return domainId; } + } + + public string Username + { + get { return username; } + } + + public string Domain + { + get { return domain; } + } + + public byte[] Signature + { + get { return signature; } + } + + public uint IPAddress + { + get { return ip; } + } + + public byte[] IPv6Address + { + get { return ip6; } + } + + public UserCertificate(byte[] data, uint offset, uint length, bool privateKeyIncluded = false) + : base(0, DateTime.MinValue, DateTime.MinValue, HashFunctionType.MD5) + { + var oOffset = offset; + + this.id = DC.GetUInt64(data, offset); + offset += 8; + + // load IPs + this.ip = DC.GetUInt32(data, offset); + offset += 4; + ip6 = DC.Clip(data, offset, 16); + offset += 16; + + this.issueDate = DC.GetDateTime(data, offset); + offset += 8; + this.expireDate = DC.GetDateTime(data, offset); + offset += 8; + + this.domainId = DC.GetUInt64(data, offset); + offset += 8; + + this.domain = Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); + offset += (uint)data[offset] + 1; + + this.username = Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); + offset += (uint)data[offset] + 1; + + // Hash Function + this.hashFunction = (HashFunctionType)(data[offset++] >> 4); + + // Public Key Encryption Algorithm + var aea = (AsymetricEncryptionAlgorithmType)(data[offset] >> 5); + + if (aea == AsymetricEncryptionAlgorithmType.RSA) + { + + var key = new RSAParameters(); + + uint exponentLength = (uint)data[offset++] & 0x1F; + + key.Exponent = DC.Clip(data, offset, exponentLength); + offset += exponentLength; + + + uint keySize = DC.GetUInt16(data, offset); + offset += 2; + + key.Modulus = DC.Clip(data, offset, keySize); + + offset += keySize; + + // copy cert data + this.publicRawData = new byte[offset - oOffset]; + Buffer.BlockCopy(data, (int)oOffset, publicRawData, 0, publicRawData.Length); + + + if (privateKeyIncluded) + { + uint privateKeyLength = (keySize * 3) + (keySize / 2); + uint halfKeySize = keySize / 2; + + this.privateRawData = DC.Clip(data, offset, privateKeyLength); + + key.D = DC.Clip(data, offset, keySize); + offset += keySize; + key.DP = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + key.DQ = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + key.InverseQ = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + key.P = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + key.Q = DC.Clip(data, offset, halfKeySize); + offset += halfKeySize; + } + + // setup rsa + this.rsa = RSA.Create();// new RSACryptoServiceProvider(); + this.rsa.ImportParameters(key); + + this.signature = DC.Clip(data, offset, length - (offset - oOffset)); + } + + + } + + public UserCertificate(ulong id, string username, DomainCertificate domainCertificate, DateTime issueDate, + DateTime expireDate, HashFunctionType hashFunction = HashFunctionType.SHA1, uint ip = 0, byte[] ip6 = null) + : base(id, issueDate, expireDate, hashFunction) + { + // assign type + var cr = new BinaryList(); + + //id + cr.Append(id); + + // ip + this.ip = ip; + this.ip6 = ip6; + + cr.Append(ip); + + + if (ip6?.Length == 16) + cr.Append(ip6); + else + cr.Append(new byte[16]); + + + // dates + this.issueDate = DateTime.UtcNow; + this.expireDate = expireDate; + + cr.Append(issueDate, expireDate); + + + // domain + this.domainId = domainCertificate.Id; + cr.Append(domainCertificate.Id); + this.domain = domainCertificate.Domain; + cr.Append((byte)domainCertificate.Domain.Length, Encoding.ASCII.GetBytes(domainCertificate.Domain)); + + + // username + this.username = username; + + cr.Append((byte)(username.Length), Encoding.ASCII.GetBytes(username)); + + // hash function (SHA1) + cr.Append((byte)((byte)hashFunction << 4));// (byte)0x10); + + // public key + + rsa = RSA.Create();// new RSACryptoServiceProvider(2048); + rsa.KeySize = 2048; + // write public certificate file + + var key = rsa.ExportParameters(true); + publicRawData = BinaryList.ToBytes((byte)key.Exponent.Length, key.Exponent, (ushort)key.Modulus.Length, key.Modulus); + + + // sign it + this.signature = domainCertificate.Sign(publicRawData); + + + // store private info + privateRawData = DC.Merge(key.D, key.DP, key.DQ, key.InverseQ, key.P, key.Q, signature); + + } + + public override bool Save(string filename, bool includePrivate = false) + { + try + { + if (includePrivate) + File.WriteAllBytes(filename, BinaryList.ToBytes((byte)CertificateType.DomainPrivate, publicRawData, signature, privateRawData)); + else + File.WriteAllBytes(filename, BinaryList.ToBytes((byte)CertificateType.DomainPublic, publicRawData, signature)); + + return true; + } + catch + { + return false; + } + } + + public override byte[] Serialize(bool includePrivate = false) + { + if (includePrivate) + return BinaryList.ToBytes(publicRawData, signature, privateRawData); + else + return BinaryList.ToBytes(publicRawData, signature); + } + } +} diff --git a/Esiur/Security/Cryptography/AsymetricEncryptionAlgorithmType.cs b/Esiur/Security/Cryptography/AsymetricEncryptionAlgorithmType.cs new file mode 100644 index 0000000..f3e745e --- /dev/null +++ b/Esiur/Security/Cryptography/AsymetricEncryptionAlgorithmType.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Cryptography +{ + // Enums + public enum AsymetricEncryptionAlgorithmType + { + RSA = 0, + DSA = 1, + ECDSA = 2 + } +} diff --git a/Esiur/Security/Cryptography/SymetricEncryptionAlgorithmType.cs b/Esiur/Security/Cryptography/SymetricEncryptionAlgorithmType.cs new file mode 100644 index 0000000..ef2d946 --- /dev/null +++ b/Esiur/Security/Cryptography/SymetricEncryptionAlgorithmType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Cryptography +{ + public enum SymetricEncryptionAlgorithmType + { + AES = 0, + Blowfish = 1, + DES = 2 + } +} diff --git a/Esiur/Security/Integrity/HashFunctionType.cs b/Esiur/Security/Integrity/HashFunctionType.cs new file mode 100644 index 0000000..9ccd7dc --- /dev/null +++ b/Esiur/Security/Integrity/HashFunctionType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Integrity +{ + public enum HashFunctionType + { + MD5 = 0, + SHA1, + SHA256, + SHA384, + SHA512 + } +} diff --git a/Esiur/Security/Membership/IDomain.cs b/Esiur/Security/Membership/IDomain.cs new file mode 100644 index 0000000..51bcc98 --- /dev/null +++ b/Esiur/Security/Membership/IDomain.cs @@ -0,0 +1,16 @@ +using Esiur.Resource; +using Esiur.Security.Authority; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Authority +{ + public interface IDomain : IResource + { + string Name { get; } + DomainCertificate Certificate { get; } + } +} diff --git a/Esiur/Security/Membership/IMembership.cs b/Esiur/Security/Membership/IMembership.cs new file mode 100644 index 0000000..e97859b --- /dev/null +++ b/Esiur/Security/Membership/IMembership.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Esiur.Data; +using Esiur.Net.IIP; +using Esiur.Engine; +using Esiur.Security.Authority; +using Esiur.Resource; + +namespace Esiur.Security.Membership +{ + public interface IMembership:IResource + { + //IUser[] GetUsers(QueryFilter user); + + //bool AddCertificate(Certificate certificate); + + //CACertificate[] GetCACertificates(string authority); + //UserCertificate[] GetUserCertificate(string user, string domain); + //DomainCertificate[] GetDomainCertificates(string domain); + + + bool UserExists(string username); + AsyncReply GetPassword(string username, string domain); + + //ClientAuthentication Authenticate(string username, byte[] credentials, int flag); + //HostAuthentication Authenticate(DomainCertificate domainCertificate); + //CoHostAuthentication Authenticate(DomainCertificate hostCertificate, int hostId); + + /* + object GetUserInfo(User user, string field); + object[] GetUserInfo(User user, string[] fields); + + bool SetUserInfo(User user, string field, object value); + bool SetUserInfo(User user, KeyList info); + */ + + //bool AddUser(User user, KeyList info); + //bool RemoveUser(string username); + + + + + + } +} diff --git a/Esiur/Security/Membership/IUser.cs b/Esiur/Security/Membership/IUser.cs new file mode 100644 index 0000000..a806ef2 --- /dev/null +++ b/Esiur/Security/Membership/IUser.cs @@ -0,0 +1,17 @@ +using Esiur.Engine; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Membership +{ + public interface IUser + { + string Username + { + get; + } + } +} diff --git a/Esiur/Security/Permissions/ActionType.cs b/Esiur/Security/Permissions/ActionType.cs new file mode 100644 index 0000000..5985bd8 --- /dev/null +++ b/Esiur/Security/Permissions/ActionType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Permissions +{ + public enum ActionType + { + Attach, + Delete, + Execute, + Get, + Set, + } +} diff --git a/Esiur/Security/Permissions/IPermissionManager.cs b/Esiur/Security/Permissions/IPermissionManager.cs new file mode 100644 index 0000000..58a372f --- /dev/null +++ b/Esiur/Security/Permissions/IPermissionManager.cs @@ -0,0 +1,18 @@ +using Esiur.Engine; +using Esiur.Net; +using Esiur.Resource; +using Esiur.Resource.Template; +using Esiur.Security.Authority; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Security.Permissions +{ + public interface IPermissionManager + { + bool Applicable(IResource resource, Session session, ActionType action, MemberTemplate member); + } +} diff --git a/Esiur/Stores/MemoryStore.cs b/Esiur/Stores/MemoryStore.cs new file mode 100644 index 0000000..dfc290b --- /dev/null +++ b/Esiur/Stores/MemoryStore.cs @@ -0,0 +1,56 @@ +using Esiur.Resource; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Esiur.Engine; + +namespace Esiur.Stores +{ + public class MemoryStore : IStore + { + public Instance Instance { get; set; } + + public event DestroyedEvent OnDestroy; + + Dictionary resources = new Dictionary(); + + public void Destroy() + { + + } + + public string Link(IResource resource) + { + if (resource.Instance.Store == this) + return this.Instance.Name + "/" + resource.Instance.Id; + + return null; + } + + public AsyncReply Get(string path) + { + return new AsyncReply(null); + } + + public bool Put(IResource resource) + { + resources.Add(resource.Instance.Id, resource); + return true; + } + + public AsyncReply Retrieve(uint iid) + { + if (resources.ContainsKey(iid)) + return new AsyncReply(resources[iid]); + else + return new AsyncReply(null); + } + + public AsyncReply Trigger(ResourceTrigger trigger) + { + return new AsyncReply(true); + } + } +} diff --git a/Test/MyMembership.cs b/Test/MyMembership.cs new file mode 100644 index 0000000..cace289 --- /dev/null +++ b/Test/MyMembership.cs @@ -0,0 +1,39 @@ +using Esiur.Data; +using Esiur.Engine; +using Esiur.Resource; +using Esiur.Security.Membership; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Test +{ + class MyMembership : IMembership + { + public Instance Instance { get; set; } + + public event DestroyedEvent OnDestroy; + + + + public void Destroy() + { + } + + public AsyncReply GetPassword(string username, string domain) + { + return new AsyncReply(DC.ToBytes("password")); + } + + public AsyncReply Trigger(ResourceTrigger trigger) + { + return new AsyncReply(true); + } + + public bool UserExists(string username) + { + throw new NotImplementedException(); + } + } + +} diff --git a/Test/MyObject.cs b/Test/MyObject.cs new file mode 100644 index 0000000..bee5cb6 --- /dev/null +++ b/Test/MyObject.cs @@ -0,0 +1,92 @@ +using Esiur.Data; +using Esiur.Engine; +using Esiur.Resource; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Test +{ + class MyObject : IResource + { + public Instance Instance { get; set; } + + public event DestroyedEvent OnDestroy; + [ResourceEvent] + public event ResourceEventHanlder LevelUp; + [ResourceEvent] + public event ResourceEventHanlder LevelDown; + + public void Destroy() + { + + } + public MyObject() + { + Info = new Structure(); + Info["size"] = 200; + Info["age"] = 30; + Info["name"] = "Zamil"; + Name = "Ahmed"; + Level = 5; + } + + public AsyncReply Trigger(ResourceTrigger trigger) + { + return new AsyncReply(); + } + + [ResourceFunction] + public int Add(int value) + { + Level += value; + LevelUp?.Invoke(null, "going up", value); + return Level; + } + + [ResourceFunction] + public int Subtract(int value) + { + Level -= value; + LevelDown?.Invoke(null, "going down", value); + return Level; + } + + [ResourceProperty] + public Structure Info + { + get; + set; + } + + [ResourceProperty] + public string Name + { + get; + set; + } + + [ResourceProperty] + public MyObject Me + { + get + { + return this; + } + } + + int level; + [ResourceProperty] + public int Level + { + get { return level; } + set + { + level = value; + Instance?.Modified(); + } + } + } + + +} diff --git a/Test/Program.cs b/Test/Program.cs new file mode 100644 index 0000000..5e71ca0 --- /dev/null +++ b/Test/Program.cs @@ -0,0 +1,152 @@ +/* + +Copyright(c) Ahmed Kh. Zamil + +All rights reserved. + +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.Engine; +using Esiur.Net.HTTP; +using Esiur.Net.IIP; +using Esiur.Net.Sockets; +using Esiur.Resource; +using Esiur.Stores; +using Esiur.Stores.MongoDB; +using System; +using System.Threading; + +namespace Test +{ + class Program + { + static MyObject myObject; + static DistributedResource remoteObject; + + static void Main(string[] args) + { + + var system = Warehouse.New("system"); + var remote = Warehouse.New("remote"); + var mongo = Warehouse.New("db"); + + Warehouse.Open().Then((ok)=> { + if (mongo.Count == 0) + myObject = Warehouse.New("my", mongo); + else + Warehouse.Get("db/my").Then((o) => { myObject = (MyObject)o; }); + + var iip = Warehouse.New("iip", system); + iip.Membership = new MyMembership(); + iip.Start(new TCPSocket(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 5000)), 600000, 60000); + + + + var http = Warehouse.New("http", system); + http.Start(new TCPSocket(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 5001)), 600000, 60000); + + var wsOverHttp = Warehouse.New("IIPoWS", system, http); + + + TestClient(); + }); + + + + + var running = true; + + while (running) + { + var cmd = Console.ReadLine(); + if (cmd.ToLower() == "exit") + Warehouse.Close().Then((x) => + { + if (!x) + Console.WriteLine("Failed to close the warehouse."); + else + Console.WriteLine("Successfully closed the warehouse."); + + running = false; + }); + else + Console.WriteLine(myObject.Name + " " + myObject.Level); + } + } + + private static void TestClient() + { + var client = new DistributedConnection(new TCPSocket("localhost", 5000), "any", "ahmed", "password"); + + var remote = Warehouse.GetStore("remote"); + + + Warehouse.Put(client, client.RemoteEndPoint.ToString(), remote); + + + client.OnReady += (c) => + { + client.Get("db/my").Then((dynamic x) => + { + remoteObject = x; + + Console.WriteLine("My Name is: " + x.Name); + x.Name = "Hamoo"; + x.LevelUp += new DistributedResourceEvent((sender, parameters) => + { + Console.WriteLine("LevelUp " + parameters[0] + " " + parameters[1]); + }); + + x.LevelDown += new DistributedResourceEvent((sender, parameters) => + { + Console.WriteLine("LevelUp " + parameters[0] + " " + parameters[1]); + }); + + (x.Add(10) as AsyncReply).Then((r) => + { + Console.WriteLine("RT: " + r + " " + x.Level); + }); + + (x.Subtract(10) as AsyncReply).Then((r) => + { + Console.WriteLine("RT: " + r + " " + x.Level); + }); + + + var t = new Timer(T_Elapsed, null, 5000, 5000); + + }); + }; + } + + private static void T_Elapsed(object state) + { + myObject.Level++; + dynamic o = remoteObject; + + + Console.WriteLine(myObject.Level + " " + o.Level + o.Me.Me.Level); + + Console.WriteLine(o.Info.ToString()); + } + } +} + diff --git a/Test/Test.csproj b/Test/Test.csproj new file mode 100644 index 0000000..b3da5bf --- /dev/null +++ b/Test/Test.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp1.1 + + + + + + + + \ No newline at end of file