Quellcode durchsuchen

Added binary serializer for query responses so that JSON isn't involved.

Kenric Nugteren vor 2 Jahren
Ursprung
Commit
7f20a42508

+ 2 - 0
InABox.Client.Remote.Json/InABox.Client.Remote.Json.csproj

@@ -23,4 +23,6 @@
 
     <Import Project="..\InABox.Client.Remote.Shared\InABox.Client.Remote.Shared.projitems" Label="Shared" />
 
+    <Import Project="..\InABox.Remote.Shared\InABox.Remote.Shared.projitems" Label="Shared" />
+
 </Project>

+ 29 - 21
InABox.Client.Remote.Json/JsonClient.cs

@@ -7,6 +7,7 @@ using System.Net;
 using System.Net.Http;
 using System.Text;
 using InABox.Core;
+using InABox.Remote.Shared;
 using RestSharp;
 
 //using InABox.Logging;
@@ -60,7 +61,7 @@ namespace InABox.Clients
         //        System.Net.ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3;
         //}
 
-        protected override TResponse SendRequest<TRequest, TResponse>(TRequest request, string Action, bool includeEntity = true)
+        protected override TResponse SendRequest<TRequest, TResponse>(TRequest request, string Action, SerializationFormat responseFormat, bool includeEntity = true)
         {
             //DateTime now = DateTime.Now;
             //Log("  * {0}{1}() Starting..", Action, typeof(TEntity).Name);
@@ -89,10 +90,10 @@ namespace InABox.Clients
             var uri = new Uri(string.Format("{0}:{1}", URL, Port));
             var cli = new RestClient(uri);
             var cmd = string.Format(
-                "{0}{1}?format=json{2}", 
+                "{0}{1}?format=json&responseFormat={2}", 
                 Action, 
-                includeEntity ? typeof(TEntity).Name : "", 
-                ""
+                includeEntity ? typeof(TEntity).Name : "",
+                responseFormat
             );
             var req = new RestRequest(cmd, Method.POST)
             {
@@ -106,10 +107,16 @@ namespace InABox.Clients
                 //Log("  * {0}{1}() Response from Server took {2}ms ({3} bytes)", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds, response.ContentLength);
                 //length = response.ContentLength;
                 //sw.Restart();
-                //var str = new StreamReader(stream);//.ReadToEnd();
                 try
                 {
-                    result = Serialization.Deserialize<TResponse>(stream, true);
+                    if (responseFormat == SerializationFormat.Binary && typeof(TResponse).HasInterface<ISerializeBinary>())
+                    {
+                        result = (TResponse)Serialization.ReadBinary(typeof(TResponse), stream);
+                    }
+                    else
+                    {
+                        result = Serialization.Deserialize<TResponse>(stream, true);
+                    }
                 }
                 catch (Exception e)
                 {
@@ -135,7 +142,7 @@ namespace InABox.Clients
                         
                         try
                         {
-                            var data = "";
+                            Stream stream;
 
                             if (_compression)
                             {
@@ -143,27 +150,28 @@ namespace InABox.Clients
                                 var comp = Serialization.Deserialize<CompressedResponse>(res.Content, true);
                                 var bytes = Convert.FromBase64String(comp.Response);
                                 var ms = new MemoryStream(bytes);
-                                using (var resultDeCompressedStream = new MemoryStream())
-                                {
-                                    using (var decompressionStream = new DeflateStream(ms, CompressionMode.Decompress))
-                                    {
-                                        decompressionStream.CopyTo(resultDeCompressedStream);
 
-                                        data = Encoding.UTF8.GetString(resultDeCompressedStream.ToArray(), 0,
-                                            Convert.ToInt32(resultDeCompressedStream.Length));
-                                    }
+                                stream = new MemoryStream();
+                                using (var decompressionStream = new DeflateStream(ms, CompressionMode.Decompress))
+                                {
+                                    decompressionStream.CopyTo(stream);
                                 }
-                                //Log("  * {0}{1}() Decompressing {2} -> {3} ({4:F2}%) bytes took {5}ms", Action, typeof(TEntity).Name, res.ContentLength, data.Length, ((double)data.Length *100.0F / (double)res.ContentLength), sw.ElapsedMilliseconds);
                             }
                             else
                             {
-                                data = res.Content;
+                                stream = new MemoryStream(res.RawBytes);
+                            }
+
+                            if (responseFormat == SerializationFormat.Binary && typeof(TResponse).HasInterface<ISerializeBinary>())
+                            {
+                                result = (TResponse)Serialization.ReadBinary(typeof(TResponse), stream);
+                            }
+                            else
+                            {
+                                result = Serialization.Deserialize<TResponse>(stream, true);
                             }
 
-                            //sw.Restart();
-                            result = Serialization.Deserialize<TResponse>(data, true);
-                            //Log("  * {0}{1}() Deserializing data took {2}ms", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds);
-                            //sw.Stop();                      
+                            stream.Dispose();
                         }
                         catch (Exception eDeserialize)
                         {

+ 17 - 16
InABox.Client.Remote.Shared/InABox.Client.Remote.Shared.shproj

@@ -1,18 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-    <PropertyGroup Label="Globals">
-        <ProjectGuid>803042ca-e67e-44e6-b73c-244ae5bb2a09</ProjectGuid>
-        <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
-        <ReleaseVersion>1.00</ReleaseVersion>
-        <SynchReleaseVersion>false</SynchReleaseVersion>
-    </PropertyGroup>
-    <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
-    <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props"/>
-    <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props"/>
-    <PropertyGroup/>
-    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-        <OutputPath>bin\Debug\</OutputPath>
-    </PropertyGroup>
-    <Import Project="InABox.Client.Remote.Shared.projitems" Label="Shared"/>
-    <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets"/>
-</Project>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>803042ca-e67e-44e6-b73c-244ae5bb2a09</ProjectGuid>
+    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+    <ReleaseVersion>1.00</ReleaseVersion>
+    <SynchReleaseVersion>false</SynchReleaseVersion>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
+  <PropertyGroup />
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <OutputPath>bin\Debug\</OutputPath>
+  </PropertyGroup>
+  <Import Project="InABox.Client.Remote.Shared.projitems" Label="Shared" />
+  <Import Project="..\InABox.Remote.Shared\InABox.Remote.Shared.projitems" Label="Shared" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
+</Project>

+ 12 - 11
InABox.Client.Remote.Shared/RemoteClient.cs

@@ -5,6 +5,7 @@ using System.Net;
 using System.Net.Http;
 using InABox.Client.WebSocket;
 using InABox.Core;
+using InABox.Remote.Shared;
 using InABox.WebSocket.Shared;
 using RestSharp;
 using RestSharp.Extensions;
@@ -139,7 +140,7 @@ namespace InABox.Clients
                 request.Credentials.Session = session;
             }
 
-            var response = SendRequest<ValidateRequest, ValidateResponse>(request, "validate", false);
+            var response = SendRequest<ValidateRequest, ValidateResponse>(request, "validate", SerializationFormat.Json, false);
             if (response != null)
                 if (response.Status.Equals(StatusCode.OK))
                 {
@@ -148,7 +149,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", false);
+                        var notifyResponse = SendRequest<NotifyRequest, NotifyResponse>(notifyRequest, "notify", SerializationFormat.Json, false);
                         if(notifyResponse != null && notifyResponse.Status.Equals(StatusCode.OK))
                         {
                             if (notifyResponse.SocketPort.HasValue)
@@ -184,7 +185,7 @@ namespace InABox.Clients
             );
         }
 
-        protected abstract TResponse SendRequest<TRequest, TResponse>(TRequest request, string Action, bool includeEntity = true)
+        protected abstract TResponse SendRequest<TRequest, TResponse>(TRequest request, string Action, SerializationFormat responseFormat, bool includeEntity = true)
             where TRequest : Request, new() where TResponse : Response, new();
 
         #region Query Data
@@ -200,7 +201,7 @@ namespace InABox.Clients
 
             PrepareRequest(request);
 
-            var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List");
+            var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List", SerializationFormat.Binary);
 
             if (response != null)
             {
@@ -231,7 +232,7 @@ namespace InABox.Clients
 
             PrepareRequest(request);
 
-            var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List");
+            var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List", SerializationFormat.Binary);
             if (response.Items != null)
                 foreach (var row in response.Items.Rows)
                     result.Add(row.ToObject<TEntity>());
@@ -261,7 +262,7 @@ namespace InABox.Clients
 
             PrepareRequest(request);
 
-            var response = SendRequest<MultiQueryRequest, MultiQueryResponse>(request, "QueryMultiple", false);
+            var response = SendRequest<MultiQueryRequest, MultiQueryResponse>(request, "QueryMultiple", SerializationFormat.Binary, false);
             if (response != null)
             {
                 return response.Status switch
@@ -288,7 +289,7 @@ namespace InABox.Clients
 
             PrepareRequest(request);
 
-            var response = SendRequest<SaveRequest<TEntity>, SaveResponse<TEntity>>(request, "Save");
+            var response = SendRequest<SaveRequest<TEntity>, SaveResponse<TEntity>>(request, "Save", SerializationFormat.Json);
             switch (response.Status)
             {
                 case StatusCode.OK:
@@ -319,7 +320,7 @@ namespace InABox.Clients
 
             PrepareRequest(request);
 
-            var response = SendRequest<MultiSaveRequest<TEntity>, MultiSaveResponse<TEntity>>(request, "MultiSave");
+            var response = SendRequest<MultiSaveRequest<TEntity>, MultiSaveResponse<TEntity>>(request, "MultiSave", SerializationFormat.Json);
             switch (response.Status)
             {
                 case StatusCode.OK:
@@ -356,7 +357,7 @@ namespace InABox.Clients
 
             PrepareRequest(request);
 
-            var response = SendRequest<DeleteRequest<TEntity>, DeleteResponse<TEntity>>(request, "Delete");
+            var response = SendRequest<DeleteRequest<TEntity>, DeleteResponse<TEntity>>(request, "Delete", SerializationFormat.Json);
             switch (response.Status)
             {
                 case StatusCode.OK:
@@ -377,7 +378,7 @@ namespace InABox.Clients
 
             PrepareRequest(request);
 
-            var response = SendRequest<MultiDeleteRequest<TEntity>, MultiDeleteResponse<TEntity>>(request, "MultiDelete");
+            var response = SendRequest<MultiDeleteRequest<TEntity>, MultiDeleteResponse<TEntity>>(request, "MultiDelete", SerializationFormat.Json);
             switch (response.Status)
             {
                 case StatusCode.OK:
@@ -399,7 +400,7 @@ namespace InABox.Clients
 
             PrepareRequest(request);
 
-            var response = SendRequest<Check2FARequest, Check2FAResponse>(request, "check_2fa", false);
+            var response = SendRequest<Check2FARequest, Check2FAResponse>(request, "check_2fa", SerializationFormat.Json, false);
             if (response != null)
             {
                 return response.Status switch

+ 88 - 4
InABox.Core/Client/Request.cs

@@ -1,8 +1,8 @@
 using System;
 using System.Collections.Generic;
-using System.Net;
+using System.IO;
+using System.Runtime.InteropServices.ComTypes;
 using InABox.Core;
-using Newtonsoft.Json;
 
 namespace InABox.Clients
 {
@@ -69,6 +69,27 @@ namespace InABox.Clients
 
         public List<string> Messages { get; }
 
+        public virtual void DeserializeBinary(BinaryReader reader)
+        {
+            Status = (StatusCode)Enum.ToObject(typeof(StatusCode), reader.ReadInt32());
+            Messages.Clear();
+            var nMessages = reader.ReadInt32();
+            for (int i = 0; i < nMessages; ++i)
+            {
+                Messages.Add(reader.ReadString());
+            }
+        }
+
+        public virtual void SerializeBinary(BinaryWriter writer)
+        {
+            writer.Write((int)Status);
+
+            writer.Write(Messages.Count);
+            foreach(var message in Messages)
+            {
+                writer.Write(message);
+            }
+        }
     }
 
     public abstract class BaseRequest<TEntity> : Request where TEntity : Entity, new()
@@ -106,9 +127,39 @@ namespace InABox.Clients
         public override RequestMethod GetMethod() => RequestMethod.Query;
     }
 
-    public class QueryResponse<TEntity> : BaseResponse<TEntity> where TEntity : Entity, new()
+    public class QueryResponse<TEntity> : BaseResponse<TEntity>, ISerializeBinary where TEntity : Entity, new()
     {
         public CoreTable? Items { get; set; }
+
+        public override void DeserializeBinary(BinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            if (reader.ReadBoolean())
+            {
+                Items = new CoreTable();
+                Items.DeserializeBinary(reader);
+            }
+            else
+            {
+                Items = null;
+            }
+        }
+
+        public override void SerializeBinary(BinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            if(Items is null)
+            {
+                writer.Write(false);
+            }
+            else
+            {
+                writer.Write(true);
+                Items.SerializeBinary(writer);
+            }
+        }
     }
 
     /*public class LoadRequest<TEntity> : BaseRequest<TEntity> where TEntity : Entity, new()
@@ -185,9 +236,42 @@ namespace InABox.Clients
         public override RequestMethod GetMethod() => RequestMethod.MultiQuery;
     }
 
-    public class MultiQueryResponse : Response
+    public class MultiQueryResponse : Response, ISerializeBinary
     {
+        public MultiQueryResponse()
+        {
+            Tables = new Dictionary<string, CoreTable>();
+        }
+
         public Dictionary<string, CoreTable> Tables { get; set; }
+
+        public override void DeserializeBinary(BinaryReader reader)
+        {
+            base.DeserializeBinary(reader);
+
+            Tables.Clear();
+
+            var nTables = reader.ReadInt32();
+            for(int i = 0; i < nTables; ++i)
+            {
+                var name = reader.ReadString();
+                var table = new CoreTable();
+                table.DeserializeBinary(reader);
+                Tables[name] = table;
+            }
+        }
+
+        public override void SerializeBinary(BinaryWriter writer)
+        {
+            base.SerializeBinary(writer);
+
+            writer.Write(Tables.Count);
+            foreach(var (name, table) in Tables)
+            {
+                writer.Write(name);
+                table.SerializeBinary(writer);
+            }
+        }
     }
 
     public enum ValidationResult

+ 239 - 2
InABox.Core/DataTable.cs

@@ -2,6 +2,8 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
+using System.Drawing;
+using System.IO;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Reflection;
@@ -19,6 +21,13 @@ namespace InABox.Core
         {
             DataType = typeof(object);
         }
+        public CoreColumn(string columnName) : this(typeof(object), columnName) { }
+
+        public CoreColumn(Type dataType, string columnName)
+        {
+            DataType = dataType;
+            ColumnName = columnName;
+        }
 
         public Type DataType { get; set; }
 
@@ -28,6 +37,7 @@ namespace InABox.Core
         {
             return string.Format("{0} ({1})", ColumnName, DataType.EntityName().Split('.').Last());
         }
+
     }
 
     [Serializable]
@@ -254,6 +264,7 @@ namespace InABox.Core
             _columnindexes[columnname] = -1;
             return -1;
         }
+
     }
 
     public class CoreFieldMap<T1, T2>
@@ -283,7 +294,7 @@ namespace InABox.Core
     }
 
     [Serializable]
-    public class CoreTable : ICoreTable //: IEnumerable, INotifyCollectionChanged
+    public class CoreTable : ICoreTable, ISerializeBinary //: IEnumerable, INotifyCollectionChanged
     {
 
         #region Fields
@@ -714,7 +725,233 @@ namespace InABox.Core
             return result;
         }
 
-        
+        #region Serialize Binary
+
+        private static void WriteValue(BinaryWriter writer, Type type, object? value)
+        {
+            value ??= CoreUtils.GetDefault(type);
+            if (type == typeof(byte[]) && value is byte[] bArray)
+            {
+                writer.Write(bArray.Length);
+                writer.Write(bArray);
+            }
+            else if (type == typeof(byte[]) && value is null)
+            {
+                writer.Write(0);
+            }
+            else if (type.IsArray && value is Array array)
+            {
+                var elementType = type.GetElementType();
+                writer.Write(array.Length);
+                foreach (var val1 in array)
+                {
+                    WriteValue(writer, elementType, val1);
+                }
+            }
+            else if (type.IsArray && value is null)
+            {
+                writer.Write(0);
+            }
+            else if (type.IsEnum && value is Enum e)
+            {
+                var underlyingType = type.GetEnumUnderlyingType();
+                WriteValue(writer, underlyingType, Convert.ChangeType(e, underlyingType));
+            }
+            else if (type == typeof(bool) && value is bool b)
+            {
+                writer.Write(b);
+            }
+            else if (type == typeof(string) && value is string str)
+            {
+                writer.Write(str);
+            }
+            else if (type == typeof(string) && value is null)
+            {
+                writer.Write("");
+            }
+            else if (type == typeof(Guid) && value is Guid guid)
+            {
+                writer.Write(guid.ToByteArray());
+            }
+            else if (type == typeof(byte) && value is byte i8)
+            {
+                writer.Write(i8);
+            }
+            else if (type == typeof(Int16) && value is Int16 i16)
+            {
+                writer.Write(i16);
+            }
+            else if (type == typeof(Int32) && value is Int32 i32)
+            {
+                writer.Write(i32);
+            }
+            else if (type == typeof(Int64) && value is Int64 i64)
+            {
+                writer.Write(i64);
+            }
+            else if (type == typeof(float) && value is float f32)
+            {
+                writer.Write(f32);
+            }
+            else if (type == typeof(double) && value is double f64)
+            {
+                writer.Write(f64);
+            }
+            else if (type == typeof(DateTime) && value is DateTime date)
+            {
+                writer.Write(date.Ticks);
+            }
+            else if (type == typeof(TimeSpan) && value is TimeSpan time)
+            {
+                writer.Write(time.Ticks);
+            }
+            else
+            {
+                throw new Exception($"Invalid type; Target DataType is {type} and value DataType is {value?.GetType().ToString() ?? "null"}");
+            }
+        }
+
+        public void WriteBinary(BinaryWriter writer, bool includeColumns)
+        {
+            writer.Write(TableName);
+
+            if (includeColumns)
+            {
+                foreach (var column in Columns)
+                {
+                    writer.Write(true);
+                    writer.Write(column.ColumnName);
+                    writer.Write(column.DataType.EntityName());
+                }
+                writer.Write(false);
+            }
+
+            writer.Write(Rows.Count);
+            foreach (var row in Rows)
+            {
+                foreach (var col in Columns)
+                {
+                    var val = row[col.ColumnName];
+                    WriteValue(writer, col.DataType, val);
+                }
+            }
+        }
+
+        public void SerializeBinary(BinaryWriter writer) => WriteBinary(writer, true);
+
+        private static object? ReadValue(BinaryReader reader, Type type)
+        {
+            if (type == typeof(byte[]))
+            {
+                var length = reader.ReadInt32();
+                return reader.ReadBytes(length);
+            }
+            else if (type.IsArray)
+            {
+                var length = reader.ReadInt32();
+                var elementType = type.GetElementType();
+
+                var array = Array.CreateInstance(elementType, length);
+                for (int i = 0; i < array.Length; ++i)
+                {
+                    array.SetValue(ReadValue(reader, elementType), i);
+                }
+                return array;
+            }
+            else if (type.IsEnum)
+            {
+                var val = ReadValue(reader, type.GetEnumUnderlyingType());
+                return Enum.ToObject(type, val);
+            }
+            else if (type == typeof(bool))
+            {
+                return reader.ReadBoolean();
+            }
+            else if (type == typeof(string))
+            {
+                return reader.ReadString();
+            }
+            else if (type == typeof(Guid))
+            {
+                return new Guid(reader.ReadBytes(16));
+            }
+            else if (type == typeof(byte))
+            {
+                return reader.ReadByte();
+            }
+            else if (type == typeof(Int16))
+            {
+                return reader.ReadInt16();
+            }
+            else if (type == typeof(Int32))
+            {
+                return reader.ReadInt32();
+            }
+            else if (type == typeof(Int64))
+            {
+                return reader.ReadInt64();
+            }
+            else if (type == typeof(float))
+            {
+                return reader.ReadSingle();
+            }
+            else if (type == typeof(double))
+            {
+                return reader.ReadDouble();
+            }
+            else if (type == typeof(DateTime))
+            {
+                return new DateTime(reader.ReadInt64());
+            }
+            else if (type == typeof(TimeSpan))
+            {
+                return new TimeSpan(reader.ReadInt64());
+            }
+            else
+            {
+                throw new Exception($"Invalid type; Target DataType is {type}");
+            }
+        }
+
+        public void ReadBinary(BinaryReader reader, IList<CoreColumn>? columns)
+        {
+            TableName = reader.ReadString();
+
+            Columns.Clear();
+            if (columns is null)
+            {
+                while (reader.ReadBoolean())
+                {
+                    var columnName = reader.ReadString();
+                    var dataType = CoreUtils.GetEntity(reader.ReadString());
+                    Columns.Add(new CoreColumn(dataType, columnName));
+                }
+            }
+            else
+            {
+                foreach (var column in columns)
+                {
+                    Columns.Add(column);
+                }
+            }
+
+            Rows.Clear();
+            var nRows = reader.ReadInt32();
+            for (int i = 0; i < nRows; ++i)
+            {
+                var row = NewRow();
+                foreach (var column in Columns)
+                {
+                    var value = ReadValue(reader, column.DataType);
+                    row.Values.Add(value);
+                }
+                Rows.Add(row);
+            }
+        }
+
+        public void DeserializeBinary(BinaryReader reader) => ReadBinary(reader, null);
+
+        #endregion
     }
 
     public class CoreTableAdapter<T> : IEnumerable<T> where T : BaseObject, new()

+ 36 - 0
InABox.Core/Serialization.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Threading;
@@ -8,6 +9,13 @@ using Newtonsoft.Json;
 
 namespace InABox.Core
 {
+    public interface ISerializeBinary
+    {
+        public void SerializeBinary(BinaryWriter writer);
+
+        public void DeserializeBinary(BinaryReader reader);
+    }
+
     public static class Serialization
     {
         private static JsonSerializerSettings? _serializerSettings;
@@ -177,5 +185,33 @@ namespace InABox.Core
 
             return ret;
         }
+
+        #region Binary Serialization
+
+        public static byte[] WriteBinary(this ISerializeBinary obj)
+        {
+            using var stream = new MemoryStream();
+            obj.SerializeBinary(new BinaryWriter(stream));
+            return stream.ToArray();
+        }
+
+        public static T ReadBinary<T>(byte[] data)
+            where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), data);
+        public static T ReadBinary<T>(Stream stream)
+            where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), stream);
+
+        public static object ReadBinary(Type T, byte[] data)
+        {
+            using var stream = new MemoryStream(data);
+            return ReadBinary(T, stream);
+        }
+        public static object ReadBinary(Type T, Stream stream)
+        {
+            var obj = (Activator.CreateInstance(T) as ISerializeBinary)!;
+            obj.DeserializeBinary(new BinaryReader(stream));
+            return obj;
+        }
+
+        #endregion
     }
 }

+ 14 - 0
InABox.Remote.Shared/InABox.Remote.Shared.projitems

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+    <HasSharedItems>true</HasSharedItems>
+    <SharedGUID>eac7b383-2eea-4256-9e65-7883bf148a6b</SharedGUID>
+  </PropertyGroup>
+  <PropertyGroup Label="Configuration">
+    <Import_RootNamespace>InABox.Remote.Shared</Import_RootNamespace>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildThisFileDirectory)SerializationFormat.cs" />
+  </ItemGroup>
+</Project>

+ 13 - 0
InABox.Remote.Shared/InABox.Remote.Shared.shproj

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>eac7b383-2eea-4256-9e65-7883bf148a6b</ProjectGuid>
+    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
+  <PropertyGroup />
+  <Import Project="InABox.Remote.Shared.projitems" Label="Shared" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
+</Project>

+ 16 - 0
InABox.Remote.Shared/SerializationFormat.cs

@@ -0,0 +1,16 @@
+using InABox.Clients;
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+namespace InABox.Remote.Shared
+{
+    public enum SerializationFormat
+    {
+        Json,
+        Binary
+    }
+}

+ 2 - 0
InABox.Server/InABox.Server.csproj

@@ -26,4 +26,6 @@
         <ProjectReference Include="..\InABox.WebSocket.Shared\InABox.WebSocket.Shared.csproj" />
     </ItemGroup>
 
+    <Import Project="..\InABox.Remote.Shared\InABox.Remote.Shared.projitems" Label="Shared" />
+
 </Project>

+ 33 - 11
InABox.Server/Rest/RestListener.cs

@@ -14,10 +14,12 @@ using GenHTTP.Modules.Practices;
 using InABox.Clients;
 using InABox.Core;
 using InABox.Database;
+using InABox.Remote.Shared;
 using InABox.Server.WebSocket;
 using InABox.WebSocket.Shared;
 using NPOI.SS.Formula.Functions;
 using NPOI.XSSF.Streaming.Values;
+using Twilio.Rest.Taskrouter.V1.Workspace.TaskQueue;
 using RequestMethod = GenHTTP.Api.Protocol.RequestMethod;
 using StreamContent = GenHTTP.Modules.IO.Streaming.StreamContent;
 
@@ -259,6 +261,29 @@ namespace InABox.API
                 ?? throw new Exception("Deserialization failed");
         }
 
+        private IResponseBuilder SerializeResponse(IRequest request, SerializationFormat responseFormat, Response? result)
+        {
+            if (responseFormat == SerializationFormat.Binary && result is ISerializeBinary binary)
+            {
+                var stream = new MemoryStream();
+                binary.SerializeBinary(new BinaryWriter(stream));
+
+                var response = request.Respond()
+                    .Type(new FlexibleContentType(ContentType.ApplicationOctetStream))
+                    .Content(stream, (ulong?)stream.Length, () => new ValueTask<ulong?>((ulong)stream.GetHashCode()));
+                return response;
+            }
+            else
+            {
+                var serialized = Serialization.Serialize(result);
+
+                var response = request.Respond()
+                    .Type(new FlexibleContentType(ContentType.ApplicationJson))
+                    .Content(new ResourceContent(Resource.FromString(serialized).Build()));
+                return response;
+            }
+        }
+
         /// <summary>
         /// Handler for all database requests
         /// </summary>
@@ -266,18 +291,20 @@ namespace InABox.API
         /// <returns></returns>
         private ValueTask<IResponse?> HandleDatabaseRequest(IRequest request)
         {
+            var responseFormat = SerializationFormat.Json;
+            if (request.Query.TryGetValue("responseFormat", out var formatString) && Enum.TryParse<SerializationFormat>(formatString, out var format))
+            {
+                responseFormat = format;
+            }
+
             var endpoint = request.Target.Current.Value;
             if (endpoint.StartsWith("QueryMultiple"))
             {
                 var requestObject = Deserialize<MultiQueryRequest>(request.Content, true);
 
                 var result = RestService.QueryMultiple(requestObject, false);
-                var serialized = Serialization.Serialize(result);
 
-                var response = request.Respond()
-                    .Type(new FlexibleContentType(ContentType.ApplicationJson))
-                    .Content(new ResourceContent(Resource.FromString(serialized).Build()));
-                return new ValueTask<IResponse?>(response.Build());
+                return new ValueTask<IResponse?>(SerializeResponse(request, responseFormat, result).Build());
             }
 
             foreach (var (name, method) in methodMap)
@@ -297,12 +324,7 @@ namespace InABox.API
                         var resolvedMethod = method.MakeGenericMethod(entityType);
                         var result = resolvedMethod.Invoke(null, new object[] { request }) as Response;
 
-                        var serialized = Serialization.Serialize(result);
-
-                        var response = request.Respond()
-                            .Type(new FlexibleContentType(ContentType.ApplicationJson))
-                            .Content(new ResourceContent(Resource.FromString(serialized).Build()));
-                        return new ValueTask<IResponse?>(response.Build());
+                        return new ValueTask<IResponse?>(SerializeResponse(request, responseFormat, result).Build());
                     }
 
                     Logger.Send(LogType.Error, request.Client.IPAddress.ToString(),