using System; 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 { public class Credentials : ISerializeBinary { [Obsolete] public string? UserID { get; set; } [Obsolete] public string? Password { get; set; } public Platform Platform { get; set; } public string? Version { get; set; } public Guid Session { get; set; } public void SerializeBinary(CoreBinaryWriter writer) { writer.Write(UserID ?? string.Empty); writer.Write(Password ?? string.Empty); writer.Write(Platform.ToString()); writer.Write(Version ?? string.Empty); writer.Write(Session); } public void DeserializeBinary(CoreBinaryReader reader) { UserID = reader.ReadString(); Password = reader.ReadString(); if(Enum.TryParse(reader.ReadString(), out var platform)) { Platform = platform; } Version = reader.ReadString(); Session = reader.ReadGuid(); } } public abstract class Request { public Request() { Credentials = new Credentials(); } public static Action? BeforeRequest { get; set; } public Credentials Credentials { get; set; } public abstract RequestMethod GetMethod(); public virtual void SerializeBinary(CoreBinaryWriter writer) { Credentials.SerializeBinary(writer); } public virtual void DeserializeBinary(CoreBinaryReader reader) { Credentials.DeserializeBinary(reader); } } public enum StatusCode { OK, Incomplete, Error, Unauthenticated, BadServer } public enum RequestMethod { Info, Ping, Check2FA, Validate, MultiQuery, MultiDelete, Delete, Save, MultiSave, Query, Push, Version, ReleaseNotes, Installer } public abstract class Response { public Response() { Status = StatusCode.Incomplete; Messages = new List(); } public StatusCode Status { get; set; } public List Messages { get; } public virtual void DeserializeBinary(CoreBinaryReader 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(CoreBinaryWriter writer) { writer.Write((int)Status); writer.Write(Messages.Count); foreach(var message in Messages) { writer.Write(message); } } } public abstract class BaseRequest : Request where TEntity : Entity, new() { } public abstract class BaseResponse : Response where TEntity : Entity, new() { } /*public class ListRequest : BaseRequest where TEntity : Entity, new() { public Filter? Filter { get; set; } public Columns? Columns { get; set; } public SortOrder? Sort { get; set; } public override RequestMethod GetMethod() => RequestMethod.Query; } public class ListResponse : BaseResponse where TEntity : Entity, new() { public IEnumerable Data { get; set; } }*/ public interface IQueryRequest : ISerializeBinary { public IFilter? Filter { get; set; } public IColumns? Columns { get; set; } public ISortOrder? Sort { get; set; } public CoreRange? Range { get; set; } } public class QueryRequest : BaseRequest, IQueryRequest, ISerializeBinary where TEntity : Entity, new() { IFilter? IQueryRequest.Filter { get => Filter; set => Filter = value as Filter; } public Filter? Filter { get; set; } IColumns? IQueryRequest.Columns { get => Columns; set => Columns = value as Columns; } public Columns? Columns { get; set; } ISortOrder? IQueryRequest.Sort { get => Sort; set => Sort = value as SortOrder; } public SortOrder? Sort { get; set; } public CoreRange? Range { get; set; } [JsonConstructor] public QueryRequest() { Filter = null; Columns = null; Sort = null; Range = null; } public QueryRequest(Filter? filter, Columns? columns, SortOrder? sort, CoreRange? range) { Filter = filter; Columns = columns; Sort = sort; Range = range; } public override RequestMethod GetMethod() => RequestMethod.Query; public override void SerializeBinary(CoreBinaryWriter writer) { base.SerializeBinary(writer); writer.Write(Filter); writer.Write(Columns); writer.Write(Sort); writer.WriteBinaryValue(Range); } public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); Filter = reader.ReadFilter(); Columns = reader.ReadColumns(); Sort = reader.ReadSortOrder(); Range = reader.ReadBinaryValue(); } } public class QueryResponse : BaseResponse, ISerializeBinary where TEntity : Entity, new() { public CoreTable Items { get; set; } = new CoreTable(); public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); Items = new CoreTable(); Items.DeserializeBinary(reader); } public override void SerializeBinary(CoreBinaryWriter writer) { base.SerializeBinary(writer); Items.SerializeBinary(writer); } } /*public class LoadRequest : BaseRequest where TEntity : Entity, new() { public Filter? Filter { get; set; } public SortOrder? Sort { get; set; } } public class LoadResponse : BaseResponse where TEntity : Entity, new() { public TEntity[] Items { get; set; } }*/ public class MultiSaveRequest : BaseRequest, ISerializeBinary where TEntity : Entity, new() { public TEntity[] Items { get; set; } public string AuditNote { get; set; } [Obsolete("We don't like this; it should always be true.")] // 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(); AuditNote = ""; } public MultiSaveRequest(TEntity[] items, string auditNode) { Items = items; AuditNote = auditNode; } public override RequestMethod GetMethod() => RequestMethod.MultiSave; public override void SerializeBinary(CoreBinaryWriter writer) { base.SerializeBinary(writer); writer.WriteObjects(Items); writer.Write(AuditNote); writer.Write(ReturnOnlyChanged); } public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); Items = reader.ReadObjects().ToArray(); AuditNote = reader.ReadString(); ReturnOnlyChanged = reader.ReadBoolean(); } } public class MultiSaveResponse : BaseResponse, ISerializeBinary where TEntity : Entity, new() { //public Guid[] IDs { get; set; } public TEntity[]? Items { get; set; } public List> ChangedValues { get; set; } = new List>(); 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.SerializeChangedValues(writer, changed); } } public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); if (reader.ReadBoolean()) { Items = reader.ReadObjects().ToArray(); } else { Items = null; } ChangedValues.Clear(); var nChangedValues = reader.ReadInt32(); for(int i = 0; i < nChangedValues; ++i) { ChangedValues.Add(SaveResponse.DeserializeChangedValues(reader)); } } } public class SaveRequest : BaseRequest, ISerializeBinary where TEntity : Entity, new() { public TEntity Item { get; set; } public string AuditNote { get; set; } 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) { base.SerializeBinary(writer); writer.WriteObject(Item); writer.Write(AuditNote); writer.Write(ReturnOnlyChanged); } public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); Item = reader.ReadObject(); AuditNote = reader.ReadString(); ReturnOnlyChanged = reader.ReadBoolean(); } } public class SaveResponse : BaseResponse, ISerializeBinary where TEntity : Entity, new() { //public Guid ID { get; set; } public TEntity? Item { get; set; } public Dictionary ChangedValues { get; set; } = new Dictionary(); 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(); } ChangedValues = DeserializeChangedValues(reader); } public static void SerializeChangedValues(CoreBinaryWriter writer, Dictionary changedValues) { var props = new List>(); writer.Write(changedValues.Count); foreach (var (key, value) in changedValues) { var property = DatabaseSchema.Property(typeof(TEntity), key); if (property != null) { props.Add(new Tuple(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 DeserializeChangedValues(CoreBinaryReader reader) { var changedDict = new Dictionary(); 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 : BaseRequest, 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(); AuditNote = reader.ReadString(); } } public class DeleteResponse : BaseResponse, ISerializeBinary where TEntity : Entity, new() { } public class MultiDeleteRequest : BaseRequest, 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().ToArray(); AuditNote = reader.ReadString(); } } public class MultiDeleteResponse : BaseResponse, ISerializeBinary where TEntity : Entity, new() { } public interface IMultiQueryTableQuery { 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 : IMultiQueryTableQuery, ISerializeBinary where TEntity : Entity, new() { public string Type => typeof(TEntity).EntityName(); public Filter? Filter { get; set; } public Columns? Columns { get; set; } public SortOrder? Sort { get; set; } IFilter? IMultiQueryTableQuery.Filter { get => Filter; set => Filter = value as Filter; } IColumns? IMultiQueryTableQuery.Columns { get => Columns; set => Columns = value as Columns; } ISortOrder? IMultiQueryTableQuery.Sort { get => Sort; set => Sort = value as SortOrder; } 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(); Columns = reader.ReadColumns(); Sort = reader.ReadSortOrder(); } } public class MultiQueryRequest : Request, ISerializeBinary { public Dictionary Queries { get; set; } = new Dictionary(); 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 { public MultiQueryResponse() { Tables = new Dictionary(); } public Dictionary Tables { get; set; } public override void DeserializeBinary(CoreBinaryReader 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(CoreBinaryWriter writer) { base.SerializeBinary(writer); writer.Write(Tables.Count); foreach(var (name, table) in Tables) { writer.Write(name); table.SerializeBinary(writer); } } } public enum ValidationStatus { VALID, INVALID, REQUIRE_2FA, PASSWORD_EXPIRED } public class ValidateRequest : Request, ISerializeBinary { public string? UserID { get; set; } public string? Password { get; set; } public string? PIN { get; set; } public bool UsePIN { get; set; } public override RequestMethod GetMethod() => RequestMethod.Validate; public override void SerializeBinary(CoreBinaryWriter writer) { base.SerializeBinary(writer); writer.Write(UserID ?? string.Empty); writer.Write(Password ?? string.Empty); writer.Write(PIN ?? string.Empty); 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, ISerializeBinary { public ValidationStatus ValidationStatus { get; set; } public Guid UserGuid { get; set; } public string? UserID { get; set; } public Guid SecurityID { get; set; } public Guid Session { get; set; } public string? Recipient2FA { get; set; } public DateTime PasswordExpiration { get; set; } public override void SerializeBinary(CoreBinaryWriter writer) { base.SerializeBinary(writer); writer.Write((int)ValidationStatus); writer.Write(UserGuid); writer.Write(UserID ?? string.Empty); writer.Write(SecurityID); writer.Write(Session); writer.Write(Recipient2FA ?? string.Empty); writer.Write(PasswordExpiration.ToUniversalTime().Ticks); } public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); ValidationStatus = (ValidationStatus)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, 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, 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, ISerializeBinary { public override RequestMethod GetMethod() => RequestMethod.Ping; } public class PingResponse : Response, ISerializeBinary { } public class InfoRequest : Request, ISerializeBinary { public override RequestMethod GetMethod() => RequestMethod.Info; } public class DatabaseInfo : ISerializeBinary { public string? ColorScheme { get; set; } public byte[]? Logo { get; set; } public string Version { get; set; } public bool IsHTTPS { get; set; } // Ideally, we should not need these. However, there is currently a bug in RPCServer // that crashes the server when uploading large files (ie photos) // So we need to be a able to maintain both a Rest and RPC-style connection // to the server to try and have both session (RPC) capabilities // as well as having stable upload performance :-( // Once the RPC listeners are stable, then this needs to be removed. public int RestPort { get; set; } public int RPCPort { get; set; } public Guid DatabaseID { get; set; } [JsonConstructor] public DatabaseInfo() { Version = ""; } public DatabaseInfo(string? colorScheme, byte[]? logo, string version, bool isHTTTPS, int restPort, int rpcPort, Guid databaseID) { ColorScheme = colorScheme; Logo = logo; Version = version; IsHTTPS = isHTTTPS; RestPort = restPort; RPCPort = rpcPort; DatabaseID = databaseID; } public void SerializeBinary(CoreBinaryWriter writer) { writer.Write(ColorScheme ?? string.Empty); if (Logo is null) { writer.Write(0); } else { writer.Write(Logo.Length); writer.Write(Logo); } writer.Write(Version); writer.Write(IsHTTPS); if (String.CompareOrdinal(writer.Settings.Version, "1.1") > 0) { writer.Write(RestPort); writer.Write(RPCPort); } writer.Write(DatabaseID); } public void DeserializeBinary(CoreBinaryReader reader) { ColorScheme = reader.ReadString(); var nLogoBytes = reader.ReadInt32(); Logo = reader.ReadBytes(nLogoBytes); Version = reader.ReadString(); IsHTTPS = reader.ReadBoolean(); if (String.CompareOrdinal(reader.Settings.Version, "1.1") > 0) { try { RestPort = reader.ReadInt32(); RPCPort = reader.ReadInt32(); } catch { Logger.Send(LogType.Error,"","Unable to read RestPort and RPCPort Values"); } } DatabaseID = reader.ReadGuid(); } } 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 class VersionRequest : Request, ISerializeBinary { public override RequestMethod GetMethod() => RequestMethod.Version; } public class VersionResponse : Response, ISerializeBinary { public string Version { get; set; } = ""; public override void SerializeBinary(CoreBinaryWriter writer) { base.SerializeBinary(writer); writer.Write(Version); } public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); Version = reader.ReadString(); } } public class ReleaseNotesRequest : Request, ISerializeBinary { public override RequestMethod GetMethod() => RequestMethod.ReleaseNotes; } public class ReleaseNotesResponse : Response, ISerializeBinary { public string ReleaseNotes { get; set; } = ""; public override void SerializeBinary(CoreBinaryWriter writer) { base.SerializeBinary(writer); writer.Write(ReleaseNotes); } public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); ReleaseNotes = reader.ReadString(); } } public class InstallerRequest : Request, ISerializeBinary { public override RequestMethod GetMethod() => RequestMethod.Installer; } public class InstallerResponse : Response, ISerializeBinary { public byte[]? Installer { get; set; } public override void SerializeBinary(CoreBinaryWriter writer) { base.SerializeBinary(writer); if(Installer != null) { writer.Write(Installer.Length); writer.Write(Installer); } else { writer.Write(0); } } public override void DeserializeBinary(CoreBinaryReader reader) { base.DeserializeBinary(reader); Installer = reader.ReadBytes(reader.ReadInt32()); if(Installer.Length == 0) { Installer = null; } } } public static class Extensions { public static T Status(this T response, StatusCode status) where T : Response { response.Status = status; return response; } } public class RequestException : Exception { public StatusCode Status { get; set; } public RequestMethod Method { get; set; } public RequestException(string message, StatusCode status, Request request): base(message) { Status = status; Method = request.GetMethod(); } } }