Kaynağa Gözat

Updated IPC Clients to use Binary Serialisation; changed QueryMultiple interface; added constructors to Request objects;
Added binary serialisation for Columns, Filters, SortOrders and expressions.
Added logic for UTC time on User passwords
Added binary serialization to notify, info and validate and check2fa.

Kenric Nugteren 2 yıl önce
ebeveyn
işleme
ff6e240f67

+ 502 - 27
InABox.Core/Client/Request.cs

@@ -3,6 +3,8 @@ using System.Collections.Generic;
 using System.IO;
 using System.Runtime.InteropServices.ComTypes;
 using InABox.Core;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
 
 namespace InABox.Clients
 {
@@ -14,7 +16,7 @@ namespace InABox.Clients
         public string? Password { get; set; }
 
         public Platform Platform { get; set; }
-        public string Version { get; set; }
+        public string? Version { get; set; }
         public Guid Session { get; set; }
 
         public void SerializeBinary(CoreBinaryWriter writer)
@@ -22,7 +24,7 @@ namespace InABox.Clients
             writer.Write(UserID ?? "");
             writer.Write(Password ?? "");
             writer.Write(Platform.ToString());
-            writer.Write(Version);
+            writer.Write(Version ?? "");
             writer.Write(Session);
         }
 
@@ -148,7 +150,7 @@ namespace InABox.Clients
         public IEnumerable<object[]> Data { get; set; }
     }*/
 
-    public class QueryRequest<TEntity> : BaseRequest<TEntity> where TEntity : Entity, new()
+    public class QueryRequest<TEntity> : BaseRequest<TEntity>, ISerializeBinary where TEntity : Entity, new()
     {
         public Filter<TEntity>? Filter { get; set; }
 
@@ -156,7 +158,39 @@ namespace InABox.Clients
 
         public SortOrder<TEntity>? Sort { get; set; }
 
+        [JsonConstructor]
+        public QueryRequest()
+        {
+            Filter = null;
+            Columns = null;
+            Sort = null;
+        }
+
+        public QueryRequest(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? sort)
+        {
+            Filter = filter;
+            Columns = columns;
+            Sort = sort;
+        }
+
         public override RequestMethod GetMethod() => RequestMethod.Query;
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.Write(Filter);
+            writer.Write(Columns);
+            writer.Write(Sort);
+        }
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            Filter = reader.ReadFilter<TEntity>();
+            Columns = reader.ReadColumns<TEntity>();
+            Sort = reader.ReadSortOrder<TEntity>();
+        }
     }
 
     public class QueryResponse<TEntity> : BaseResponse<TEntity>, ISerializeBinary where TEntity : Entity, new()
@@ -215,6 +249,19 @@ namespace InABox.Clients
         // Added 11/04/23 to address incompatibility, remove as soon as possible; the relevant code to update is in RestService; update assuming that ReturnOnlyChanged is always true.
         public bool ReturnOnlyChanged { get; set; } = false;
 
+        [JsonConstructor]
+        public MultiSaveRequest()
+        {
+            Items = Array.Empty<TEntity>();
+            AuditNote = "";
+        }
+
+        public MultiSaveRequest(TEntity[] items, string auditNode)
+        {
+            Items = items;
+            AuditNote = auditNode;
+        }
+
         public override RequestMethod GetMethod() => RequestMethod.MultiSave;
 
         public override void SerializeBinary(CoreBinaryWriter writer)
@@ -236,12 +283,54 @@ namespace InABox.Clients
         }
     }
 
-    public class MultiSaveResponse<TEntity> : BaseResponse<TEntity> where TEntity : Entity, new()
+    public class MultiSaveResponse<TEntity> : BaseResponse<TEntity>, ISerializeBinary where TEntity : Entity, new()
     {
         //public Guid[] IDs { get; set; }
-        public TEntity[] Items { get; set; }
+        public TEntity[]? Items { get; set; }
 
         public List<Dictionary<string, object?>> ChangedValues { get; set; } = new List<Dictionary<string, object?>>();
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            if(Items is null)
+            {
+                writer.Write(false);
+            }
+            else
+            {
+                writer.Write(true);
+                writer.WriteObjects(Items);
+            }
+
+            writer.Write(ChangedValues.Count);
+            foreach(var changed in ChangedValues)
+            {
+                SaveResponse<TEntity>.SerializeChangedValues(writer, changed);
+            }
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            if (reader.ReadBoolean())
+            {
+                Items = reader.ReadObjects<TEntity>().ToArray();
+            }
+            else
+            {
+                Items = null;
+            }
+
+            ChangedValues.Clear();
+            var nChangedValues = reader.ReadInt32();
+            for(int i = 0; i < nChangedValues; ++i)
+            {
+                ChangedValues.Add(SaveResponse<TEntity>.DeserializeChangedValues(reader));
+            }
+        }
     }
 
     public class SaveRequest<TEntity> : BaseRequest<TEntity>, ISerializeBinary where TEntity : Entity, new()
@@ -251,6 +340,18 @@ namespace InABox.Clients
 
         public bool ReturnOnlyChanged { get; set; } = false;
 
+        [JsonConstructor]
+        public SaveRequest()
+        {
+            // Newtonsoft should initialise Item to non-null
+        }
+
+        public SaveRequest(TEntity item, string auditNote)
+        {
+            Item = item;
+            AuditNote = auditNote;
+        }
+
         public override RequestMethod GetMethod() => RequestMethod.Save;
 
         public override void SerializeBinary(CoreBinaryWriter writer)
@@ -272,46 +373,265 @@ namespace InABox.Clients
         }
     }
 
-    public class SaveResponse<TEntity> : BaseResponse<TEntity> where TEntity : Entity, new()
+    public class SaveResponse<TEntity> : BaseResponse<TEntity>, ISerializeBinary where TEntity : Entity, new()
     {
         //public Guid ID { get; set; }
-        public TEntity Item { get; set; }
+        public TEntity? Item { get; set; }
 
         public Dictionary<string, object?> ChangedValues { get; set; } = new Dictionary<string, object?>();
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            if (Item is null)
+            {
+                writer.Write(false);
+            }
+            else
+            {
+                writer.Write(true);
+                writer.WriteObject(Item);
+            }
+            SerializeChangedValues(writer, ChangedValues);
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            if (reader.ReadBoolean())
+            {
+                Item = reader.ReadObject<TEntity>();
+            }
+            ChangedValues = DeserializeChangedValues(reader);
+        }
+
+        public static void SerializeChangedValues(CoreBinaryWriter writer, Dictionary<string, object?> changedValues)
+        {
+            var props = new List<Tuple<string, Type, object?>>();
+
+            writer.Write(changedValues.Count);
+            foreach (var (key, value) in changedValues)
+            {
+                var property = DatabaseSchema.Property(typeof(TEntity), key);
+                if (property != null)
+                {
+                    props.Add(new Tuple<string, Type, object?>(key, property.PropertyType, value));
+                }
+                else
+                {
+                    Logger.Send(LogType.Error, "", $"Error serializing changedValues: Property {key} does not exist");
+                }
+            }
+            foreach (var (key, type, value) in props)
+            {
+                writer.Write(key);
+                writer.WriteBinaryValue(type, value);
+            }
+        }
+        public static Dictionary<string, object?> DeserializeChangedValues(CoreBinaryReader reader)
+        {
+            var changedDict = new Dictionary<string, object?>();
+            var nChanged = reader.ReadInt32();
+            for (int j = 0; j < nChanged; ++j)
+            {
+                var key = reader.ReadString();
+                var property = DatabaseSchema.Property(typeof(TEntity), key);
+                if (property != null)
+                {
+                    changedDict.Add(key, reader.ReadBinaryValue(property.PropertyType));
+                }
+                else
+                {
+                    throw new Exception($"Property {key} does not exist");
+                }
+            }
+            return changedDict;
+        }
+
     }
 
-    public class DeleteRequest<TEntity> : BaseRequest<TEntity> where TEntity : Entity, new()
+    public class DeleteRequest<TEntity> : BaseRequest<TEntity>, ISerializeBinary where TEntity : Entity, new()
     {
         public TEntity Item { get; set; }
         public string AuditNote { get; set; }
 
+        [JsonConstructor]
+        public DeleteRequest()
+        {
+            // Newtonsoft should initialise Item to non-null.
+        }
+
+        public DeleteRequest(TEntity item, string auditNote)
+        {
+            Item = item;
+            AuditNote = auditNote;
+        }
+
         public override RequestMethod GetMethod() => RequestMethod.Delete;
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.WriteObject(Item);
+            writer.Write(AuditNote);
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            Item = reader.ReadObject<TEntity>();
+            AuditNote = reader.ReadString();
+        }
     }
 
-    public class DeleteResponse<TEntity> : BaseResponse<TEntity> where TEntity : Entity, new()
+    public class DeleteResponse<TEntity> : BaseResponse<TEntity>, ISerializeBinary where TEntity : Entity, new()
     {
     }
 
-    public class MultiDeleteRequest<TEntity> : BaseRequest<TEntity> where TEntity : Entity, new()
+    public class MultiDeleteRequest<TEntity> : BaseRequest<TEntity>, ISerializeBinary where TEntity : Entity, new()
     {
         public TEntity[] Items { get; set; }
         public string AuditNote { get; set; }
 
+        [JsonConstructor]
+        public MultiDeleteRequest()
+        {
+            // Newtonsoft should initialise Items to non-null.
+        }
+
+        public MultiDeleteRequest(TEntity[] items, string auditNote)
+        {
+            Items = items;
+            AuditNote = auditNote;
+        }
+
         public override RequestMethod GetMethod() => RequestMethod.MultiDelete;
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.WriteObjects(Items);
+            writer.Write(AuditNote);
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            Items = reader.ReadObjects<TEntity>().ToArray();
+            AuditNote = reader.ReadString();
+        }
     }
 
-    public class MultiDeleteResponse<TEntity> : BaseResponse<TEntity> where TEntity : Entity, new()
+    public class MultiDeleteResponse<TEntity> : BaseResponse<TEntity>, ISerializeBinary where TEntity : Entity, new()
     {
     }
 
-    public class MultiQueryRequest : Request
+    public interface IMultiQueryTableQuery
     {
-        public Dictionary<string, string> TableTypes { get; set; }
-        public Dictionary<string, string> Filters { get; set; }
-        public Dictionary<string, string> Columns { get; set; }
-        public Dictionary<string, string> Sorts { get; set; }
+        string Type { get; }
+
+        IFilter? Filter { get; set; }
+
+        IColumns? Columns { get; set; }
+
+        ISortOrder? Sort { get; set; }
+
+        void SerializeBinary(CoreBinaryWriter writer) => SerializeBinary(this, writer);
+
+        public static void SerializeBinary(IMultiQueryTableQuery query, CoreBinaryWriter writer)
+        {
+            writer.Write(query.Type);
+        }
+
+        public static IMultiQueryTableQuery DeserializeBinary(CoreBinaryReader reader)
+        {
+            var typeString = reader.ReadString();
+            var type = CoreUtils.GetEntity(typeString);
+
+            var query = (Activator.CreateInstance(typeof(MultiQueryTableQuery<>).MakeGenericType(type)) as ISerializeBinary)!;
+            query.DeserializeBinary(reader);
+
+            return (query as IMultiQueryTableQuery)!;
+        }
+    }
+
+    public class MultiQueryTableQuery<TEntity> : IMultiQueryTableQuery, ISerializeBinary
+        where TEntity : Entity, new()
+    {
+        public string Type => typeof(TEntity).EntityName();
+
+        public Filter<TEntity>? Filter { get; set; }
+
+        public Columns<TEntity>? Columns { get; set; }
+
+        public SortOrder<TEntity>? Sort { get; set; }
+
+        IFilter? IMultiQueryTableQuery.Filter { get => Filter; set => Filter = value as Filter<TEntity>; }
+        IColumns? IMultiQueryTableQuery.Columns { get => Columns; set => Columns = value as Columns<TEntity>; }
+        ISortOrder? IMultiQueryTableQuery.Sort { get => Sort; set => Sort = value as SortOrder<TEntity>; }
+
+        public void SerializeBinary(CoreBinaryWriter writer)
+        {
+            IMultiQueryTableQuery.SerializeBinary(this, writer);
+
+            writer.Write(Filter);
+            writer.Write(Columns);
+            writer.Write(Sort);
+        }
+
+        public void DeserializeBinary(CoreBinaryReader reader)
+        {
+            Filter = reader.ReadFilter<TEntity>();
+            Columns = reader.ReadColumns<TEntity>();
+            Sort = reader.ReadSortOrder<TEntity>();
+        }
+    }
+
+    public class MultiQueryRequest : Request, ISerializeBinary
+    {
+        public Dictionary<string, IMultiQueryTableQuery> Queries { get; set; } = new Dictionary<string, IMultiQueryTableQuery>();
+
+        public void AddQuery(string key, IQueryDef queryDef)
+        {
+            var query = Activator.CreateInstance(typeof(MultiQueryTableQuery<>).MakeGenericType(queryDef.Type)) as IMultiQueryTableQuery;
+            query.Filter = queryDef.Filter;
+            query.Columns = queryDef.Columns;
+            query.Sort = queryDef.SortOrder;
+            Queries.Add(key, query);
+        }
 
         public override RequestMethod GetMethod() => RequestMethod.MultiQuery;
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.Write(Queries.Count);
+            foreach(var (key, query) in Queries)
+            {
+                writer.Write(key);
+                query.SerializeBinary(writer);
+            }
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            var nQueries = reader.ReadInt32();
+            for(int i = 0; i < nQueries; ++i)
+            {
+                var key = reader.ReadString();
+                var query = IMultiQueryTableQuery.DeserializeBinary(reader);
+                Queries[key] = query;
+            }
+        }
     }
 
     public class MultiQueryResponse : Response, ISerializeBinary
@@ -360,7 +680,7 @@ namespace InABox.Clients
         PASSWORD_EXPIRED
     }
 
-    public class ValidateRequest : Request
+    public class ValidateRequest : Request, ISerializeBinary
     {
         public string? UserID { get; set; }
         public string? Password { get; set; }
@@ -371,14 +691,33 @@ namespace InABox.Clients
 
         public override RequestMethod GetMethod() => RequestMethod.Validate;
 
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.Write(UserID ?? "");
+            writer.Write(Password ?? "");
+            writer.Write(PIN ?? "");
+            writer.Write(UsePIN);
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            UserID = reader.ReadString();
+            Password = reader.ReadString();
+            PIN = reader.ReadString();
+            UsePIN = reader.ReadBoolean();
+        }
     }
-    public class ValidateResponse : Response
+    public class ValidateResponse : Response, ISerializeBinary
     {
         public ValidationResult ValidationResult { get; set; }
 
         public Guid UserGuid { get; set; }
 
-        public string UserID { get; set; }
+        public string? UserID { get; set; }
 
         public Guid SecurityID { get; set; }
 
@@ -387,50 +726,186 @@ namespace InABox.Clients
         public string? Recipient2FA { get; set; }
 
         public DateTime PasswordExpiration { get; set; }
-        
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.Write((int)ValidationResult);
+            writer.Write(UserGuid);
+            writer.Write(UserID ?? "");
+            writer.Write(SecurityID);
+            writer.Write(Session);
+            writer.Write(Recipient2FA ?? "");
+            writer.Write(PasswordExpiration.ToUniversalTime().Ticks);
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+            
+            ValidationResult = (ValidationResult)reader.ReadInt32();
+            UserGuid = reader.ReadGuid();
+            UserID = reader.ReadString();
+            SecurityID = reader.ReadGuid();
+            Session = reader.ReadGuid();
+            Recipient2FA = reader.ReadString();
+            PasswordExpiration = new DateTime(reader.ReadInt64(), DateTimeKind.Utc).ToLocalTime();
+        }
     }
 
-    public class Check2FARequest : Request
+    public class Check2FARequest : Request, ISerializeBinary
     {
         public string Code { get; set; }
 
+        [JsonConstructor]
+        public Check2FARequest()
+        {
+            Code = "";
+        }
+
+        public Check2FARequest(string code)
+        {
+            Code = code;
+        }
+
         public override RequestMethod GetMethod() => RequestMethod.Check2FA;
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.Write(Code);
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            Code = reader.ReadString();
+        }
     }
 
-    public class Check2FAResponse : Response
+    public class Check2FAResponse : Response, ISerializeBinary
     {
         public bool Valid { get; set; }
+
+        [JsonConstructor]
+        public Check2FAResponse()
+        {
+        }
+
+        public Check2FAResponse(bool valid)
+        {
+            Valid = valid;
+        }
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.Write(Valid);
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            Valid = reader.ReadBoolean();
+        }
     }
 
-    public class PingRequest : Request
+    public class PingRequest : Request, ISerializeBinary
     {
         public override RequestMethod GetMethod() => RequestMethod.Ping;
     }
 
-    public class PingResponse : Response { }
+    public class PingResponse : Response, ISerializeBinary { }
     
-    public class InfoRequest : Request
+    public class InfoRequest : Request, ISerializeBinary
     {
         public override RequestMethod GetMethod() => RequestMethod.Info;
     }
 
-    public class DatabaseInfo
+    public class DatabaseInfo : ISerializeBinary
     {
         public string? ColorScheme { get; set; }
         public byte[]? Logo { get; set; }
         public string Version { get; set; }
 
         public bool IsHTTPS { get; set; }
+
+        [JsonConstructor]
+        public DatabaseInfo()
+        {
+
+        }
+
+        public DatabaseInfo(string? colorScheme, byte[]? logo, string version, bool isHTTTPS)
+        {
+            ColorScheme = colorScheme;
+            Logo = logo;
+            Version = version;
+            IsHTTPS = isHTTTPS;
+        }
+
+        public void SerializeBinary(CoreBinaryWriter writer)
+        {
+            writer.Write(ColorScheme ?? "");
+
+            if(Logo is null)
+            {
+                writer.Write(0);
+            }
+            else
+            {
+                writer.Write(Logo.Length);
+                writer.Write(Logo);
+            }
+
+            writer.Write(Version);
+            writer.Write(IsHTTPS);
+        }
+        public void DeserializeBinary(CoreBinaryReader reader)
+        {
+            ColorScheme = reader.ReadString();
+
+            var nLogoBytes = reader.ReadInt32();
+            Logo = reader.ReadBytes(nLogoBytes);
+
+            Version = reader.ReadString();
+            IsHTTPS = reader.ReadBoolean();
+        }
     }
     
-    public class InfoResponse : Response
+    public class InfoResponse : Response, ISerializeBinary
     {
         public DatabaseInfo Info { get; set; }
 
+        [JsonConstructor]
         public InfoResponse() : base()
         {
             Info = new DatabaseInfo();
         }
+
+        public InfoResponse(DatabaseInfo info)
+        {
+            Info = info;
+        }
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            Info.SerializeBinary(writer);
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            Info.DeserializeBinary(reader);
+        }
     }
 
     public static class Extensions

+ 73 - 2
InABox.Core/Column.cs

@@ -6,6 +6,7 @@ using System.Linq.Expressions;
 using System.Reflection;
 using System.Runtime.Serialization;
 using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
 
 namespace InABox.Core
 {
@@ -16,6 +17,16 @@ namespace InABox.Core
         Type Type { get; }
     }
 
+    public static class Column
+    {
+        public static IColumn Create(Type concrete, string property)
+        {
+            var type = typeof(Column<>).MakeGenericType(concrete);
+            var result = Activator.CreateInstance(type, property) as IColumn;
+            return result!;
+        }
+    }
+
     public class Column<T> : SerializableExpression<T>, IColumn
     {
 
@@ -143,7 +154,7 @@ namespace InABox.Core
         }
     }
 
-    public class Columns<T> : IColumns
+    public class Columns<T> : IColumns, ISerializeBinary
     {
         private readonly List<Column<T>> columns;
 
@@ -184,7 +195,7 @@ namespace InABox.Core
             }
         }
 
-        public int Count => Items.Length;
+        public int Count => columns.Count;
 
         public IEnumerable<IColumn> GetColumns() => columns;
 
@@ -455,8 +466,68 @@ namespace InABox.Core
 
             return this;
         }
+
+        #region Binary Serialization
+
+        public void SerializeBinary(CoreBinaryWriter writer)
+        {
+            writer.Write(columns.Count);
+            foreach(var column in columns)
+            {
+                writer.Write(column.Property);
+            }
+        }
+        public void DeserializeBinary(CoreBinaryReader reader)
+        {
+            columns.Clear();
+            var nColumns = reader.ReadInt32();
+            for(int i = 0; i < nColumns; ++i)
+            {
+                var property = reader.ReadString();
+                columns.Add(new Column<T>(property));
+            }
+        }
+
+        #endregion
+    }
+    public static class ColumnSerialization
+    {
+        /// <summary>
+        /// Inverse of <see cref="Write{T}(CoreBinaryWriter, Columns{T}?)"/>.
+        /// </summary>
+        /// <param name="reader"></param>
+        /// <returns></returns>
+        public static Columns<T>? ReadColumns<T>(this CoreBinaryReader reader)
+        {
+            if (reader.ReadBoolean())
+            {
+                var columns = new Columns<T>();
+                columns.DeserializeBinary(reader);
+                return columns;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Inverse of <see cref="ReadColumns{T}(CoreBinaryReader)"/>.
+        /// </summary>
+        /// <param name="filter"></param>
+        /// <param name="writer"></param>
+        public static void Write<T>(this CoreBinaryWriter writer, Columns<T>? columns)
+        {
+            if (columns is null)
+            {
+                writer.Write(false);
+            }
+            else
+            {
+                writer.Write(true);
+                columns.SerializeBinary(writer);
+            }
+        }
     }
 
+
     public class ColumnJsonConverter : JsonConverter
     {
         public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)

+ 95 - 30
InABox.Core/CoreUtils.cs

@@ -3,6 +3,7 @@ using System.Collections;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.IO;
@@ -2174,9 +2175,99 @@ namespace InABox.Core
 
         #endregion
 
-        #region Expression To/From String
-        
-        
+        #region Expression Serialisation
+
+        public static string MemberExpressionToString(MemberExpression mExpression)
+        {
+            var mOriginal = mExpression;
+
+            var path = "";
+            while (true)
+            {
+                if (mExpression.Expression.NodeType == ExpressionType.MemberAccess)
+                {
+                    var propInfo = mExpression.Expression
+                        .GetType().GetTypeInfo().GetProperty("Member");
+                    var propValue = (propInfo.GetValue(mExpression.Expression, null) as PropertyInfo)!;
+                    path = propValue.Name + "." + path;
+
+                    mExpression = (mExpression.Expression as MemberExpression)!;
+                }
+                else if (mExpression.Expression.NodeType == ExpressionType.Convert)
+                {
+                    mExpression = MemberExpression.PropertyOrField((mExpression.Expression as UnaryExpression)?.Operand as MemberExpression, mExpression.Member.Name);
+                }
+                else
+                {
+                    break;
+                }
+            }
+
+            return path + mOriginal.Member.Name;
+        }
+
+        private enum SerialisedExpressionType
+        {
+            Member,
+            Index,
+            Parameter
+        }
+
+        public static void SerialiseExpression(this CoreBinaryWriter writer, Type t, Expression expression, bool includeType)
+        {
+            if (includeType)
+            {
+                writer.Write(t.EntityName());
+            }
+            if (expression.NodeType == ExpressionType.MemberAccess)
+            {
+                writer.Write((byte)SerialisedExpressionType.Member);
+                writer.Write(MemberExpressionToString((expression as MemberExpression)!));
+            }
+            else if (expression.NodeType == ExpressionType.Index)
+            {
+                writer.Write((byte)SerialisedExpressionType.Index);
+
+                var iexp = (IndexExpression)expression;
+                var sexp = expression.ToString().Split('[');
+                writer.Write(sexp[0]); // Member
+                writer.Write(sexp[1].Replace("]", "").Replace("\"", "")); // Key
+            }
+            else
+            {
+                writer.Write((byte)SerialisedExpressionType.Parameter);
+            }
+        }
+        public static Expression DeserialiseExpression(this CoreBinaryReader reader, Type t)
+        {
+            var expressionType = (SerialisedExpressionType)reader.ReadByte();
+            switch (expressionType)
+            {
+                case SerialisedExpressionType.Member:
+                    var member = reader.ReadString();
+                    return CreateMemberExpression(t, member);
+                case SerialisedExpressionType.Index:
+                    member = reader.ReadString();
+                    var key = reader.ReadString();
+                    return CreateIndexExpression(t, member, key);
+                case SerialisedExpressionType.Parameter:
+                default:
+                    return Expression.Parameter(t, "x");
+            }
+        }
+
+        /// <summary>
+        /// Deserialise the expression, with type information included in the serialised form.
+        /// </summary>
+        /// <param name="reader"></param>
+        /// <returns></returns>
+        public static Expression DeserialiseExpression(this CoreBinaryReader reader)
+        {
+            var entityName = reader.ReadString();
+            var t = GetEntity(entityName);
+            return reader.DeserialiseExpression(t);
+        }
+
         public static string ExpressionToString(Type t, Expression expression)
         {
             if (expression.NodeType == ExpressionType.MemberAccess)
@@ -2206,41 +2297,15 @@ namespace InABox.Core
             }
 
             return expression.ToString();
-
         }
         
-
         public static string ExpressionToString(Type t, Expression expression, bool includetype)
         {
             var result = new Dictionary<string, string>();
             if (expression.NodeType == ExpressionType.MemberAccess)
             {
                 var mexp = (expression as MemberExpression)!;
-                var morg = (MemberExpression)expression;
-
-                var Path = "";
-                while (true)
-                {
-                    if (mexp.Expression.NodeType == ExpressionType.MemberAccess)
-                    {
-                        var propInfo = mexp.Expression
-                            .GetType().GetTypeInfo().GetProperty("Member");
-                        var propValue = (propInfo.GetValue(mexp.Expression, null) as PropertyInfo)!;
-                        Path = propValue.Name + "." + Path;
-
-                        mexp = (mexp.Expression as MemberExpression)!;
-                    }
-                    else if(mexp.Expression.NodeType == ExpressionType.Convert)
-                    {
-                        mexp = MemberExpression.PropertyOrField((mexp.Expression as UnaryExpression)?.Operand as MemberExpression, mexp.Member.Name);
-                    }
-                    else
-                    {
-                        break;
-                    }
-                }
-
-                result["Member"] = Path + morg.Member.Name;
+                result["Member"] = MemberExpressionToString(mexp);
             }
             else if (expression.NodeType == ExpressionType.Index)
             {

+ 419 - 3
InABox.Core/Filter.cs

@@ -197,7 +197,10 @@ namespace InABox.Core
         IFilter None();
 
         string AsOData();
-        
+
+        void SerializeBinary(CoreBinaryWriter writer);
+        void DeserializeBinary(CoreBinaryReader reader);
+
         IEnumerable<string> ColumnNames();
     }
 
@@ -228,7 +231,7 @@ namespace InABox.Core
 
     }
 
-    public class Filter<T> : SerializableExpression<T>, IFilter
+    public class Filter<T> : SerializableExpression<T>, IFilter, ISerializeBinary
     {
         
         public Filter<T>? Parent { get; set; }
@@ -1015,7 +1018,383 @@ namespace InABox.Core
         }
 
         #endregion
-        
+
+        #region Binary Serialisation
+
+        private enum ValueType
+        {
+            Null,
+            Byte,
+            Short,
+            Integer,
+            Long,
+            Float,
+            Double,
+            String,
+            Boolean,
+            DateTime,
+            TimeSpan,
+            Guid,
+            FilterConstant,
+            Array,
+            SubQuery
+        }
+
+        private class ValueTypeClass
+        {
+            public ValueType ValueType;
+
+            public ValueTypeClass(ValueType valueType)
+            {
+                ValueType = valueType;
+            }
+        }
+        private class ArrayClass : ValueTypeClass
+        {
+            public ValueTypeClass ElementType;
+
+            public ArrayClass(ValueTypeClass elementType): base(ValueType.Array)
+            {
+                ElementType = elementType;
+            }
+        }
+
+        private void WriteValueType(CoreBinaryWriter writer, Type? type)
+        {
+            ValueType valueType;
+            Type? nestedType = null;
+            if (type is null)
+            {
+                valueType = ValueType.Null;
+            }
+            else if (type.IsArray)
+            {
+                // Essentially, the array ValueType will always be followed by another type which represents the element type; and if the element type is Array,
+                // it also will be followed by another ValueType.
+                valueType = ValueType.Array;
+                nestedType = type.GetElementType();
+            }
+            else if (typeof(ISubQuery).IsAssignableFrom(type))
+            {
+                valueType = ValueType.SubQuery;
+            }
+            else if (type == typeof(bool))
+            {
+                valueType = ValueType.Boolean;
+            }
+            else if (type == typeof(Guid))
+            {
+                valueType = ValueType.Guid;
+            }
+            else if (type == typeof(DateTime))
+            {
+                valueType = ValueType.DateTime;
+            }
+            else if (type == typeof(TimeSpan))
+            {
+                valueType = ValueType.TimeSpan;
+            }
+            else if (type == typeof(FilterConstant))
+            {
+                valueType = ValueType.FilterConstant;
+            }
+            else if (type.IsEnum)
+            {
+                valueType = ValueType.Integer;
+            }
+            else if (type == typeof(string))
+            {
+                valueType = ValueType.String;
+            }
+            else if (type == typeof(byte))
+            {
+                valueType = ValueType.Byte;
+            }
+            else if (type == typeof(short))
+            {
+                valueType = ValueType.Short;
+            }
+            else if (type == typeof(int))
+            {
+                valueType = ValueType.Integer;
+            }
+            else if (type == typeof(long))
+            {
+                valueType = ValueType.Long;
+            }
+            else if (type == typeof(float))
+            {
+                valueType = ValueType.Float;
+            }
+            else if (type == typeof(double))
+            {
+                valueType = ValueType.Double;
+            }
+            else
+            {
+                throw new Exception("Invalid Filter Value Type");
+            }
+
+            writer.Write((byte)valueType);
+            if(nestedType != null)
+            {
+                WriteValueType(writer, nestedType);
+            }
+        }
+        private void SerializeValue(CoreBinaryWriter writer, object? value, bool includeType)
+        {
+            if (includeType)
+            {
+                WriteValueType(writer, value?.GetType());
+            }
+            if (value is null)
+            {
+            }
+            else if (value.GetType().IsArray)
+            {
+                var arr = (value as Array)!;
+                writer.Write(arr.Length);
+                foreach (var item in arr)
+                {
+                    // Type of the array has already been figured out by the WriteValueType call.
+                    SerializeValue(writer, item, false);
+                }
+            }
+            else if(value is ISubQuery subQuery)
+            {
+                writer.Write(subQuery.GetQueryType().EntityName());
+                writer.Write(subQuery.GetColumn().Property);
+
+                var filter = subQuery.GetFilter();
+                if(filter != null)
+                {
+                    writer.Write(true);
+                    filter.SerializeBinary(writer);
+                }
+                else
+                {
+                    writer.Write(false);
+                }
+            }
+            else if (value is bool bl)
+            {
+                writer.Write(bl);
+            }
+            else if (value is Guid guid)
+            {
+                writer.Write(guid);
+            }
+            else if (value is DateTime dateTime)
+            {
+                writer.Write(dateTime.Ticks);
+            }
+            else if (value is TimeSpan timeSpan)
+            {
+                writer.Write(timeSpan.Ticks);
+            }
+            else if (value is FilterConstant f) // Filter Constant must come before Enum check.
+            {
+                writer.Write((int)f);
+            }
+            else if (value is Enum e)
+            {
+                writer.Write((int)value);
+            }
+            else if (value is string str)
+            {
+                writer.Write(str);
+            }
+            else if (value is byte b)
+            {
+                writer.Write(b);
+            }
+            else if (value is short s)
+            {
+                writer.Write(s);
+            }
+            else if (value is int i)
+            {
+                writer.Write(i);
+            }
+            else if (value is long l)
+            {
+                writer.Write(l);
+            }
+            else if (value is float f32)
+            {
+                writer.Write(f32);
+            }
+            else if (value is double f64)
+            {
+                writer.Write(f64);
+            }
+        }
+
+        private ValueTypeClass ReadValueType(CoreBinaryReader reader)
+        {
+            ValueType valueType = (ValueType)reader.ReadByte();
+
+            if(valueType == ValueType.Array)
+            {
+                return new ArrayClass(ReadValueType(reader));
+            }
+            else
+            {
+                return new ValueTypeClass(valueType);
+            }
+        }
+        private Type? ConvertToType(ValueTypeClass valueType)
+        {
+            switch (valueType.ValueType)
+            {
+                case ValueType.Null:
+                    return null;
+                case ValueType.Byte:
+                    return typeof(byte);
+                case ValueType.Short:
+                    return typeof(short);
+                case ValueType.Integer:
+                    return typeof(int);
+                case ValueType.Long:
+                    return typeof(long);
+                case ValueType.Float:
+                    return typeof(float);
+                case ValueType.Double:
+                    return typeof(double);
+                case ValueType.String:
+                    return typeof(string);
+                case ValueType.Boolean:
+                    return typeof(bool);
+                case ValueType.DateTime:
+                    return typeof(DateTime);
+                case ValueType.TimeSpan:
+                    return typeof(TimeSpan);
+                case ValueType.Guid:
+                    return typeof(Guid);
+                case ValueType.FilterConstant:
+                    return typeof(FilterConstant);
+                case ValueType.Array:
+                    var arrClass = (ArrayClass)valueType;
+                    return ConvertToType(arrClass.ElementType)?.MakeArrayType();
+                case ValueType.SubQuery:
+                    return typeof(ISubQuery);
+                default:
+                    throw new Exception($"Unknown value type {valueType.ValueType}");
+            }
+        }
+        private object? DeserializeValue(CoreBinaryReader reader, ValueTypeClass valueType)
+        {
+            switch (valueType.ValueType)
+            {
+                case ValueType.Null:
+                    return null;
+                case ValueType.Byte:
+                    return reader.ReadByte();
+                case ValueType.Short:
+                    return reader.ReadInt16();
+                case ValueType.Integer:
+                    return reader.ReadInt32();
+                case ValueType.Long:
+                    return reader.ReadInt64();
+                case ValueType.Float:
+                    return reader.ReadSingle();
+                case ValueType.Double:
+                    return reader.ReadDouble();
+                case ValueType.String:
+                    return reader.ReadString();
+                case ValueType.Boolean:
+                    return reader.ReadBoolean();
+                case ValueType.DateTime:
+                    return new DateTime(reader.ReadInt64());
+                case ValueType.TimeSpan:
+                    return new TimeSpan(reader.ReadInt64());
+                case ValueType.Guid:
+                    return reader.ReadGuid();
+                case ValueType.FilterConstant:
+                    return (FilterConstant)reader.ReadInt32();
+                case ValueType.Array:
+                    var array = (ArrayClass)valueType;
+                    var nItems = reader.ReadInt32();
+                    var arr = Array.CreateInstance(ConvertToType(array.ElementType), nItems);
+                    for (int i = 0; i < nItems; ++i)
+                    {
+                        arr.SetValue(DeserializeValue(reader, array.ElementType), i);
+                    }
+                    return arr;
+                case ValueType.SubQuery:
+                    var queryType = CoreUtils.GetEntity(reader.ReadString());
+                    var column = Column.Create(queryType, reader.ReadString());
+
+                    IFilter? filter;
+                    if (reader.ReadBoolean())
+                    {
+                        filter = Filter.Create(queryType);
+                        filter.DeserializeBinary(reader);
+                    }
+                    else
+                    {
+                        filter = null;
+                    }
+
+                    var subQuery = Activator.CreateInstance(typeof(SubQuery<>).MakeGenericType(queryType), filter, column);
+                    return subQuery;
+                default:
+                    throw new Exception($"Unknown value type {valueType.ValueType}");
+            }
+        }
+        private object? DeserializeValue(CoreBinaryReader reader)
+        {
+            var type = ReadValueType(reader);
+            return DeserializeValue(reader, type);
+        }
+
+        public void SerializeBinary(CoreBinaryWriter writer)
+        {
+            writer.SerialiseExpression(typeof(T), Expression, false);
+            writer.Write((byte)Operator);
+
+            SerializeValue(writer, Value, true);
+
+            writer.Write(Ands.Count);
+            foreach(var and in Ands)
+            {
+                and.SerializeBinary(writer);
+            }
+
+            writer.Write(Ors.Count);
+            foreach(var or in Ors)
+            {
+                or.SerializeBinary(writer);
+            }
+        }
+
+        public void DeserializeBinary(CoreBinaryReader reader)
+        {
+            Expression = reader.DeserialiseExpression(typeof(T));
+            Operator = (Operator)reader.ReadByte();
+            Value = DeserializeValue(reader);
+
+            Ands.Clear();
+            var nAnds = reader.ReadInt32();
+            for(int i = 0; i < nAnds; ++i)
+            {
+                var and = new Filter<T>();
+                and.DeserializeBinary(reader);
+                Ands.Add(and);
+            }
+
+            Ors.Clear();
+            var nOrs = reader.ReadInt32();
+            for (int i = 0; i < nOrs; ++i)
+            {
+                var or = new Filter<T>();
+                or.DeserializeBinary(reader);
+                Ors.Add(or);
+            }
+        }
+
+        #endregion
+
         #region Display Functions
 
         private static string ValueToString(object? value)
@@ -1127,6 +1506,43 @@ namespace InABox.Core
 
     }
 
+    public static class FilterSerialization
+    {
+        /// <summary>
+        /// Inverse of <see cref="FilterSerialization.Write{T}(CoreBinaryWriter, Filter{T}?)"/>.
+        /// </summary>
+        /// <param name="reader"></param>
+        /// <returns></returns>
+        public static Filter<T>? ReadFilter<T>(this CoreBinaryReader reader)
+        {
+            if (reader.ReadBoolean())
+            {
+                var filter = new Filter<T>();
+                filter.DeserializeBinary(reader);
+                return filter;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Inverse of <see cref="FilterSerialization.ReadFilter{T}(CoreBinaryReader)"/>.
+        /// </summary>
+        /// <param name="filter"></param>
+        /// <param name="writer"></param>
+        public static void Write<T>(this CoreBinaryWriter writer, Filter<T>? filter)
+        {
+            if (filter is null)
+            {
+                writer.Write(false);
+            }
+            else
+            {
+                writer.Write(true);
+                filter.SerializeBinary(writer);
+            }
+        }
+    }
+
     public class Filters<T> //where T : Entity
     {
         private readonly List<Filter<T>> filters = new List<Filter<T>>();

+ 67 - 1
InABox.Core/SortOrder.cs

@@ -38,7 +38,7 @@ namespace InABox.Core
         }
     }
     
-    public class SortOrder<T> : SerializableExpression<T>, ISortOrder // where T : Entity
+    public class SortOrder<T> : SerializableExpression<T>, ISortOrder, ISerializeBinary // where T : Entity
     {
         public SortDirection Direction { get; set; }
 
@@ -180,6 +180,72 @@ namespace InABox.Core
         }
 
         #endregion
+
+        #region Binary Serialization
+
+        public void SerializeBinary(CoreBinaryWriter writer)
+        {
+            writer.SerialiseExpression(typeof(T), Expression, false);
+            writer.Write((byte)Direction);
+            writer.Write(Thens.Count);
+            foreach (var then in Thens)
+            {
+                then.SerializeBinary(writer);
+            }
+        }
+
+        public void DeserializeBinary(CoreBinaryReader reader)
+        {
+            Expression = reader.DeserialiseExpression(typeof(T));
+            Direction = (SortDirection)reader.ReadByte();
+
+            Thens.Clear();
+            var nThens = reader.ReadInt32();
+            for(int i = 0; i < nThens; ++i)
+            {
+                var then = new SortOrder<T>();
+                then.DeserializeBinary(reader);
+                Thens.Add(then);
+            }
+        }
+
+        #endregion
+    }
+    public static class SortOrderSerialization
+    {
+        /// <summary>
+        /// Inverse of <see cref="Write{T}(CoreBinaryWriter, SortOrder{T}?)"/>.
+        /// </summary>
+        /// <param name="reader"></param>
+        /// <returns></returns>
+        public static SortOrder<T>? ReadSortOrder<T>(this CoreBinaryReader reader)
+        {
+            if (reader.ReadBoolean())
+            {
+                var sortOrder = new SortOrder<T>();
+                sortOrder.DeserializeBinary(reader);
+                return sortOrder;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Inverse of <see cref="ReadSortOrder{T}(CoreBinaryReader)"/>.
+        /// </summary>
+        /// <param name="filter"></param>
+        /// <param name="writer"></param>
+        public static void Write<T>(this CoreBinaryWriter writer, SortOrder<T>? sortOrder)
+        {
+            if (sortOrder is null)
+            {
+                writer.Write(false);
+            }
+            else
+            {
+                writer.Write(true);
+                sortOrder.SerializeBinary(writer);
+            }
+        }
     }
 
     public class SortOrderJsonConverter : JsonConverter

+ 1 - 0
InABox.Core/User/User.cs

@@ -44,6 +44,7 @@ namespace InABox.Core
         [PasswordEditor(Visible = Visible.Hidden, Editable = Editable.Enabled, ViewButtonVisible = true)]
         public virtual string Password { get; set; }
 
+        // Please note: this is a UTC time.
         [NullEditor]
         public DateTime PasswordExpiration { get; set; }
 

+ 1 - 1
InABox.Database/Stores/UserStore.cs

@@ -24,7 +24,7 @@ namespace InABox.Database
                 }
                 else
                 {
-                    entity.PasswordExpiration = DateTime.Now + PasswordExpirationTime;
+                    entity.PasswordExpiration = DateTime.UtcNow + PasswordExpirationTime;
                 }
             }
         }

+ 18 - 29
InABox.Server/Rest/RestListener.cs

@@ -15,6 +15,7 @@ using InABox.Database;
 using InABox.Remote.Shared;
 using InABox.Server.WebSocket;
 using InABox.WebSocket.Shared;
+using NPOI.POIFS.Crypt.Dsig;
 using RequestMethod = GenHTTP.Api.Protocol.RequestMethod;
 
 namespace InABox.API
@@ -70,6 +71,12 @@ namespace InABox.API
             {
                 data.RequestFormat = format;
             }
+            data.ResponseFormat = SerializationFormat.Json;
+            if (request.Query.TryGetValue("responseFormat", out formatString) && Enum.TryParse<SerializationFormat>(formatString, out format))
+            {
+                data.ResponseFormat = format;
+            }
+
             return data;
         }
 
@@ -118,7 +125,7 @@ namespace InABox.API
                                 "validate" => new ValueTask<IResponse?>(Validate(request, data).Build()),
                                 "check_2fa" => new ValueTask<IResponse?>(Check2FA(request, data).Build()),
                                 "notify" => new ValueTask<IResponse?>(GetNotify(request, data).Build()),
-                                _ => HandleDatabaseRequest(request),
+                                _ => HandleDatabaseRequest(request, data),
                             };
                         }
                         return new ValueTask<IResponse?>(request.Respond().Status(ResponseStatus.NotFound).Build());
@@ -166,13 +173,11 @@ namespace InABox.API
         /// <returns></returns>
         private IResponseBuilder GetServerInfo(IRequest request)
         {
+            var data = GetRequestData(request);
             InfoResponse response = RestService.Info(new InfoRequest());
-            var serialized = Core.Serialization.Serialize(response, true) ?? "";
-            return request.Respond()
-                .Type(new FlexibleContentType(ContentType.ApplicationJson))
-                .Content(new ResourceContent(Resource.FromString(serialized).Build()));
+            return SerializeResponse(request, data.ResponseFormat, data.BinarySerializationSettings, response);
         }
-        
+
         /// <summary>
         /// Gets port for web socket
         /// </summary>
@@ -190,10 +195,7 @@ namespace InABox.API
                 Status = StatusCode.OK,
                 SocketPort = WebSocketPort
             };
-            var serialized = Serialization.Serialize(response);
-            return request.Respond()
-                .Type(new FlexibleContentType(ContentType.ApplicationJson))
-                .Content(new ResourceContent(Resource.FromString(serialized).Build()));
+            return SerializeResponse(request, data.ResponseFormat, data.BinarySerializationSettings, response);
         }
 
         #region Authentication
@@ -203,10 +205,7 @@ namespace InABox.API
             var requestObj = Deserialize<ValidateRequest>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
             var response = RestService.Validate(requestObj);
 
-            var serialized = Serialization.Serialize(response);
-            return request.Respond()
-                .Type(FlexibleContentType.Get(ContentType.ApplicationJson))
-                .Content(new ResourceContent(Resource.FromString(serialized).Build()));
+            return SerializeResponse(request, data.ResponseFormat, data.BinarySerializationSettings, response);
         }
 
         private IResponseBuilder Check2FA(IRequest request, RequestData data)
@@ -214,10 +213,7 @@ namespace InABox.API
             var requestObj = Deserialize<Check2FARequest>(request.Content, data.RequestFormat, data.BinarySerializationSettings, true);
             var response = RestService.Check2FA(requestObj);
 
-            var serialized = Serialization.Serialize(response);
-            return request.Respond()
-                .Type(FlexibleContentType.Get(ContentType.ApplicationJson))
-                .Content(new ResourceContent(Resource.FromString(serialized).Build()));
+            return SerializeResponse(request, data.ResponseFormat, data.BinarySerializationSettings, response);
         }
 
         #endregion
@@ -240,6 +236,7 @@ namespace InABox.API
         private class RequestData
         {
             public SerializationFormat RequestFormat { get; set; }
+            public SerializationFormat ResponseFormat { get; set; }
 
             public BinarySerializationSettings BinarySerializationSettings { get; set; }
 
@@ -325,21 +322,13 @@ namespace InABox.API
         /// </summary>
         /// <param name="request"></param>
         /// <returns></returns>
-        private ValueTask<IResponse?> HandleDatabaseRequest(IRequest request)
+        private ValueTask<IResponse?> HandleDatabaseRequest(IRequest request, RequestData requestData)
         {
-            var responseFormat = SerializationFormat.Json;
-            if (request.Query.TryGetValue("responseFormat", out var formatString) && Enum.TryParse<SerializationFormat>(formatString, out var format))
-            {
-                responseFormat = format;
-            }
-
-            var requestData = GetRequestData(request);
-
             var endpoint = request.Target.Current?.Value ?? "";
             if (endpoint.StartsWith("QueryMultiple"))
             {
                 var result = QueryMultiple(request, requestData);
-                return new ValueTask<IResponse?>(SerializeResponse(request, responseFormat, requestData.BinarySerializationSettings, result).Build());
+                return new ValueTask<IResponse?>(SerializeResponse(request, requestData.ResponseFormat, requestData.BinarySerializationSettings, result).Build());
             }
 
             foreach (var (name, method) in methodMap)
@@ -359,7 +348,7 @@ namespace InABox.API
                         var resolvedMethod = method.MakeGenericMethod(entityType);
                         var result = resolvedMethod.Invoke(null, new object[] { request, requestData }) as Response;
 
-                        return new ValueTask<IResponse?>(SerializeResponse(request, responseFormat, requestData.BinarySerializationSettings, result).Build());
+                        return new ValueTask<IResponse?>(SerializeResponse(request, requestData.ResponseFormat, requestData.BinarySerializationSettings, result).Build());
                     }
 
                     Logger.Send(LogType.Error, request.Client.IPAddress.ToString(),

+ 6 - 31
InABox.Server/RestService.cs

@@ -20,15 +20,6 @@ namespace InABox.API
             //Logger.Send(LogType.Information, "", String.Format("Garbage Collection takes {0:F2}ms", (DateTime.Now - now).TotalMilliseconds));
         }
 
-        private static IQueryDef GetQuery<T>(string filterString, string columnsString, string sortString)
-            where T : Entity, IRemotable, IPersistent, new()
-        {
-            var filter = Core.Serialization.Deserialize<Filter<T>>(filterString);
-            var columns = Core.Serialization.Deserialize<Columns<T>>(columnsString);
-            var sort = Core.Serialization.Deserialize<SortOrder<T>>(sortString);
-            return new QueryDef<T>(filter, columns, sort);
-        }
-
         protected static Guid ValidateRequest(Request request, out string? userID)
         {
             var session = request.Credentials.Session;
@@ -65,28 +56,21 @@ namespace InABox.API
             Logger.Send(LogType.Information, userid, string.Format("[{0} {1}] QueryMultiple({2})",
                 request.Credentials.Platform,
                 request.Credentials.Version,
-                request.TableTypes.Count));
+                request.Queries.Count));
 
             try
             {
-                var getQueryMethod = typeof(RestService).GetMethod(nameof(RestService.GetQuery), BindingFlags.NonPublic | BindingFlags.Static);
-
                 var queries = new Dictionary<string, IQueryDef>();
-                foreach (var item in request.TableTypes)
+                foreach (var (key, query) in request.Queries)
                 {
-                    var type = CoreUtils.GetEntity(item.Value);
+                    var type = CoreUtils.GetEntity(query.Type);
                     if (type.IsAssignableTo(typeof(ISecure)) && !isSecure)
                     {
-                        Logger.Send(LogType.Error, userid, $"{type} is a secure entity. Request failed");
+                        Logger.Send(LogType.Error, userid, $"{query.Type} is a secure entity. Request failed");
                     }
                     else
                     {
-                        queries.Add(item.Key, getQueryMethod.MakeGenericMethod(type).Invoke(null, new object[]
-                        {
-                            request.Filters[item.Key],
-                            request.Columns[item.Key],
-                            request.Sorts[item.Key]
-                        }) as IQueryDef);
+                        queries.Add(key, Activator.CreateInstance(typeof(QueryDef<>).MakeGenericType(type), query.Filter, query.Columns, query.Sort) as IQueryDef);
                     }
                 }
 
@@ -223,16 +207,7 @@ namespace InABox.API
 
         public static InfoResponse Info(InfoRequest request)
         {
-            var response = new InfoResponse()
-            {
-                Info = new DatabaseInfo()
-                {
-                    ColorScheme = DbFactory.ColorScheme,
-                    Version = CoreUtils.GetVersion(),
-                    Logo = DbFactory.Logo,
-                    IsHTTPS = IsHTTPS
-                }
-            };
+            var response = new InfoResponse(new DatabaseInfo(DbFactory.ColorScheme, DbFactory.Logo, CoreUtils.GetVersion(), IsHTTPS));
             response.Status = StatusCode.OK;
             return response;
         }

+ 1 - 1
inabox.client.ipc/IPCClientFactory.cs

@@ -30,7 +30,7 @@ namespace InABox.Client.IPC
             {
                 if(request.Type is not null && CoreUtils.TryGetEntity(request.Type, out var entity))
                 {
-                    ClientFactory.Notifications.Notify(entity, Serialization.Deserialize(entity, request.Data));
+                    ClientFactory.Notifications.Notify(entity, request.GetRequest(entity));
                 }
             }
         }

+ 9 - 31
inabox.client.ipc/PipeIPCClient.cs

@@ -77,7 +77,7 @@ namespace InABox.Client.IPC
 
         protected override bool DoCheck2FA(string code, Guid? session)
         {
-            var request = new Check2FARequest { Code = code };
+            var request = new Check2FARequest(code);
 
             PrepareRequest(request);
 
@@ -120,7 +120,7 @@ namespace InABox.Client.IPC
         
         protected override void DoDelete(TEntity entity, string auditnote)
         {
-            var request = new DeleteRequest<TEntity> { Item = entity };
+            var request = new DeleteRequest<TEntity>(entity, auditnote);
             PrepareRequest(request);
 
             var response = Send(PipeRequest.Delete(request)).GetResponse<DeleteResponse<TEntity>>();
@@ -138,7 +138,7 @@ namespace InABox.Client.IPC
         protected override void DoDelete(IList<TEntity> entities, string auditnote)
         {
             var items = entities.ToArray();
-            var request = new MultiDeleteRequest<TEntity> { Items = items, AuditNote = auditnote };
+            var request = new MultiDeleteRequest<TEntity>(items, auditnote);
             PrepareRequest(request);
 
             var response = Send(PipeRequest.MultiDelete(request)).GetResponse<MultiDeleteResponse<TEntity>>();
@@ -155,11 +155,7 @@ namespace InABox.Client.IPC
 
         protected override TEntity[] DoLoad(Filter<TEntity>? filter = null, SortOrder<TEntity>? sort = null)
         {
-            var request = new QueryRequest<TEntity>
-            {
-                Filter = filter,
-                Sort = sort
-            };
+            var request = new QueryRequest<TEntity>(filter, null, sort);
             PrepareRequest(request);
 
             var result = new List<TEntity>();
@@ -172,12 +168,7 @@ namespace InABox.Client.IPC
 
         protected override CoreTable DoQuery(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? sort = null)
         {
-            var request = new QueryRequest<TEntity>
-            {
-                Columns = columns,
-                Filter = filter,
-                Sort = sort
-            };
+            var request = new QueryRequest<TEntity>(filter, columns, sort);
             PrepareRequest(request);
 
             var response = Send(PipeRequest.Query(request)).GetResponse<QueryResponse<TEntity>>();
@@ -197,19 +188,10 @@ namespace InABox.Client.IPC
 
         protected override Dictionary<string, CoreTable> DoQueryMultiple(Dictionary<string, IQueryDef> queries)
         {
-            var request = new MultiQueryRequest
-            {
-                TableTypes = new(),
-                Filters = new(),
-                Columns = new(),
-                Sorts = new()
-            };
+            var request = new MultiQueryRequest();
             foreach (var item in queries)
             {
-                request.TableTypes[item.Key] = item.Value.Type.EntityName();
-                request.Filters[item.Key] = Serialization.Serialize(item.Value.Filter);
-                request.Columns[item.Key] = Serialization.Serialize(item.Value.Columns);
-                request.Sorts[item.Key] = Serialization.Serialize(item.Value.SortOrder);
+                request.AddQuery(item.Key, item.Value);
             }
             PrepareRequest(request);
 
@@ -229,10 +211,8 @@ namespace InABox.Client.IPC
 
         protected override void DoSave(TEntity entity, string auditnote)
         {
-            var request = new SaveRequest<TEntity>
+            var request = new SaveRequest<TEntity>(entity, auditnote)
             {
-                Item = entity,
-                AuditNote = auditnote,
                 ReturnOnlyChanged = true
             };
             PrepareRequest(request);
@@ -274,10 +254,8 @@ namespace InABox.Client.IPC
         protected override void DoSave(IEnumerable<TEntity> entities, string auditnote)
         {
             var items = entities.ToArray();
-            var request = new MultiSaveRequest<TEntity>
+            var request = new MultiSaveRequest<TEntity>(items, auditnote)
             {
-                Items = items,
-                AuditNote = auditnote,
                 ReturnOnlyChanged = true
             };
             PrepareRequest(request);

+ 19 - 43
inabox.client.rest/InABox.Client.Rest/RestClient.cs

@@ -109,7 +109,7 @@ namespace InABox.Clients
                 request.Credentials.Session = session;
             }
 
-            var response = SendRequest<ValidateRequest, ValidateResponse>(request, "validate", SerializationFormat.Json, SerializationFormat.Json, false);
+            var response = SendRequest<ValidateRequest, ValidateResponse>(request, "validate", SerializationFormat.Binary, SerializationFormat.Binary, false);
             if (response != null)
                 if (response.Status.Equals(StatusCode.OK))
                 {
@@ -118,7 +118,7 @@ namespace InABox.Clients
                         var notifyRequest = new NotifyRequest();
                         // Session is required so that the server can exclude any requests from bad actors
                         notifyRequest.Credentials.Session = response.Session;
-                        var notifyResponse = SendRequest<NotifyRequest, NotifyResponse>(notifyRequest, "notify", SerializationFormat.Json, SerializationFormat.Json, false);
+                        var notifyResponse = SendRequest<NotifyRequest, NotifyResponse>(notifyRequest, "notify", SerializationFormat.Binary, SerializationFormat.Binary, false);
                         if(notifyResponse != null && notifyResponse.Status.Equals(StatusCode.OK))
                         {
                             if (notifyResponse.SocketPort.HasValue)
@@ -155,7 +155,7 @@ namespace InABox.Clients
         }
 
         protected TResponse SendRequest<TRequest, TResponse>(TRequest request, string Action, SerializationFormat requestFormat, SerializationFormat responseFormat, bool includeEntity = true)
-            where TRequest : Request, new() where TResponse : Response, new()
+            where TRequest : Request where TResponse : Response, new()
         {
 
             var result = default(TResponse);
@@ -305,16 +305,11 @@ namespace InABox.Clients
 
         protected override CoreTable DoQuery(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? sort = null)
         {
-            var request = new QueryRequest<TEntity>
-            {
-                Columns = columns,
-                Filter = filter,
-                Sort = sort
-            };
+            var request = new QueryRequest<TEntity>(filter, columns, sort);
 
             PrepareRequest(request);
 
-            var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List", SerializationFormat.Json, SerializationFormat.Binary);
+            var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List", SerializationFormat.Binary, SerializationFormat.Binary);
 
             if (response != null)
             {
@@ -337,15 +332,11 @@ namespace InABox.Clients
         protected override TEntity[] DoLoad(Filter<TEntity>? filter = null, SortOrder<TEntity>? sort = null)
         {
             var result = new List<TEntity>();
-            var request = new QueryRequest<TEntity>
-            {
-                Filter = filter,
-                Sort = sort
-            };
+            var request = new QueryRequest<TEntity>(filter, null, sort);
 
             PrepareRequest(request);
 
-            var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List", SerializationFormat.Json, SerializationFormat.Binary);
+            var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List", SerializationFormat.Binary, SerializationFormat.Binary);
             if (response.Items != null)
                 foreach (var row in response.Items.Rows)
                     result.Add(row.ToObject<TEntity>());
@@ -360,22 +351,14 @@ namespace InABox.Clients
         {
             var request = new MultiQueryRequest();
 
-            request.TableTypes = new Dictionary<string, string>();
-            request.Filters = new Dictionary<string, string>();
-            request.Columns = new Dictionary<string, string>();
-            request.Sorts = new Dictionary<string, string>();
-
             foreach (var item in queries)
             {
-                request.TableTypes[item.Key] = item.Value.Type.EntityName();
-                request.Filters[item.Key] = Serialization.Serialize(item.Value.Filter);
-                request.Columns[item.Key] = Serialization.Serialize(item.Value.Columns);
-                request.Sorts[item.Key] = Serialization.Serialize(item.Value.SortOrder);
+                request.AddQuery(item.Key, item.Value);
             }
 
             PrepareRequest(request);
 
-            var response = SendRequest<MultiQueryRequest, MultiQueryResponse>(request, "QueryMultiple", SerializationFormat.Json, SerializationFormat.Binary, false);
+            var response = SendRequest<MultiQueryRequest, MultiQueryResponse>(request, "QueryMultiple", SerializationFormat.Binary, SerializationFormat.Binary, false);
             if (response != null)
             {
                 return response.Status switch
@@ -396,14 +379,12 @@ namespace InABox.Clients
 
         protected override void DoSave(TEntity entity, string auditnote)
         {
-            var request = new SaveRequest<TEntity>();
-            request.Item = entity;
-            request.AuditNote = auditnote;
+            var request = new SaveRequest<TEntity>(entity, auditnote);
             request.ReturnOnlyChanged = true;
 
             PrepareRequest(request);
 
-            var response = SendRequest<SaveRequest<TEntity>, SaveResponse<TEntity>>(request, "Save", SerializationFormat.Binary, SerializationFormat.Json);
+            var response = SendRequest<SaveRequest<TEntity>, SaveResponse<TEntity>>(request, "Save", SerializationFormat.Binary, SerializationFormat.Binary);
             switch (response.Status)
             {
                 case StatusCode.OK:
@@ -440,14 +421,12 @@ namespace InABox.Clients
         protected override void DoSave(IEnumerable<TEntity> entities, string auditnote)
         {
             var items = entities.ToArray();
-            var request = new MultiSaveRequest<TEntity>();
-            request.Items = items;
-            request.AuditNote = auditnote;
+            var request = new MultiSaveRequest<TEntity>(items, auditnote);
             request.ReturnOnlyChanged = true;
 
             PrepareRequest(request);
 
-            var response = SendRequest<MultiSaveRequest<TEntity>, MultiSaveResponse<TEntity>>(request, "MultiSave", SerializationFormat.Binary, SerializationFormat.Json);
+            var response = SendRequest<MultiSaveRequest<TEntity>, MultiSaveResponse<TEntity>>(request, "MultiSave", SerializationFormat.Binary, SerializationFormat.Binary);
             switch (response.Status)
             {
                 case StatusCode.OK:
@@ -496,12 +475,11 @@ namespace InABox.Clients
 
         protected override void DoDelete(TEntity entity, string auditnote)
         {
-            var request = new DeleteRequest<TEntity>();
-            request.Item = entity;
+            var request = new DeleteRequest<TEntity>(entity, auditnote);
 
             PrepareRequest(request);
 
-            var response = SendRequest<DeleteRequest<TEntity>, DeleteResponse<TEntity>>(request, "Delete", SerializationFormat.Json, SerializationFormat.Json);
+            var response = SendRequest<DeleteRequest<TEntity>, DeleteResponse<TEntity>>(request, "Delete", SerializationFormat.Binary, SerializationFormat.Binary);
             switch (response.Status)
             {
                 case StatusCode.OK:
@@ -516,13 +494,11 @@ namespace InABox.Clients
         protected override void DoDelete(IList<TEntity> entities, string auditnote)
         {
             var items = entities.ToArray();
-            var request = new MultiDeleteRequest<TEntity>();
-            request.Items = items;
-            request.AuditNote = auditnote;
+            var request = new MultiDeleteRequest<TEntity>(items, auditnote);
 
             PrepareRequest(request);
 
-            var response = SendRequest<MultiDeleteRequest<TEntity>, MultiDeleteResponse<TEntity>>(request, "MultiDelete", SerializationFormat.Json, SerializationFormat.Json);
+            var response = SendRequest<MultiDeleteRequest<TEntity>, MultiDeleteResponse<TEntity>>(request, "MultiDelete", SerializationFormat.Binary, SerializationFormat.Binary);
             switch (response.Status)
             {
                 case StatusCode.OK:
@@ -540,11 +516,11 @@ namespace InABox.Clients
 
         protected override bool DoCheck2FA(string code, Guid? session)
         {
-            var request = new Check2FARequest { Code = code };
+            var request = new Check2FARequest(code);
 
             PrepareRequest(request);
 
-            var response = SendRequest<Check2FARequest, Check2FAResponse>(request, "check_2fa", SerializationFormat.Json, SerializationFormat.Json, false);
+            var response = SendRequest<Check2FARequest, Check2FAResponse>(request, "check_2fa", SerializationFormat.Binary, SerializationFormat.Binary, false);
             if (response != null)
             {
                 return response.Status switch

+ 62 - 16
inabox.ipc.shared/PipeRequest.cs

@@ -39,7 +39,8 @@ namespace InABox.IPC.Shared
         public Guid RequestID;
         public Method Method;
         public string? Type;
-        public string Data;
+        private string? Data;
+        private byte[]? BinaryData;
 
         [NonSerialized]
         public RequestError ErrorCode;
@@ -50,24 +51,69 @@ namespace InABox.IPC.Shared
             Method = method;
             Type = type;
             Data = data;
+            BinaryData = null;
+            ErrorCode = error;
+        }
+        private PipeRequest(Guid requestID, Method method, string? type, byte[] data, RequestError error = RequestError.NONE)
+        {
+            RequestID = requestID;
+            Method = method;
+            Type = type;
+            BinaryData = data;
+            Data = null;
             ErrorCode = error;
         }
 
         public PipeRequest Respond<TResponse>(TResponse response) where TResponse : Response
         {
-            return new PipeRequest(RequestID, Method.None, Type, Serialization.Serialize(response));
+            if(response is ISerializeBinary binary)
+            {
+                return new PipeRequest(RequestID, Method.None, Type, binary.WriteBinary(BinarySerializationSettings.Latest));
+            }
+            {
+                return new PipeRequest(RequestID, Method.None, Type, Serialization.Serialize(response));
+            }
+        }
+        private static PipeRequest CreateRequest(Guid requestID, Method method, string? type, object? data)
+        {
+            if (data is ISerializeBinary binary)
+            {
+                return new PipeRequest(requestID, method, type, binary.WriteBinary(BinarySerializationSettings.Latest));
+            }
+            {
+                return new PipeRequest(requestID, method, type, Serialization.Serialize(data));
+            }
         }
 
         public TRequest GetRequest<TRequest>()
         {
-            return Serialization.Deserialize<TRequest>(Data);
+            if (BinaryData is not null)
+            {
+                return (TRequest)Serialization.ReadBinary(typeof(TRequest), BinaryData, BinarySerializationSettings.Latest);
+            }
+            else
+            {
+                return Serialization.Deserialize<TRequest>(Data);
+            }
+        }
+        public object? GetRequest(Type TRequest)
+        {
+            if (BinaryData is not null)
+            {
+                return Serialization.ReadBinary(TRequest, BinaryData, BinarySerializationSettings.Latest);
+            }
+            else
+            {
+                return Serialization.Deserialize(TRequest, Data);
+            }
         }
 
         public TResponse GetResponse<TResponse>() where TResponse : Response, new()
         {
             var start = DateTime.Now;
-            var response = Serialization.Deserialize<TResponse>(Data);
-            if (response == null) response = new TResponse();
+            var response = GetRequest<TResponse>();
+
+            response ??= new TResponse();
             switch (ErrorCode)
             {
                 case RequestError.NONE:
@@ -90,42 +136,42 @@ namespace InABox.IPC.Shared
 
         public static PipeRequest Query<T>(QueryRequest<T> request) where T : Entity, new()
         {
-            return new PipeRequest(Guid.NewGuid(), Method.Query, typeof(T).Name, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.Query, typeof(T).Name, request);
         }
 
         public static PipeRequest Save<T>(SaveRequest<T> request) where T : Entity, new()
         {
-            return new PipeRequest(Guid.NewGuid(), Method.Save, typeof(T).Name, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.Save, typeof(T).Name, request);
         }
 
         public static PipeRequest MultiSave<T>(MultiSaveRequest<T> request) where T : Entity, new()
         {
-            return new PipeRequest(Guid.NewGuid(), Method.MultiSave, typeof(T).Name, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.MultiSave, typeof(T).Name, request);
         }
 
         public static PipeRequest Delete<T>(DeleteRequest<T> request) where T : Entity, new()
         {
-            return new PipeRequest(Guid.NewGuid(), Method.Delete, typeof(T).Name, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.Delete, typeof(T).Name, request);
         }
 
         public static PipeRequest MultiDelete<T>(MultiDeleteRequest<T> request) where T : Entity, new()
         {
-            return new PipeRequest(Guid.NewGuid(), Method.MultiDelete, typeof(T).Name, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.MultiDelete, typeof(T).Name, request);
         }
 
         public static PipeRequest QueryMultiple(MultiQueryRequest request)
         {
-            return new PipeRequest(Guid.NewGuid(), Method.QueryMultiple, null, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.QueryMultiple, null, request);
         }
 
         public static PipeRequest Validate(ValidateRequest request)
         {
-            return new PipeRequest(Guid.NewGuid(), Method.Validate, null, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.Validate, null, request);
         }
 
         public static PipeRequest Check2FA(Check2FARequest request)
         {
-            return new PipeRequest(Guid.NewGuid(), Method.Check2FA, null, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.Check2FA, null, request);
         }
 
         public static PipeRequest Error(RequestError error)
@@ -135,19 +181,19 @@ namespace InABox.IPC.Shared
 
         public static PipeRequest Ping(PingRequest request)
         {
-            return new PipeRequest(Guid.NewGuid(), Method.Ping, null, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.Ping, null, request);
         }
         
         public static PipeRequest Info(InfoRequest request)
         {
-            return new PipeRequest(Guid.NewGuid(), Method.Info, null, Serialization.Serialize(request));
+            return CreateRequest(Guid.NewGuid(), Method.Info, null, request);
         }
 
         public static PipeRequest Notification<TNotification>(TNotification notification) where TNotification : BaseObject
             => Notification(typeof(TNotification), notification);
         public static PipeRequest Notification(Type TNotification, BaseObject notification)
         {
-            return new PipeRequest(Guid.NewGuid(), Method.Notification, TNotification.EntityName(), Serialization.Serialize(notification));
+            return CreateRequest(Guid.NewGuid(), Method.Notification, TNotification.EntityName(), notification);
         }
     }
 }

+ 32 - 2
inabox.websocket.shared/NotifyResponse.cs

@@ -1,4 +1,5 @@
 using InABox.Clients;
+using InABox.Core;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -7,13 +8,42 @@ using System.Threading.Tasks;
 
 namespace InABox.WebSocket.Shared
 {
-    public class NotifyRequest : Request
+    public class NotifyRequest : Request, ISerializeBinary
     {
         public override RequestMethod GetMethod() => RequestMethod.Notify;
     }
 
-    public class NotifyResponse : Response
+    public class NotifyResponse : Response, ISerializeBinary
     {
         public int? SocketPort { get; set; }
+
+        public override void SerializeBinary(CoreBinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            if(SocketPort != null)
+            {
+                writer.Write(true);
+                writer.Write(SocketPort.Value);
+            }
+            else
+            {
+                writer.Write(false);
+            }
+        }
+
+        public override void DeserializeBinary(CoreBinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            if(reader.ReadBoolean())
+            {
+                SocketPort = reader.ReadInt32();
+            }
+            else
+            {
+                SocketPort = null;
+            }
+        }
     }
 }