81 lines
5.4 KiB
C#
81 lines
5.4 KiB
C#
using VoiceCat.Core;
|
|||
|
|
using VoiceCat.Crypto;
|
||
|
|
using Voicecat.V1;
|
||
|
|
using static VoiceCat.Tests.ServerTests;
|
||
|
|
using static VoiceCat.Tests.MediaRelayTests;
|
||
|
|
|
||
|
|
namespace VoiceCat.Tests;
|
||
|
|
|
||
|
|
public class ManagedClientTests
|
||
|
|
{
|
||
|
|
private static VoiceCatClient NewClient(ServerFixture fixture, string name) => new(name, "test", Path.Combine(fixture.Directory, name + ".pins"));
|
||
|
|
private static Task Connect(VoiceCatClient client, ServerFixture fixture) => client.ConnectAsync("127.0.0.1", (ushort)fixture.Server.EndPoint.Port, (_, _) => ValueTask.FromResult(true));
|
||
|
|
private static async Task<Envelope> Event(VoiceCatClient client, Func<Envelope, bool> predicate)
|
||
|
|
{
|
||
|
|
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||
|
|
await foreach (Envelope message in client.ReadEventsAsync(timeout.Token)) if (predicate(message)) return message;
|
||
|
|
throw new IOException("Expected client event was not received.");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task ManagedClientsAuthenticateChatAndCorrelateConcurrentRequests()
|
||
|
|
{
|
||
|
|
await using var fixture = new ServerFixture();
|
||
|
|
await using var alice = NewClient(fixture, "Alice"); await using var bob = NewClient(fixture, "Bob");
|
||
|
|
await Connect(alice, fixture); await Connect(bob, fixture);
|
||
|
|
Assert.True((await alice.AuthenticateGuestAsync("Alice")).Ok); Assert.True((await bob.AuthenticateGuestAsync("Bob")).Ok);
|
||
|
|
await Event(bob, e => e.ServerState is not null); await Event(alice, e => e.UserEvent?.User?.Nickname == "Bob");
|
||
|
|
Assert.Equal(2, alice.Users.Count);
|
||
|
|
var copy = alice.Users[0]; copy.Nickname = "Mutated"; Assert.DoesNotContain(alice.Users, u => u.Nickname == "Mutated");
|
||
|
|
alice.Send(new() { TextMessage = new() { Scope = TextScope.TextChannel, TargetId = 1, Body = "Managed conversation", ClientMsgId = "a1" } });
|
||
|
|
Assert.Equal("Managed conversation", (await Event(bob, e => e.TextMessage is not null)).TextMessage.Body);
|
||
|
|
var requests = Enumerable.Range(1, 20).Select(async i =>
|
||
|
|
{
|
||
|
|
Envelope response = await alice.RequestAsync(new() { Ping = new() { Nonce = (ulong)i } });
|
||
|
|
Assert.Equal((ulong)i, response.Pong.Nonce); return response.RequestId;
|
||
|
|
});
|
||
|
|
Assert.Equal(20, (await Task.WhenAll(requests)).Distinct().Count());
|
||
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => Connect(alice, fixture));
|
||
|
|
Assert.Equal(ClientConnectionState.Connected, alice.State);
|
||
|
|
await alice.DisconnectAsync();
|
||
|
|
await Event(bob, e => e.UserEvent?.Kind == UserEvent.Types.Kind.Left);
|
||
|
|
await Connect(alice, fixture); Assert.True((await alice.AuthenticateGuestAsync("Returned")).Ok);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task TofuRequiresApprovalPinsAcceptedCertificateAndRejectsChanges()
|
||
|
|
{
|
||
|
|
await using var first = new ServerFixture(); await using var second = new ServerFixture();
|
||
|
|
await using var client = NewClient(first, "Tofu");
|
||
|
|
await Assert.ThrowsAsync<System.Security.Authentication.AuthenticationException>(() => client.ConnectAsync("127.0.0.1", (ushort)first.Server.EndPoint.Port));
|
||
|
|
await client.ConnectAsync("127.0.0.1", (ushort)first.Server.EndPoint.Port, (challenge, _) =>
|
||
|
|
{ Assert.Equal(TofuStatus.FirstConnect, challenge.Status); return ValueTask.FromResult(true); });
|
||
|
|
await client.DisconnectAsync();
|
||
|
|
await client.ConnectAsync("127.0.0.1", (ushort)first.Server.EndPoint.Port); await client.DisconnectAsync();
|
||
|
|
// Pin the other server's certificate to this endpoint, simulating a changed server certificate.
|
||
|
|
using var credentials = ServerCredentials.LoadOrCreate(second.Directory, "VoiceCat Server");
|
||
|
|
new TofuStore(Path.Combine(first.Directory, "Other.pins")).Pin("127.0.0.1", (ushort)first.Server.EndPoint.Port, credentials.CertificateFingerprint);
|
||
|
|
await using var changed = new VoiceCatClient(tofuStorePath: Path.Combine(first.Directory, "Other.pins"));
|
||
|
|
await Assert.ThrowsAsync<System.Security.Authentication.AuthenticationException>(() => changed.ConnectAsync("127.0.0.1", (ushort)first.Server.EndPoint.Port,
|
||
|
|
(challenge, _) => { Assert.Equal(TofuStatus.Mismatch, challenge.Status); return ValueTask.FromResult(false); }));
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task ManagedClientSendsAndReceivesAuthenticatedEncodedVoice()
|
||
|
|
{
|
||
|
|
await using var fixture = new ServerFixture();
|
||
|
|
await using var managed = NewClient(fixture, "Managed"); await Connect(managed, fixture); await managed.AuthenticateGuestAsync("Managed");
|
||
|
|
Assert.True((await managed.SubscribeVoiceAsync()).Ok);
|
||
|
|
await using var peer = await VoicePeer.ConnectAsync(fixture, "Peer");
|
||
|
|
var remote = await peer.AnnounceAsync(StreamKind.StreamMic);
|
||
|
|
var local = (await managed.RequestAsync(new() { StreamAnnounce = new() { Kind = StreamKind.StreamMic } })).StreamAnnounceResult;
|
||
|
|
Assert.True(local.Ok);
|
||
|
|
var received = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||
|
|
managed.VoiceReceived += (header, payload) => { Assert.Equal(remote.Ssrc, header.Ssrc); received.TrySetResult(payload.ToArray()); };
|
||
|
|
await peer.SendAsync(peer.Seal(remote.Ssrc, [1, 2, 3]));
|
||
|
|
Assert.Equal(new byte[] { 1, 2, 3 }, await received.Task.WaitAsync(TimeSpan.FromSeconds(5)));
|
||
|
|
Assert.True(managed.TrySendEncodedVoice(local.Ssrc, 960, [4, 5, 6]));
|
||
|
|
Assert.Equal(new byte[] { 4, 5, 6 }, (await peer.ReceiveVoiceAsync()).Payload);
|
||
|
|
}
|
||
|
|
}
|