using InABox.Clients; using InABox.Core; using InABox.IPC.Shared; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace InABox.Client.IPC { internal static class LocalCache { public static string Password { get; set; } } public class PipeIPCClient : BaseClient where TEntity : Entity, new() { private IPCClient Client; public PipeIPCClient(string pipeName) { Client = IPCClientFactory.GetClient(pipeName); Timeout = TimeSpan.FromSeconds(300); } private static string[]? _types; public override string[] SupportedTypes() { _types ??= CoreUtils.Entities .Where(x => x.GetInterfaces().Contains(typeof(IPersistent))) .Select(x => x.EntityName().Replace(".", "_")) .ToArray(); return _types; } public override DatabaseInfo Info() { try { var request = new InfoRequest(); PrepareRequest(request, false); var response = Send(PipeRequest.Info(request)).GetResponse(); return response.Info; } catch (Exception) { return new DatabaseInfo(); } } private void PrepareRequest(Request request, bool doCredentials = true) { if(request is not ValidateRequest && Client.Disconnected) { ClientFactory.Validate(ClientFactory.UserID, LocalCache.Password); } if (doCredentials) { request.Credentials.Platform = ClientFactory.Platform; request.Credentials.Version = ClientFactory.Version; request.Credentials.Session = ClientFactory.SessionID; } Request.BeforeRequest?.Invoke(request); } private PipeRequest Send(PipeRequest request, int? timeout = null) { return Client.Send(request, timeout ?? Convert.ToInt32(Timeout.TotalMilliseconds)); } protected override bool DoCheck2FA(string code, Guid? session) { var request = new Check2FARequest { Code = code }; PrepareRequest(request); var response = Send(PipeRequest.Check2FA(request)).GetResponse(); if (response != null) { return response.Status switch { StatusCode.OK => response.Valid, StatusCode.Unauthenticated => false, _ => throw new IPCException(response.Messages), }; } return false; } protected override bool DoPing() { try { var request = new PingRequest(); PrepareRequest(request); var response = Send(PipeRequest.Ping(request), 10_000).GetResponse(); if (response != null) { return response.Status switch { StatusCode.Error or StatusCode.BadServer or StatusCode.Incomplete => throw new IPCException(response.Messages), _ => true }; } } catch (Exception) { } return false; } protected override void DoDelete(TEntity entity, string auditnote) { var request = new DeleteRequest { Item = entity }; PrepareRequest(request); var response = Send(PipeRequest.Delete(request)).GetResponse>(); switch (response.Status) { case StatusCode.OK: break; case StatusCode.Unauthenticated: throw new IPCException("Client not authenticated"); default: throw new IPCException(response.Messages); } } protected override void DoDelete(IList entities, string auditnote) { var items = entities.ToArray(); var request = new MultiDeleteRequest { Items = items, AuditNote = auditnote }; PrepareRequest(request); var response = Send(PipeRequest.MultiDelete(request)).GetResponse>(); switch (response.Status) { case StatusCode.OK: break; case StatusCode.Unauthenticated: throw new IPCException("Client not authenticated"); default: throw new IPCException(response.Messages); } } protected override TEntity[] DoLoad(Filter filter = null, SortOrder sort = null) { var request = new QueryRequest { Filter = filter, Sort = sort }; PrepareRequest(request); var result = new List(); var response = Send(PipeRequest.Query(request)).GetResponse>(); if (response.Items != null) foreach (var row in response.Items.Rows) result.Add(row.ToObject()); return result.ToArray(); } protected override CoreTable DoQuery(Filter? filter, Columns? columns, SortOrder? sort = null) { var request = new QueryRequest { Columns = columns, Filter = filter, Sort = sort }; PrepareRequest(request); var response = Send(PipeRequest.Query(request)).GetResponse>(); if (response != null) { return response.Status switch { StatusCode.OK => response.Items, StatusCode.Unauthenticated => throw new IPCException("Client not authenticated", StatusCode.Unauthenticated), _ => throw new IPCException(response.Messages), }; } return null; } protected override Dictionary DoQueryMultiple(Dictionary queries) { var request = new MultiQueryRequest { TableTypes = new(), Filters = new(), Columns = new(), Sorts = new() }; 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); } PrepareRequest(request); var response = Send(PipeRequest.QueryMultiple(request)).GetResponse(); if (response != null) { return response.Status switch { StatusCode.OK => response.Tables, StatusCode.Unauthenticated => throw new IPCException("Client not authenticated"), _ => throw new IPCException(response.Messages), }; } return null; } protected override void DoSave(TEntity entity, string auditnote) { var request = new SaveRequest { Item = entity, AuditNote = auditnote, ReturnOnlyChanged = true }; PrepareRequest(request); var response = Send(PipeRequest.Save(request)).GetResponse>(); switch (response.Status) { case StatusCode.OK: /*var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true); entity.SetObserving(false); foreach (var prop in props.Keys) { var value = CoreUtils.GetPropertyValue(response.Item, prop); CoreUtils.SetPropertyValue(entity, prop, value); } entity.CommitChanges(); entity.SetObserving(true);*/ entity.SetObserving(false); foreach (var (key, value) in response.ChangedValues) { if (CoreUtils.TryGetProperty(key, out var property)) { CoreUtils.SetPropertyValue(entity, key, CoreUtils.ChangeType(value, property.PropertyType)); } } entity.CommitChanges(); entity.SetObserving(true); break; case StatusCode.Unauthenticated: throw new IPCException("Client not authenticated"); default: throw new IPCException(response.Messages); } } protected override void DoSave(IEnumerable entities, string auditnote) { var items = entities.ToArray(); var request = new MultiSaveRequest { Items = items, AuditNote = auditnote, ReturnOnlyChanged = true }; PrepareRequest(request); var response = Send(PipeRequest.MultiSave(request)).GetResponse>(); switch (response.Status) { case StatusCode.OK: /*var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true); for (var i = 0; i < items.Length; i++) { items[i].SetObserving(false); foreach (var prop in props.Keys) { var value = CoreUtils.GetPropertyValue(response.Items[i], prop); CoreUtils.SetPropertyValue(items[i], prop, value); } //CoreUtils.DeepClone(response.Items[i], items[i]); items[i].CommitChanges(); items[i].SetObserving(true); }*/ for (int i = 0; i < items.Length; ++i) { var entity = items[i]; var changedValues = response.ChangedValues[i]; entity.SetObserving(false); foreach (var (key, value) in changedValues) { if (CoreUtils.TryGetProperty(key, out var property)) { CoreUtils.SetPropertyValue(entity, key, CoreUtils.ChangeType(value, property.PropertyType)); } } entity.CommitChanges(); entity.SetObserving(true); } break; case StatusCode.Unauthenticated: throw new IPCException("Client not authenticated"); default: throw new IPCException(response.Messages); } } protected override ValidationData DoValidate(Guid session) { return Validate( null, null, false, session); } protected override ValidationData DoValidate(string pin, Guid session) { return Validate( null, pin, true, session); } protected override ValidationData DoValidate(string userid, string password, Guid session) { return Validate( userid, password, false, session); } private ValidationData Validate(string? userid, string? password, bool usePin, Guid session = default) { var ticks = DateTime.Now.ToUniversalTime().Ticks.ToString(); var request = new ValidateRequest { UsePIN = usePin }; if (usePin) { request.UserID = Encryption.Encrypt(ticks, "wCq9rryEJEuHIifYrxRjxg", true); request.Password = Encryption.Encrypt(ticks, "7mhvLnqMwkCAzN+zNGlyyg", true); request.PIN = password; } else { request.UserID = userid; request.Password = password; } request.Credentials.Platform = ClientFactory.Platform; request.Credentials.Version = ClientFactory.Version; PrepareRequest(request, false); if(session != Guid.Empty) { request.Credentials.Session = session; } var response = Send(PipeRequest.Validate(request), 10000).GetResponse(); if (response != null) if (response.Status.Equals(StatusCode.OK)) { LocalCache.Password = password; return new ValidationData( response.ValidationResult, response.UserID, response.UserGuid, response.SecurityID, response.Session, response.Recipient2FA, response.PasswordExpiration ); } else if (response.Status == StatusCode.BadServer) { throw new IPCException(response.Messages); } return new ValidationData( ValidationResult.INVALID, "", Guid.Empty, Guid.Empty, Guid.Empty, null, DateTime.MinValue ); } } }