Explorar el Código

Database proxy implemented. (DON'T PULL; UNTESTED)

Kenric Nugteren hace 1 año
padre
commit
655953c8c0

+ 2 - 0
prs.classes/Server/Properties/DatabaseServerProperties.cs

@@ -101,5 +101,7 @@ namespace PRSServer
             newstyle 
                 ? $"{serviceName}_RPC"
                 : $"{serviceName}_IPC";
+
+        public static string GetProxyName(string serviceName) => $"{serviceName}_Proxy";
     }
 }

+ 22 - 0
prs.classes/Server/Properties/HTTPDatabaseProxyProperties.cs

@@ -0,0 +1,22 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PRSServer
+{
+    
+    public class HTTPDatabaseProxyProperties : DatabaseProxyProperties
+    {
+        [IntegerEditor]
+        [EditorSequence(2)]
+        public int ListenPort { get; set; }
+    
+        [EditorSequence(3)]
+        [FileNameEditor("Certificate Files (*.pfx)|*.pfx")]
+        public string CertificateFile { get; set; }
+
+        public override ServerType Type() => ServerType.HTTPProxy;
+    }
+}
+

+ 12 - 0
prs.classes/Server/Properties/PipeDatabaseProxyProperties.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PRSServer
+{
+    public class PipeDatabaseProxyProperties : DatabaseProxyProperties
+    {
+        public override ServerType Type() => ServerType.PipeProxy;
+    }
+}
+

+ 21 - 0
prs.classes/Server/Properties/WebSocketDatabaseProxyProperties.cs

@@ -0,0 +1,21 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PRSServer
+{
+    public class WebSocketDatabaseProxyProperties : DatabaseProxyProperties
+    {
+        [IntegerEditor]
+        [EditorSequence(1)]
+        public int ListenPort { get; set; }
+    
+        [EditorSequence(2)]
+        [FileNameEditor("Certificate Files (*.pfx)|*.pfx")]
+        public string CertificateFile { get; set; }
+
+        public override ServerType Type() => ServerType.WebSocketProxy;
+    }
+}
+

+ 9 - 0
prs.classes/Server/ServerSettings.cs

@@ -54,6 +54,15 @@ namespace PRSServer
                 case ServerType.Certificate:
                     return Serialization.Deserialize<CertificateEngineProperties>(Properties);
                 
+                case ServerType.HTTPProxy:
+                    return Serialization.Deserialize<HTTPDatabaseProxyProperties>(Properties);
+                
+                case ServerType.WebSocketProxy:
+                    return Serialization.Deserialize<WebSocketDatabaseProxyProperties>(Properties);
+                
+                case ServerType.PipeProxy:
+                    return Serialization.Deserialize<PipeDatabaseProxyProperties>(Properties);
+                
                 default:
                     return null;
             }

+ 3 - 1
prs.classes/Server/ServerType.cs

@@ -8,7 +8,9 @@
         AutoDiscovery,
         Web,
         Certificate,
-        Proxy,
+        HTTPProxy,
+        WebSocketProxy,
+        PipeProxy,
         Other = -1
     }
 }

+ 19 - 44
prs.server/Engines/Database/DatabaseEngine.cs

@@ -25,22 +25,20 @@ public class DatabaseEngine : Engine<DatabaseServerProperties>
 {
     private Timer? _certificateRefreshTimer;
     private Timer? _certificateHaltTimer;
-    
-    private string _ipcPipeName = "";
-    private IPCServer? _ipcServer;
-    
+
     private string _rpcPipeName = "";
-    private IRpcServer? _pipeserver;
+    private InternalServer? _server;
 
-    private IRpcServer? _socketserver;
+    private string _rpcProxyName = "";
+    private IRpcServer? _pipeServer;
 
     public override void Configure(Server server)
     {
         base.Configure(server);
         Logger.Send(LogType.Information, "", "Configuring...");
 
-        _ipcPipeName = DatabaseServerProperties.GetPipeName(server.Key, false);
         _rpcPipeName = DatabaseServerProperties.GetPipeName(server.Key, true);
+        _rpcProxyName = DatabaseServerProperties.GetProxyName(server.Key);
 
         MoveUpdateFiles();
     }
@@ -222,39 +220,17 @@ public class DatabaseEngine : Engine<DatabaseServerProperties>
             _certificateRefreshTimer.Start();
         }
 
-        // Older Style Rest-Listener
-        if (Properties.Port != 0)
-        {
-            RestListener.Init((ushort)Properties.Port, certificate);
-            RestListener.Start();
-            Logger.Send(LogType.Information, "", $"- Rest Listener Started: Port={Properties.Port}");
-        }
+        _server = new InternalServer(_rpcProxyName);
+        PushManager.AddPusher(_server);
+        _server.OnLog += (type, userid, message, parameters) => Logger.Send(type, userid, message, parameters);
+        _server.Start();
 
+        var transport = new RpcServerPipeTransport(_rpcPipeName);
+        _pipeServer = new RpcServer<RpcServerPipeTransport>(transport);
+        PushManager.AddPusher(transport);
+        _pipeServer.OnLog += (type, userid, message, parameters) => Logger.Send(type, userid, message, parameters);
+        _pipeServer.Start();
 
-        // New Style Socket Listener
-        if (Properties.RPCPort != 0)
-        {
-            var sockettransport = new RpcServerSocketTransport(Properties.RPCPort); //, certificate);
-            _socketserver = new RpcServer<RpcServerSocketTransport>(sockettransport);
-            _socketserver.OnLog += (type, userid, message, parameters) => Logger.Send(type, userid, $"[S] {message}", parameters);
-            _socketserver.Start();
-            PushManager.AddPusher(sockettransport);
-            Logger.Send(LogType.Information, "", $"- RPC Listener Started: Port={Properties.RPCPort}");
-        }     
-        
-        // Older-Style Pipe (IPC Server)
-        _ipcServer = new IPCServer(_ipcPipeName);
-        _ipcServer.Start();
-        Logger.Send(LogType.Information, "", $"- IPC Pipe Listener started: Name=[{_ipcPipeName}]");
-        
-        // New Style Pipe (RPC) Listener
-        var pipetransport = new RpcServerPipeTransport(_rpcPipeName);
-        PushManager.AddPusher(pipetransport);
-        _pipeserver = new RpcServer<RpcServerPipeTransport>(pipetransport);
-        _pipeserver.OnLog += (type, userid, message, parameters) => Logger.Send(type, userid, $"[P] {message}", parameters);
-        _pipeserver.Start();
-        Logger.Send(LogType.Information, "", $"- RPC Pipe Listener started: Name=[{_rpcPipeName}]");
-        
         PushManager.AddPollHandler(PollNotifications);
         Logger.Send(LogType.Information, "", $"- Push Notifications Configured");
     }
@@ -263,13 +239,12 @@ public class DatabaseEngine : Engine<DatabaseServerProperties>
     {
         Logger.Send(LogType.Information, "", "Stopping..");
         
-        _socketserver?.Stop();
-        _socketserver = null;
-
-        _pipeserver?.Stop();
-        _pipeserver = null;
+        _server?.Stop();
+        _server?.Dispose();
+        _server = null;
         
-        _ipcServer?.Dispose();
+        _pipeServer?.Stop();
+        _pipeServer = null;
 
         RestListener.Stop();
         CredentialsCache.SaveSessionCache();

+ 3 - 2
prs.server/Engines/Database/Proxies/DatabaseProxyEngine.cs

@@ -13,6 +13,7 @@ namespace PRSServer;
 public abstract class DatabaseProxyEngine<TProperties> : Engine<TProperties>
     where TProperties : DatabaseProxyProperties
 {
+    protected IRpcClientTransport ServerTransport { get; set; }
 
     public override void Run()
     {
@@ -24,8 +25,8 @@ public abstract class DatabaseProxyEngine<TProperties> : Engine<TProperties>
             return;
         }
 
-        var transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(Properties.Server, true));
-        ClientFactory.SetClientType(typeof(RpcClient<>), Platform.LicensingEngine, Version, transport);
+        ServerTransport = new RpcClientPipeTransport(DatabaseServerProperties.GetProxyName(Properties.Server));
+        ClientFactory.SetClientType(typeof(RpcClient<>), Platform.LicensingEngine, Version, ServerTransport);
         CheckConnection();
 
         RunProxy();

+ 246 - 79
prs.server/Engines/Database/Proxies/HTTPDatabaseProxyEngine.cs

@@ -5,64 +5,48 @@ using GenHTTP.Modules.IO.Streaming;
 using GenHTTP.Modules.IO.Strings;
 using InABox.Clients;
 using InABox.Core;
+using InABox.Database;
+using InABox.Rpc;
+using InABox.Server;
 using PRSServices;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 using System.Threading.Tasks;
 using RequestMethod = GenHTTP.Api.Protocol.RequestMethod;
 
 namespace PRSServer;
 
-public class HTTPDatabaseProxyProperties : DatabaseProxyProperties
+
+public class HTTPDatabaseProxyHandlerProperties
 {
-    [IntegerEditor]
-    [EditorSequence(2)]
-    public int ListenPort { get; set; }
+    public HTTPDatabaseProxyProperties Properties { get; set; }
 
-    [EditorSequence(3)]
-    [FileNameEditor("Certificate Files (*.pfx)|*.pfx")]
-    public string CertificateFile { get; set; }
+    public IRpcClientTransport ServerTransport { get; set; }
 
-    public override ServerType Type() => ServerType.Proxy;
+    public HTTPDatabaseProxyHandlerProperties(HTTPDatabaseProxyProperties properties, IRpcClientTransport serverTransport)
+    {
+        Properties = properties;
+        ServerTransport = serverTransport;
+    }
 }
 
-internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
+internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyHandlerProperties>
 {
-    private readonly List<string> endpoints;
-    private readonly List<string> operations;
-
-    public RestHandler(IHandler parent)
-    {
-        Parent = parent;
+    private HTTPDatabaseProxyProperties Properties { get; set; }
 
-        endpoints = new();
-        operations = new();
-
-        var types = CoreUtils.TypeList(
-            x => x.IsSubclassOf(typeof(Entity))
-                 && x.GetInterfaces().Contains(typeof(IRemotable))
-        );
-        var DBTypes = DbFactory.SupportedTypes();
-
-        foreach (var t in types)
-            if (DBTypes.Contains(t.EntityName().Replace(".", "_")))
-            {
-                operations.Add(t.EntityName().Replace(".", "_"));
-
-                endpoints.Add(string.Format("List{0}", t.Name));
-                endpoints.Add(string.Format("Load{0}", t.Name));
-                endpoints.Add(string.Format("Save{0}", t.Name));
-                endpoints.Add(string.Format("MultiSave{0}", t.Name));
-                endpoints.Add(string.Format("Delete{0}", t.Name));
-                endpoints.Add(string.Format("MultiDelete{0}", t.Name));
-            }
+    public IRpcClientTransport ServerTransport { get; set; }
 
-        endpoints.Add("QueryMultiple");
+    public override void Init(HTTPDatabaseProxyHandlerProperties properties)
+    {
+        Properties = properties.Properties;
+        ServerTransport = properties.ServerTransport;
     }
     
-    private RequestData GetRequestData(IRequest request)
+    private static RequestData GetRequestData(IRequest request)
     {
         BinarySerializationSettings settings = BinarySerializationSettings.V1_0;
         if (request.Query.TryGetValue("serializationVersion", out var versionString))
@@ -89,7 +73,7 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
     /// </summary>
     /// <param name="request"></param>
     /// <returns></returns>
-    public ValueTask<IResponse?> HandleAsync(IRequest request)
+    public override ValueTask<IResponse?> HandleAsync(IRequest request)
     {
         try
         {
@@ -145,6 +129,51 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
             return new ValueTask<IResponse?>(request.Respond().Status(ResponseStatus.InternalServerError).Build());
         }
     }
+
+    private IResponseBuilder DoForward<TRequest, TResponse, TCommand, TParameters, TResult>(
+        IRequest request,
+        RequestData data,
+        Func<TRequest, TParameters> convertRequest,
+        Func<TRequest, TResult, TResponse> convertResponse
+    )
+        where TRequest : Request
+        where TResponse : Response, new()
+        where TCommand : IRpcCommand<TParameters,TResult> 
+        where TParameters : IRpcCommandParameters, ISerializeBinary
+        where TResult : IRpcCommandResult, ISerializeBinary, new()
+    {
+        var requestObj = Deserialize<TRequest>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
+
+        var internalMessage = new InternalServerMessage
+        {
+            Session = requestObj.Credentials.Session,
+            Payload = convertRequest(requestObj).WriteBinary(BinarySerializationSettings.Latest)
+        };
+
+        var serverResponse = ServerTransport.Send(typeof(TCommand).Name, internalMessage, checkErrors: false);
+
+        TResponse response;
+        if(serverResponse.Error == RpcError.NONE)
+        {
+            var result = Serialization.ReadBinary<TResult>(serverResponse.Payload, BinarySerializationSettings.Latest)
+                ?? throw new Exception($"Cannot Deserialize {typeof(TCommand).Name}");
+            response = convertResponse(requestObj, result);
+            response.Status = StatusCode.OK;
+        }
+        else
+        {
+            response = new TResponse
+            {
+                Status = serverResponse.Error switch
+                {
+                    RpcError.UNAUTHENTICATED => StatusCode.Unauthenticated,
+                    _ => StatusCode.Error
+                },
+            };
+            response.Messages.Add(Encoding.UTF8.GetString(serverResponse.Payload));
+        }
+        return SerializeResponse(request, data.ResponseFormat, data.BinarySerializationSettings, response);
+    }
     
     /// <summary>
     /// Returns the Splash Logo and Color Scheme for this Database
@@ -154,27 +183,56 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
     private IResponseBuilder GetServerInfo(IRequest request)
     {
         var data = GetRequestData(request);
-        var response = new InfoResponse(Client.Info());
-        response.Status = StatusCode.OK;
-        return SerializeResponse(request, data.ResponseFormat, data.BinarySerializationSettings, response);
+        return DoForward<InfoRequest, InfoResponse, RpcInfoCommand, RpcInfoParameters, RpcInfoResult>(
+            request, data,
+            x => new RpcInfoParameters(),
+            (r, x) => new InfoResponse
+            {
+                Info = x.Info ?? new DatabaseInfo()
+            });
     }
 
     #region Authentication
 
     private IResponseBuilder Validate(IRequest request, RequestData data)
     {
-        var requestObj = Deserialize<ValidateRequest>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
-        var response = RestService.Validate(requestObj);
-
-        return SerializeResponse(request, data.ResponseFormat, data.BinarySerializationSettings, response);
+        return DoForward<ValidateRequest, ValidateResponse, RpcValidateCommand, RpcValidateParameters, RpcValidateResult>(
+            request, data,
+            x => new RpcValidateParameters
+            {
+                UserID = x.UserID,
+                Password = x.Password,
+                PIN = x.PIN,
+                UsePIN = x.UsePIN,
+                SessionID = x.Credentials.Session,
+                Platform = x.Credentials.Platform,
+                Version = x.Credentials.Version,
+            },
+            (r, x) => new ValidateResponse
+            {
+                ValidationStatus = x.Status,
+                UserGuid = x.UserGuid,
+                UserID = x.UserID,
+                SecurityID = x.SecurityID,
+                Session = x.SessionID,
+                Recipient2FA = x.Recipient2FA,
+                PasswordExpiration = x.PasswordExpiration
+            });
     }
 
     private IResponseBuilder Check2FA(IRequest request, RequestData data)
     {
-        var requestObj = Deserialize<Check2FARequest>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
-        var response = RestService.Check2FA(requestObj);
-
-        return SerializeResponse(request, data.ResponseFormat, data.BinarySerializationSettings, response);
+        return DoForward<Check2FARequest, Check2FAResponse, RpcCheck2FACommand, RpcCheck2FAParameters, RpcCheck2FAResult>(
+            request, data,
+            x => new RpcCheck2FAParameters
+            {
+                Code = x.Code,
+                SessionId = x.Credentials.Session
+            },
+            (r, x) => new Check2FAResponse
+            {
+                Valid = x.Valid
+            });
     }
 
     #endregion
@@ -182,7 +240,7 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
     #region Database
 
     private static MethodInfo GetMethod(string name) =>
-        typeof(RestHandler).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Static)
+        typeof(HTTPDatabaseProxyHandler).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance)
         ?? throw new Exception($"Invalid method '{name}'");
 
     private static readonly List<Tuple<string, MethodInfo>> methodMap = new()
@@ -207,36 +265,148 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
         }
     }
 
-    private static QueryResponse<T> List<T>(IRequest request, RequestData data) where T : Entity, new()
+    private IResponseBuilder List<T>(IRequest request, RequestData data) where T : Entity, new()
     {
-        var requestObject = Deserialize<QueryRequest<T>>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
-        return RestService<T>.List(requestObject);
+        return DoForward<QueryRequest<T>, QueryResponse<T>, RpcQueryCommand, RpcQueryParameters, RpcQueryResult>(
+            request, data,
+            x => new RpcQueryParameters
+            {
+                Queries = new[]
+                {
+                    new RpcQueryDefinition
+                    {
+                        Key = typeof(T).Name,
+                        Type = typeof(T),
+                        Filter = x.Filter,
+                        Columns = x.Columns,
+                        Sort = x.Sort
+                    }
+                }
+            },
+            (r, x) => new QueryResponse<T>
+            {
+                Items = x.Tables[0].Table
+            });
     }
-    private static SaveResponse<T> Save<T>(IRequest request, RequestData data) where T : Entity, new()
+    private IResponseBuilder Save<T>(IRequest request, RequestData data) where T : Entity, new()
     {
-        var requestObject = Deserialize<SaveRequest<T>>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
-        return RestService<T>.Save(requestObject);
+        return DoForward<SaveRequest<T>, SaveResponse<T>, RpcSaveCommand, RpcSaveParameters, RpcSaveResult>(
+            request, data,
+            x => new RpcSaveParameters
+            {
+                AuditNote = x.AuditNote,
+                Items = new[] { x.Item },
+                Type = typeof(T)
+            },
+            (r, x) =>
+            {
+                if (r.ReturnOnlyChanged)
+                {
+                    return new SaveResponse<T>
+                    {
+                        ChangedValues = x.Deltas[0]
+                    };
+                }
+                else
+                {
+                    var deltas = x.Deltas[0];
+                    r.Item.SetObserving(false);
+                    foreach (var (key, value) in deltas)
+                    {
+                        if (CoreUtils.TryGetProperty<T>(key, out var property))
+                            CoreUtils.SetPropertyValue(deltas, key, CoreUtils.ChangeType(value, property.PropertyType));
+                    }
+                    r.Item.CommitChanges();
+                    r.Item.SetObserving(true);
+                    return new SaveResponse<T>
+                    {
+                        Item = r.Item
+                    };
+                }
+            });
     }
-    private static DeleteResponse<T> Delete<T>(IRequest request, RequestData data) where T : Entity, new()
+    private IResponseBuilder Delete<T>(IRequest request, RequestData data) where T : Entity, new()
     {
-        var requestObject = Deserialize<DeleteRequest<T>>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
-        return RestService<T>.Delete(requestObject);
+        return DoForward<DeleteRequest<T>, DeleteResponse<T>, RpcDeleteCommand, RpcDeleteParameters, RpcDeleteResult>(
+            request, data,
+            x => new RpcDeleteParameters
+            {
+                AuditNote = x.AuditNote,
+                IDs = new[] { x.Item.ID },
+                Type = typeof(T)
+            },
+            (r, x) => new DeleteResponse<T>());
     }
-    private static MultiSaveResponse<T> MultiSave<T>(IRequest request, RequestData data) where T : Entity, new()
+    private IResponseBuilder MultiSave<T>(IRequest request, RequestData data) where T : Entity, new()
     {
-        var requestObject = Deserialize<MultiSaveRequest<T>>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
-        return RestService<T>.MultiSave(requestObject);
+        return DoForward<MultiSaveRequest<T>, MultiSaveResponse<T>, RpcSaveCommand, RpcSaveParameters, RpcSaveResult>(
+            request, data,
+            x => new RpcSaveParameters
+            {
+                AuditNote = x.AuditNote,
+                Items = x.Items,
+                Type = typeof(T)
+            },
+            (r, x) =>
+            {
+                if (r.ReturnOnlyChanged)
+                {
+                    return new MultiSaveResponse<T>
+                    {
+                        ChangedValues = x.Deltas.ToList()
+                    };
+                }
+                else
+                {
+                    for (int i = 0; i < x.Deltas.Length; i++)
+                    {
+                        r.Items[i].SetObserving(false);
+                        foreach (var (key, value) in x.Deltas[i])
+                        {
+                            if (CoreUtils.TryGetProperty<T>(key, out var property))
+                                CoreUtils.SetPropertyValue(r.Items[i], key, CoreUtils.ChangeType(value, property.PropertyType));
+                        }
+                        r.Items[i].CommitChanges();
+                        r.Items[i].SetObserving(true);
+                    }
+                    return new MultiSaveResponse<T>
+                    {
+                        Items = r.Items
+                    };
+                }
+            });
     }
-    private static MultiDeleteResponse<T> MultiDelete<T>(IRequest request, RequestData data) where T : Entity, new()
+    private IResponseBuilder MultiDelete<T>(IRequest request, RequestData data) where T : Entity, new()
     {
-        var requestObject = Deserialize<MultiDeleteRequest<T>>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
-        return RestService<T>.MultiDelete(requestObject);
+        return DoForward<MultiDeleteRequest<T>, MultiDeleteResponse<T>, RpcDeleteCommand, RpcDeleteParameters, RpcDeleteResult>(
+            request, data,
+            x => new RpcDeleteParameters
+            {
+                AuditNote = x.AuditNote,
+                IDs = x.Items.Select(x => x.ID).ToArray(),
+                Type = typeof(T)
+            },
+            (r, x) => new MultiDeleteResponse<T>());
     }
-    private static MultiQueryResponse QueryMultiple(IRequest request, RequestData data)
+    private IResponseBuilder QueryMultiple(IRequest request, RequestData data)
     {
-        var requestObject = Deserialize<MultiQueryRequest>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
-
-        return RestService.QueryMultiple(requestObject, false);
+        return DoForward<MultiQueryRequest, MultiQueryResponse, RpcQueryCommand, RpcQueryParameters, RpcQueryResult>(
+            request, data,
+            x => new RpcQueryParameters
+            {
+                Queries = x.Queries.Select(x => new RpcQueryDefinition
+                {
+                    Key = x.Key,
+                    Type = CoreUtils.GetEntity(x.Value.Type),
+                    Filter = x.Value.Filter,
+                    Columns = x.Value.Columns,
+                    Sort = x.Value.Sort
+                }).ToArray()
+            },
+            (r, x) => new MultiQueryResponse
+            {
+                Tables = x.Tables.ToDictionary(x => x.Key, x => x.Table)
+            });
     }
 
     private static T Deserialize<T>(Stream? stream, SerializationFormat requestFormat, BinarySerializationSettings binarySettings, bool strict = false)
@@ -255,7 +425,7 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
         }
     }
 
-    private IResponseBuilder SerializeResponse(IRequest request, SerializationFormat responseFormat, BinarySerializationSettings binarySettings, Response? result)
+    private static IResponseBuilder SerializeResponse(IRequest request, SerializationFormat responseFormat, BinarySerializationSettings binarySettings, Response? result)
     {
         if (responseFormat == SerializationFormat.Binary && result is ISerializeBinary binary)
         {
@@ -289,7 +459,7 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
         if (endpoint.StartsWith("QueryMultiple"))
         {
             var result = QueryMultiple(request, requestData);
-            return new ValueTask<IResponse?>(SerializeResponse(request, requestData.ResponseFormat, requestData.BinarySerializationSettings, result).Build());
+            return new ValueTask<IResponse?>(result.Build());
         }
 
         foreach (var (name, method) in methodMap)
@@ -307,9 +477,9 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
                     }
 
                     var resolvedMethod = method.MakeGenericMethod(entityType);
-                    var result = resolvedMethod.Invoke(null, new object[] { request, requestData }) as Response;
+                    var result = (resolvedMethod.Invoke(null, new object[] { request, requestData }) as IResponseBuilder)!;
 
-                    return new ValueTask<IResponse?>(SerializeResponse(request, requestData.ResponseFormat, requestData.BinarySerializationSettings, result).Build());
+                    return new ValueTask<IResponse?>(result.Build());
                 }
 
                 Logger.Send(LogType.Error, request.Client.IPAddress.ToString(),
@@ -336,7 +506,6 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
 
     #region Installer
 
-    
     private IResponseBuilder GetUpdateFile(IRequest request)
     {
         var endpoint = request.Target.Current;
@@ -378,17 +547,15 @@ internal class HTTPDatabaseProxyHandler : Handler<HTTPDatabaseProxyProperties>
 
 internal class HTTPDatabaseProxyEngine : DatabaseProxyEngine<HTTPDatabaseProxyProperties>
 {
-    private Listener<HTTPDatabaseProxyHandler, HTTPDatabaseProxyProperties>? Listener;
+    private Listener<HTTPDatabaseProxyHandler, HTTPDatabaseProxyHandlerProperties>? Listener;
 
     protected override void RunProxy()
     {
-        Logger.Send(LogType.Information, "", "Registering Classes");
-
         Logger.Send(LogType.Information, "", "Starting Listener on port " + Properties.ListenPort);
 
         try
         {
-            Listener = new Listener<HTTPDatabaseProxyHandler, HTTPDatabaseProxyProperties>(Properties);
+            Listener = new Listener<HTTPDatabaseProxyHandler, HTTPDatabaseProxyHandlerProperties>(new HTTPDatabaseProxyHandlerProperties(Properties, ServerTransport));
             Listener.InitHTTPS((ushort)Properties.ListenPort, CertificateFileName());
             Listener.Start();
         }

+ 45 - 0
prs.server/Engines/Database/Proxies/PipeDatabaseProxyEngine.cs

@@ -0,0 +1,45 @@
+using InABox.Clients;
+using InABox.Core;
+using InABox.Rpc;
+using PRSServices;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRSServer;
+
+internal class PipeDatabaseProxyEngine : DatabaseProxyEngine<PipeDatabaseProxyProperties>
+{
+    private string PipeName;
+
+    RpcPipeProxyServer ProxyServer { get; set; }
+
+    public override void Configure(Server server)
+    {
+        base.Configure(server);
+
+        PipeName = DatabaseServerProperties.GetPipeName(server.Key, true);
+    }
+
+    protected override void RunProxy()
+    {
+        Logger.Send(LogType.Information, "", "Starting RPC Listener: Name=[" + PipeName + "]");
+
+        try
+        {
+            ProxyServer = new RpcPipeProxyServer(PipeName, ServerTransport);
+            ProxyServer.Start();
+        }
+        catch (Exception eListen)
+        {
+            Logger.Send(LogType.Error, ClientFactory.UserID, eListen.Message);
+        }
+    }
+
+    public override void Stop()
+    {
+        ProxyServer.Stop();
+    }
+}

+ 76 - 0
prs.server/Engines/Database/Proxies/WebSocketDatabaseProxyEngine.cs

@@ -0,0 +1,76 @@
+using InABox.Clients;
+using InABox.Core;
+using InABox.Rpc;
+using PRSServices;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRSServer;
+
+internal class WebSocketDatabaseProxyEngine : DatabaseProxyEngine<WebSocketDatabaseProxyProperties>
+{
+    private RpcSocketProxyServer ProxyServer;
+
+    private CertificateManager CertificateManager;
+
+    protected override void RunProxy()
+    {
+        Logger.Send(LogType.Information, "", "Starting Listener on port " + Properties.ListenPort);
+
+        CertificateManager = new CertificateManager(CertificateFileName());
+        CertificateManager.UpdateCertificate += CertificateManager_UpdateCertificate;
+        CertificateManager.CertificateExpiring += CertificateManager_CertificateExpiring;
+        CertificateManager.CertificateExpired += CertificateManager_CertificateExpired;
+
+        try
+        {
+            var certificate = CertificateManager.GetCertificate();
+
+            ProxyServer = new RpcSocketProxyServer(Properties.ListenPort, ServerTransport, certificate);
+            ProxyServer.Start();
+        }
+        catch (Exception eListen)
+        {
+            Logger.Send(LogType.Error, ClientFactory.UserID, eListen.Message);
+        }
+    }
+
+    private void CertificateManager_CertificateExpired()
+    {
+        Logger.Send(LogType.Information, "", "Expiry of certificate reached; restarting HTTPS listener...");
+        ProxyServer.Stop();
+        ProxyServer = new RpcSocketProxyServer(Properties.ListenPort, ServerTransport, CertificateManager.GetCertificate());
+        ProxyServer.Start();
+    }
+
+    private void CertificateManager_CertificateExpiring(DateTime expiry)
+    {
+        var message = expiry.Date == DateTime.Now.Date 
+            ? $"HTTPS Certificate for Database Engine will expire today at {expiry.TimeOfDay:hh\\:mm}" 
+            : $"HTTPS Certificate for Database Engine will expire in {(expiry - DateTime.Now).Days} at {expiry:dd/MM/yyyy hh:mm}";
+        
+        Logger.Send(LogType.Information, "DATABASE", message);
+    }
+
+    private void CertificateManager_UpdateCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate)
+    {
+        Logger.Send(LogType.Information, "DATABASE", "HTTPS Certificate with greater expiry date found; restarting HTTPS listener...");
+
+        ProxyServer.Stop();
+        ProxyServer = new RpcSocketProxyServer(Properties.ListenPort, ServerTransport, certificate);
+        ProxyServer.Start();
+    }
+
+    private string CertificateFileName() =>
+        !string.IsNullOrWhiteSpace(Properties.CertificateFile)
+        ? Properties.CertificateFile
+        : CertificateEngine.CertificateFile;
+
+    public override void Stop()
+    {
+        ProxyServer.Stop();
+    }
+}

+ 7 - 1
prs.server/Forms/ServerGrid.cs

@@ -480,6 +480,9 @@ public class ServerGrid : DynamicGrid<Server>
     {
         var menu = new ContextMenu();
         CreateMenu(menu, "Database", ServerType.Database, typeof(DatabaseServerProperties));
+        CreateMenu(menu, "WebSocket Proxy", ServerType.WebSocketProxy, typeof(WebSocketDatabaseProxyProperties));
+        CreateMenu(menu, "IPC Proxy", ServerType.PipeProxy, typeof(PipeDatabaseProxyProperties));
+        CreateMenu(menu, "REST Proxy", ServerType.HTTPProxy, typeof(HTTPDatabaseProxyProperties));
         CreateMenu(menu, "GPS Connector", ServerType.GPS, typeof(GPSServerProperties));
         if (!Data.Rows.Any(r => r.Get<Server, ServerType>(c => c.Type) == ServerType.AutoDiscovery))
             CreateMenu(menu, "Auto Discovery", ServerType.AutoDiscovery, typeof(AutoDiscoveryServerProperties));
@@ -845,7 +848,10 @@ public class ServerGrid : DynamicGrid<Server>
         { ServerType.AutoDiscovery, Properties.Resources.autodiscover.AsBitmapImage() },
         { ServerType.Schedule, Properties.Resources.schedule.AsBitmapImage() },
         { ServerType.Web, Properties.Resources.web.AsBitmapImage() },
-        { ServerType.Certificate, Properties.Resources.certificate.AsBitmapImage() }
+        { ServerType.Certificate, Properties.Resources.certificate.AsBitmapImage() },
+        { ServerType.HTTPProxy, Properties.Resources.database.AsBitmapImage() },
+        { ServerType.PipeProxy, Properties.Resources.database.AsBitmapImage() },
+        { ServerType.WebSocketProxy, Properties.Resources.database.AsBitmapImage() }
     };
 
     private BitmapImage TypeImage(CoreRow? arg)

+ 6 - 0
prs.server/Services/PRSServerService.cs

@@ -27,6 +27,12 @@ internal class PRSServerService : PRSService
             return new WebEngine();
         else if (settings.Type == ServerType.Certificate)
             return new CertificateEngine();
+        else if (settings.Type == ServerType.HTTPProxy)
+            return new HTTPDatabaseProxyEngine();
+        else if (settings.Type == ServerType.WebSocketProxy)
+            return new WebSocketDatabaseProxyEngine();
+        else if (settings.Type == ServerType.PipeProxy)
+            return new PipeDatabaseProxyEngine();
         else
             return null;
     }

+ 172 - 0
prs.services/CertificateManager.cs

@@ -0,0 +1,172 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Timers;
+using Timer = System.Timers.Timer;
+
+namespace PRSServices;
+
+public enum CertificateStatus
+{
+    Valid,
+    Expired,
+    NotFound,
+    Error
+}
+
+public class CertificateManager
+{
+    public string? CertificateFile { get; set; }
+    private X509Certificate2? Certificate;
+    private Timer? CertificateRefreshTimer;
+    private Timer? CertificateHaltTimer;
+
+    public delegate void UpdateCertificateEvent(X509Certificate2 certificate);
+    public event UpdateCertificateEvent? UpdateCertificate;
+
+    public delegate void CertificateExpiringEvent(DateTime expiry);
+    public event CertificateExpiringEvent? CertificateExpiring;
+
+    public delegate void CertificateExpiredEvent();
+    public event CertificateExpiredEvent? CertificateExpired;
+
+    public CertificateManager(string? filename = null)
+    {
+        CertificateFile = filename;
+    }
+
+    public void SetFile(string certificateFile)
+    {
+        CertificateFile = certificateFile;
+    }
+
+    public void SetCertificate(X509Certificate2 certificate)
+    {
+        Certificate = certificate;
+    }
+
+    public void Clear()
+    {
+        Certificate = null;
+    }
+
+    public void Stop()
+    {
+        CertificateRefreshTimer?.Stop();
+
+        CertificateHaltTimer?.Dispose();
+        CertificateHaltTimer = null;
+    }
+
+    public X509Certificate2? GetCertificate()
+    {
+        GetCertificate(out var certificate);
+        return certificate;
+    }
+
+    public CertificateStatus CheckCertificate()
+    {
+        return GetCertificate(out var certificate);
+    }
+
+    public CertificateStatus GetCertificate(out X509Certificate2? certificate)
+    {
+        if(Certificate != null)
+        {
+            certificate = Certificate;
+            return CertificateStatus.Valid;
+        }
+        certificate = null;
+        CertificateStatus status;
+        if (File.Exists(CertificateFile))
+        {
+            try
+            {
+                certificate = new X509Certificate2(CertificateFile);
+                if (certificate.NotAfter > DateTime.Now)
+                {
+                    Certificate = certificate;
+                    status = CertificateStatus.Valid;
+                }
+                else
+                {
+                    status = CertificateStatus.Expired;
+                    certificate = null;
+                }
+            }
+            catch (Exception)
+            {
+                status = CertificateStatus.Error;
+            }
+        }
+        else
+        {
+            status = CertificateStatus.NotFound;
+        }
+        if(certificate is not null)
+        {
+            if (CertificateRefreshTimer == null)
+            {
+                CertificateRefreshTimer = new Timer(1000 * 60 * 60 * 24);
+                CertificateRefreshTimer.Elapsed += CertificateTimer_Elapsed;
+                CertificateRefreshTimer.AutoReset = true;
+            }
+            CertificateRefreshTimer.Start();
+        }
+        return status;
+    }
+
+    #region Certificate Management
+
+    private void CertificateTimer_Elapsed(object? sender, ElapsedEventArgs e)
+    {
+        if (Certificate is not null)
+        {
+            X509Certificate2? cert = null;
+            if (File.Exists(CertificateFile))
+            {
+                cert = new X509Certificate2(CertificateFile);
+            }
+
+            if (cert != null && cert.NotAfter > Certificate.NotAfter && cert.NotAfter > DateTime.Now)
+            {
+                Certificate = cert;
+                UpdateCertificate?.Invoke(cert);
+            }
+
+            var expiry = Certificate.NotAfter;
+            var untilExpiry = expiry - DateTime.Now;
+            if (untilExpiry.TotalDays <= 7)
+            {
+                CertificateExpiring?.Invoke(expiry);
+                if (untilExpiry.TotalDays <= 1)
+                {
+                    CertificateRefreshTimer?.Stop();
+
+                    CertificateHaltTimer = new Timer(untilExpiry.TotalMilliseconds);
+                    CertificateHaltTimer.Elapsed += HTTPS_Halt_Elapsed;
+                    CertificateHaltTimer.AutoReset = false;
+                    CertificateHaltTimer.Start();
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// Restarts listener in HTTP mode
+    /// </summary>
+    /// <param name="sender"></param>
+    /// <param name="e"></param>
+    private void HTTPS_Halt_Elapsed(object? sender, ElapsedEventArgs e)
+    {
+        CertificateHaltTimer?.Dispose();
+        CertificateHaltTimer = null;
+
+        CertificateExpired?.Invoke();
+    }
+
+    #endregion
+}

+ 1 - 1
prs.services/Engine.cs

@@ -26,7 +26,7 @@ public interface IEngine
 
 public abstract class Engine<TProperties> : IEngine where TProperties : ServerProperties
 {
-    
+
     private RpcServerPipeTransport _enginemanager;
 
     public TProperties Properties { get; private set; }

+ 56 - 112
prs.services/Listener.cs

@@ -71,11 +71,8 @@ public class Listener<THandler, TProperties>
     where THandler : Handler<TProperties>, new()
     where TProperties : notnull
 {
-    private X509Certificate2? certificate;
+    private CertificateManager CertificateManager;
 
-    private Timer? CertificateRefreshTimer;
-    private Timer? CertificateHaltTimer;
-    private string? CertificateFile;
     private ushort Port;
 
     private IServerHost host;
@@ -83,9 +80,40 @@ public class Listener<THandler, TProperties>
 
     public Listener(TProperties properties)
     {
+        CertificateManager = new CertificateManager();
+        CertificateManager.UpdateCertificate += CertificateManager_UpdateCertificate;
+        CertificateManager.CertificateExpiring += CertificateManager_CertificateExpiring;
+        CertificateManager.CertificateExpired += CertificateManager_CertificateExpired;
+
         Init(properties);
     }
 
+    private void CertificateManager_CertificateExpired()
+    {
+        Logger.Send(LogType.Information, "", "Expiry of certificate reached; restarting HTTPS listener...");
+        Restart();
+    }
+
+    private void CertificateManager_CertificateExpiring(DateTime expiry)
+    {
+        string message;
+        if (expiry.Date == DateTime.Now.Date)
+        {
+            message = $"HTTPS Certificate will expire today at {expiry.TimeOfDay:hh\\:mm}";
+        }
+        else
+        {
+            message = $"HTTPS Certificate will expire in {(expiry - DateTime.Now).Days} at {expiry:dd/MM/yyyy hh:mm}";
+        }
+        Logger.Send(LogType.Information, "", message);
+    }
+
+    private void CertificateManager_UpdateCertificate(X509Certificate2 certificate)
+    {
+        Logger.Send(LogType.Information, "", "HTTPS Certificate with greater expiry date found; restarting HTTPS listener...");
+        Restart();
+    }
+
     [MemberNotNull("host", "Properties")]
     public void Init(TProperties properties)
     {
@@ -96,9 +124,8 @@ public class Listener<THandler, TProperties>
             .Defaults();
     }
 
-    public void InitCertificate(ushort port, X509Certificate2 certificate)
+    private void InitCertificate(ushort port, X509Certificate2 certificate)
     {
-        this.certificate = certificate;
         host.Bind(IPAddress.Any, port, certificate);
     }
 
@@ -112,45 +139,26 @@ public class Listener<THandler, TProperties>
     {
         Port = port;
 
-        var useHTTP = true;
-        if (File.Exists(certificateFile))
+        CertificateManager.SetFile(certificateFile);
+        var status = CertificateManager.GetCertificate(out var certificate);
+        switch (status)
         {
-            Logger.Send(LogType.Information, "", "Certificate found; verifying HTTPS Certificate");
-            try
-            {
-                var certificate = new X509Certificate2(certificateFile);
-                if (certificate.NotAfter > DateTime.Now)
-                {
-                    var names = certificate.GetNameInfo(X509NameType.DnsName, false);
-                    Logger.Send(LogType.Information, "", $"Certificate valid for {names}");
-
-                    CertificateFile = certificateFile;
-                    InitCertificate(port, certificate);
-                    useHTTP = false;
-                }
-                else
-                {
-                    Logger.Send(LogType.Error, "", "HTTPS Certificate has expired, using HTTP instead");
-                }
-            }
-            catch (Exception)
-            {
+            case CertificateStatus.Valid:
+                var names = certificate!.GetNameInfo(X509NameType.DnsName, false);
+                Logger.Send(LogType.Information, "", $"Certificate valid for {names}");
+                InitCertificate(port, certificate);
+                break;
+            case CertificateStatus.Expired:
+                Logger.Send(LogType.Error, "", "HTTPS Certificate has expired, using HTTP instead");
+                InitPort(port);
+                break;
+            case CertificateStatus.NotFound:
+                InitPort(port);
+                break;
+            case CertificateStatus.Error:
                 Logger.Send(LogType.Error, "", "Error validating HTTPS Certificate, using HTTP instead");
-            }
-        }
-        if (useHTTP)
-        {
-            InitPort(port);
-        }
-        else
-        {
-            if (CertificateRefreshTimer == null)
-            {
-                CertificateRefreshTimer = new Timer(1000 * 60 * 60 * 24);
-                CertificateRefreshTimer.Elapsed += CertificateTimer_Elapsed;
-                CertificateRefreshTimer.AutoReset = true;
-            }
-            CertificateRefreshTimer.Start();
+                InitPort(port);
+                break;
         }
     }
 
@@ -162,14 +170,16 @@ public class Listener<THandler, TProperties>
     public void Stop()
     {
         host.Stop();
+        CertificateManager.Stop();
     }
     private void Restart()
     {
         Clear();
         Init(Properties);
-        if(CertificateFile != null)
+
+        if(CertificateManager.CertificateFile != null)
         {
-            InitHTTPS(Port, CertificateFile);
+            InitHTTPS(Port, CertificateManager.CertificateFile);
         }
         else
         {
@@ -184,72 +194,6 @@ public class Listener<THandler, TProperties>
     private void Clear()
     {
         host?.Stop();
-        certificate = null;
+        CertificateManager.Clear();
     }
-
-    #region Certificate Management
-
-    private void CertificateTimer_Elapsed(object? sender, ElapsedEventArgs e)
-    {
-        if (certificate != null)
-        {
-            X509Certificate2? cert = null;
-            if (File.Exists(CertificateFile))
-            {
-                cert = new X509Certificate2(CertificateFile);
-            }
-
-            if (cert != null && cert.NotAfter > certificate.NotAfter && cert.NotAfter > DateTime.Now)
-            {
-                Logger.Send(LogType.Information, "", "HTTPS Certificate with greater expiry date found; restarting HTTPS listener...");
-                Restart();
-            }
-
-            var expiry = certificate.NotAfter;
-            var untilExpiry = expiry - DateTime.Now;
-            if (untilExpiry.TotalDays <= 7)
-            {
-                SendCertificateExpiryNotification(expiry);
-                if (untilExpiry.TotalDays <= 1)
-                {
-                    CertificateRefreshTimer?.Stop();
-
-                    CertificateHaltTimer = new Timer(untilExpiry.TotalMilliseconds);
-                    CertificateHaltTimer.Elapsed += HTTPS_Halt_Elapsed;
-                    CertificateHaltTimer.AutoReset = false;
-                    CertificateHaltTimer.Start();
-                }
-            }
-        }
-    }
-
-    private void SendCertificateExpiryNotification(DateTime expiry)
-    {
-        string message;
-        if (expiry.Date == DateTime.Now.Date)
-        {
-            message = $"HTTPS Certificate will expire today at {expiry.TimeOfDay:hh\\:mm}";
-        }
-        else
-        {
-            message = $"HTTPS Certificate will expire in {(expiry - DateTime.Now).Days} at {expiry:dd/MM/yyyy hh:mm}";
-        }
-        Logger.Send(LogType.Information, "", message);
-    }
-
-    /// <summary>
-    /// Restarts listener in HTTP mode
-    /// </summary>
-    /// <param name="sender"></param>
-    /// <param name="e"></param>
-    private void HTTPS_Halt_Elapsed(object? sender, ElapsedEventArgs e)
-    {
-        CertificateHaltTimer?.Dispose();
-        CertificateHaltTimer = null;
-
-        Logger.Send(LogType.Information, "", "Expiry of certificate reached; restarting HTTPS listener...");
-        Restart();
-    }
-
-    #endregion
 }