public async Task TestSendToAccount() { Dictionary <long, bool> sent = await CreateSessions(database, connections, (alice, 1, true), (alice, 2, false), (alice, 3, true), (alice, 4, true)) .ConfigureAwait(false); var packet = packets.New <P0ACreateChannel>(); Assert.IsTrue(connections.TryGet(1, out IClient exclude), "Client disappeared from ConnectionsService"); await delivery.StartSendToAccount(packet, alice, exclude).ConfigureAwait(false); Assert.IsFalse(sent[1]); Assert.IsFalse(sent[2]); Assert.IsTrue(sent[3]); Assert.IsTrue(sent[4]); }
public void TestReplaceOnDuplicate() { IClient client1 = new FakeClient { AccountId = 1, SessionId = 1 }; IClient client2 = new FakeClient { AccountId = 1, SessionId = 1 }; var connections = new ConnectionsService(); IClient replaced1 = connections.Add(client1); Assert.IsNull(replaced1); Assert.IsTrue(connections.TryGet(1, out IClient out1)); Assert.IsTrue(ReferenceEquals(client1, out1)); IClient replaced2 = connections.Add(client2); Assert.IsTrue(ReferenceEquals(client1, replaced2)); Assert.IsTrue(connections.TryGet(1, out IClient out2)); Assert.IsTrue(ReferenceEquals(client2, out2)); }
public override async ValueTask Handle(P04DeleteAccount packet) { var response = Packets.New <P05DeleteAccountResponse>(); byte[] passwordHash; using (var csp = SHA256.Create()) passwordHash = csp.ComputeHash(packet.KeyHash); // EF Core converts the C# == operator to = in SQL which compares the contents of byte arrays Account account = await Database.Accounts.AsTracking() .SingleOrDefaultAsync(a => a.AccountId == Client.AccountId && a.PasswordHash == passwordHash).ConfigureAwait(false); if (account == null) { response.StatusCode = DeleteAccountStatus.InvalidCredentials; await Client.Send(response).ConfigureAwait(false); return; } account.DeletionTime = DateTime.Now; try { await Database.SaveChangesAsync().ConfigureAwait(false); } catch (DbUpdateConcurrencyException) // Account is being deleted by a another handler { // We can safely send a response to the client because the second handler is waiting for this one to return response.StatusCode = DeleteAccountStatus.Success; await Client.Send(response).ConfigureAwait(false); await Client.DisposeAsync(waitForHandling : false).ConfigureAwait(false); } // Delete mail confirmations and sessions // This will prevent clients from logging in with the deleted account MailConfirmation[] mailConfirmations = await Database.MailConfirmations.AsQueryable() .Where(c => c.AccountId == Client.AccountId) .ToArrayAsync().ConfigureAwait(false); Session[] sessions = await Database.Sessions.AsQueryable() .Where(s => s.AccountId == Client.AccountId) .ToArrayAsync().ConfigureAwait(false); Database.MailConfirmations.RemoveRange(mailConfirmations); Database.Sessions.RemoveRange(sessions); await Database.SaveChangesAsync().ConfigureAwait(false); // Kick all sessions var tasks = new List <Task>(); foreach (Session session in sessions) { if (connections.TryGet(session.SessionId, out IClient client) && !ReferenceEquals(client, Client)) { tasks.Add(client.DisposeAsync(true, true, false).AsTask()); } } await Task.WhenAll(tasks).ConfigureAwait(false); // Do all remaining database operations ChannelMember[] memberships = await Database.ChannelMembers.AsQueryable() .Where(m => m.AccountId == Client.AccountId) .ToArrayAsync().ConfigureAwait(false); Database.ChannelMembers.RemoveRange(memberships); await Database.SaveChangesAsync().ConfigureAwait(false); // Finish handling and close last connection response.StatusCode = DeleteAccountStatus.Success; await Client.Send(response).ConfigureAwait(false); await Client.DisposeAsync(waitForHandling : false).ConfigureAwait(false); }