diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/AutoReconnectLiveConnection.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/AutoReconnectLiveConnection.cs index 3530c679a8..9b26cb6eb9 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/AutoReconnectLiveConnection.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/AutoReconnectLiveConnection.cs @@ -2,9 +2,8 @@ using DCL.Diagnostics; using LiveKit.Internal.FFIClients.Pools.Memory; using System; -using System.Net.Sockets; using System.Threading; -using UnityEngine; +using Utility.Types; namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections { @@ -27,7 +26,7 @@ public AutoReconnectLiveConnection(IArchipelagoLiveConnection origin, Action ConnectAsync(string adapterUrl, CancellationToken token) { cachedAdapterUrl = adapterUrl; return origin.ConnectAsync(adapterUrl, token); @@ -39,26 +38,32 @@ public UniTask DisconnectAsync(CancellationToken token) return origin.DisconnectAsync(token); } - public async UniTask SendAsync(MemoryWrap data, CancellationToken token) + public async UniTask> SendAsync(MemoryWrap data, CancellationToken token) { - try { await origin.SendAsync(data, token); } - catch (SocketException e) + var result = await origin.SendAsync(data, token); + + if (result.Error?.State is IArchipelagoLiveConnection.ResponseError.ConnectionClosed) { - log($"Connection lost on sending, trying to reconnect... {e}"); + log("Connection error on sending, ensure to reconnect..."); await EnsureReconnectAsync(token); - await SendAsync(data, token); + return await SendAsync(data, token); } + + return result; } - public async UniTask ReceiveAsync(CancellationToken token) + public async UniTask> ReceiveAsync(CancellationToken token) { - try { return await origin.ReceiveAsync(token); } - catch (ConnectionClosedException) + var result = await origin.ReceiveAsync(token); + + if (result.Error?.State is IArchipelagoLiveConnection.ResponseError.ConnectionClosed) { log("Connection error on receiving, ensure to reconnect..."); await EnsureReconnectAsync(token); return await ReceiveAsync(token); } + + return result; } private async UniTask EnsureReconnectAsync(CancellationToken token) diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/ConnectionClosedException.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/ConnectionClosedException.cs index f8af65764a..f3a796cbaf 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/ConnectionClosedException.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/ConnectionClosedException.cs @@ -1,5 +1,7 @@ +using LiveKit.Internal.FFIClients.Pools.Memory; using System; using System.Net.WebSockets; +using Utility.Types; namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections { @@ -13,5 +15,11 @@ public ConnectionClosedException(WebSocket webSocket) : base("Connection closed" { this.webSocket = webSocket; } + + public static EnumResult NewErrorResult(WebSocket webSocket) => + EnumResult.ErrorResult( + IArchipelagoLiveConnection.ResponseError.ConnectionClosed, + $"WebSocket closed with state: {webSocket.State} with status: {webSocket.CloseStatus} with description: {webSocket.CloseStatusDescription} - Connection closed" + ); } } diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/IArchipelagoLiveConnection.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/IArchipelagoLiveConnection.cs index 44c591543d..bd0a4bac71 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/IArchipelagoLiveConnection.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/IArchipelagoLiveConnection.cs @@ -2,8 +2,8 @@ using DCL.Multiplayer.Connections.Messaging; using Google.Protobuf; using LiveKit.Internal.FFIClients.Pools.Memory; -using Nethereum.Model; using System.Threading; +using Utility.Types; namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections { @@ -11,16 +11,22 @@ public interface IArchipelagoLiveConnection { bool IsConnected { get; } - UniTask ConnectAsync(string adapterUrl, CancellationToken token); + UniTask ConnectAsync(string adapterUrl, CancellationToken token); UniTask DisconnectAsync(CancellationToken token); /// takes the ownership for the data /// cancellation token /// returns a memory chunk ang gives the ownership for it - UniTask SendAsync(MemoryWrap data, CancellationToken token); + UniTask> SendAsync(MemoryWrap data, CancellationToken token); - UniTask ReceiveAsync(CancellationToken token); + UniTask> ReceiveAsync(CancellationToken token); + + enum ResponseError + { + MessageError, + ConnectionClosed, + } } public static class ArchipelagoLiveConnectionExtensions @@ -41,17 +47,17 @@ public static async UniTask SendAsync(this IArchipelagoLiveConnection connect /// /// Takes ownership for the data and returns the ownership for the result /// - public static async UniTask SendAndReceiveAsync(this IArchipelagoLiveConnection archipelagoLiveConnection, MemoryWrap data, CancellationToken token) + public static async UniTask> SendAndReceiveAsync(this IArchipelagoLiveConnection archipelagoLiveConnection, MemoryWrap data, CancellationToken token) { await archipelagoLiveConnection.SendAsync(data, token); return await archipelagoLiveConnection.ReceiveAsync(token); } - public static async UniTask SendAndReceiveAsync(this IArchipelagoLiveConnection connection, T message, IMemoryPool memoryPool, CancellationToken token) where T: IMessage + public static async UniTask> SendAndReceiveAsync(this IArchipelagoLiveConnection connection, T message, IMemoryPool memoryPool, CancellationToken token) where T: IMessage { using MemoryWrap memory = memoryPool.Memory(message); message.WriteTo(memory); - MemoryWrap result = await connection.SendAndReceiveAsync(memory, token); + var result = await connection.SendAndReceiveAsync(memory, token); return result; } diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/LogArchipelagoLiveConnection.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/LogArchipelagoLiveConnection.cs index 6aabf40778..a0485f81f8 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/LogArchipelagoLiveConnection.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/LogArchipelagoLiveConnection.cs @@ -4,6 +4,7 @@ using LiveKit.Internal.FFIClients.Pools.Memory; using System; using System.Threading; +using Utility.Types; namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections { @@ -38,11 +39,12 @@ public LogArchipelagoLiveConnection(IArchipelagoLiveConnection origin, Action ConnectAsync(string adapterUrl, CancellationToken token) { log($"ArchipelagoLiveConnection ConnectAsync start to: {adapterUrl}"); - await origin.ConnectAsync(adapterUrl, token); - log($"ArchipelagoLiveConnection ConnectAsync finished to: {adapterUrl}"); + var result = await origin.ConnectAsync(adapterUrl, token); + log($"ArchipelagoLiveConnection ConnectAsync finished to: {adapterUrl} with result: {result.Success}"); + return result; } public async UniTask DisconnectAsync(CancellationToken token) @@ -52,18 +54,19 @@ public async UniTask DisconnectAsync(CancellationToken token) log("ArchipelagoLiveConnection DisconnectAsync finished"); } - public async UniTask SendAsync(MemoryWrap data, CancellationToken token) + public async UniTask> SendAsync(MemoryWrap data, CancellationToken token) { log($"ArchipelagoLiveConnection SendAsync start with size: {data.Length} and content: {data.HexReadableString()}"); - await origin.SendAsync(data, token); + var result = await origin.SendAsync(data, token); log($"ArchipelagoLiveConnection SendAsync finished with size: {data.Length} and content: {data.HexReadableString()}"); + return result; } - public async UniTask ReceiveAsync(CancellationToken token) + public async UniTask> ReceiveAsync(CancellationToken token) { log("ArchipelagoLiveConnection ReceiveAsync start"); - MemoryWrap result = await origin.ReceiveAsync(token); - log($"ArchipelagoLiveConnection ReceiveAsync finished with size: {result.Length}"); + var result = await origin.ReceiveAsync(token); + log($"ArchipelagoLiveConnection ReceiveAsync finished with error: {result.Error?.Message ?? "no error"}, size: {(result.Success ? result.Value.Length : 0)}"); return result; } } diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/WebSocketArchipelagoLiveConnection.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/WebSocketArchipelagoLiveConnection.cs index 576bf7e8c9..8762daaafc 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/WebSocketArchipelagoLiveConnection.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/LiveConnections/WebSocketArchipelagoLiveConnection.cs @@ -8,6 +8,7 @@ using System.Threading; using Utility.Multithreading; using Utility.Ownership; +using Utility.Types; namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections { @@ -34,12 +35,16 @@ public WebSocketArchipelagoLiveConnection(Func webSocketFactory current = Current.New(webSocketFactory); } - public async UniTask ConnectAsync(string adapterUrl, CancellationToken token) + public async UniTask ConnectAsync(string adapterUrl, CancellationToken token) { TryUpdateWebSocket(); - try { await current!.Value.WebSocket.ConnectAsync(new Uri(adapterUrl), token).AsUniTask(false); } - catch (Exception e) { throw new Exception($"Cannot connect to adapter url: {adapterUrl}", e); } + try + { + await current!.Value.WebSocket.ConnectAsync(new Uri(adapterUrl), token).AsUniTask(false); + return Result.SuccessResult(); + } + catch (Exception e) { return Result.ErrorResult($"Cannot connect to adapter url: {adapterUrl}, {e.Message}"); } } public UniTask DisconnectAsync(CancellationToken token) @@ -48,21 +53,37 @@ public UniTask DisconnectAsync(CancellationToken token) return current!.Value.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, token)!.AsUniTask(); } - public async UniTask SendAsync(MemoryWrap data, CancellationToken token) + public async UniTask> SendAsync(MemoryWrap data, CancellationToken token) { if (IsWebSocketInvalid()) - throw new InvalidOperationException( - $"Cannot send data, ensure that connection is correct, the connection is invalid: {current?.WebSocket.State}" - ); + return EnumResult + .ErrorResult( + IArchipelagoLiveConnection.ResponseError.MessageError, + $"Cannot send data, ensure that connection is correct, the connection is invalid: {current?.WebSocket.State}" + ); - using (data) - await current!.Value.WebSocket.SendAsync(data.DangerousArraySegment(), WebSocketMessageType.Binary, true, token)!; + try + { + using (data) + await current!.Value.WebSocket.SendAsync(data.DangerousArraySegment(), WebSocketMessageType.Binary, true, token)!; + + return EnumResult.SuccessResult(); + } + catch (Exception e) + { + return EnumResult + .ErrorResult( + IArchipelagoLiveConnection.ResponseError.ConnectionClosed, + $"Cannot send data, {e.Message}" + ); + } } - public async UniTask ReceiveAsync(CancellationToken token) + public async UniTask> ReceiveAsync(CancellationToken token) { if (IsWebSocketInvalid()) - throw new InvalidOperationException( + return EnumResult.ErrorResult( + IArchipelagoLiveConnection.ResponseError.MessageError, $"Cannot receive data, ensure that connection is correct, the connection is invalid: {current?.WebSocket.State}" ); @@ -73,17 +94,29 @@ public async UniTask ReceiveAsync(CancellationToken token) using MemoryWrap memory = memoryPool.Memory(BUFFER_SIZE); byte[] buffer = memory.DangerousBuffer(); - WebSocketReceiveResult? result = await current!.Value.WebSocket.ReceiveAsync(buffer, token)!; - - return result.MessageType switch - { - WebSocketMessageType.Text => throw new NotSupportedException( - $"Expected Binary, Text messages are not supported: {AsText(result, buffer)}" - ), - WebSocketMessageType.Binary => CopiedMemory(buffer, result.Count), - WebSocketMessageType.Close => throw new ConnectionClosedException(current!.Value.WebSocket), - _ => throw new ArgumentOutOfRangeException(), - }; + + try + { + WebSocketReceiveResult? result = await current!.Value.WebSocket.ReceiveAsync(buffer, token)!; + + return result.MessageType switch + { + WebSocketMessageType.Text => EnumResult.ErrorResult(IArchipelagoLiveConnection.ResponseError.MessageError, $"Expected Binary, Text messages are not supported: {AsText(result, buffer)}"), + WebSocketMessageType.Binary => EnumResult.SuccessResult(CopiedMemory(buffer, result.Count)), + WebSocketMessageType.Close => ConnectionClosedException.NewErrorResult(current!.Value.WebSocket), + _ => EnumResult.ErrorResult( + IArchipelagoLiveConnection.ResponseError.MessageError, + $"Unknown message type: {result.MessageType}" + ), + }; + } + catch (Exception e) + { + return EnumResult.ErrorResult( + IArchipelagoLiveConnection.ResponseError.MessageError, + $"Cannot receive data, {e.Message}" + ); + } } private static string AsText(WebSocketReceiveResult result, byte[] buffer) diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/Rooms/ArchipelagoIslandRoom.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/Rooms/ArchipelagoIslandRoom.cs index 8b3a66a771..cea1f6c0d6 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/Rooms/ArchipelagoIslandRoom.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/Rooms/ArchipelagoIslandRoom.cs @@ -120,8 +120,15 @@ private async UniTask> WelcomePeerIdAsync(string adapterUrl, IWeb3Identity identity = web3IdentityCache.EnsuredIdentity(); await signFlow.EnsureConnectedAsync(adapterUrl, token); string ethereumAddress = identity.Address; - string messageForSign = await signFlow.MessageForSignAsync(ethereumAddress, token); - string signedMessage = identity.Sign(messageForSign).ToJson(); + var messageForSignResult = await signFlow.MessageForSignAsync(ethereumAddress, token); + + if (messageForSignResult.Success == false) + { + ReportHub.LogError(ReportCategory.ARCHIPELAGO_REQUEST, $"Cannot obtain a message to sign a welcome peer"); + return LightResult.FAILURE; + } + + string signedMessage = identity.Sign(messageForSignResult.Result).ToJson(); ReportHub.Log(ReportCategory.ARCHIPELAGO_REQUEST, $"Signed message: {signedMessage}"); return await signFlow.WelcomePeerIdAsync(signedMessage, token); } diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/IArchipelagoSignFlow.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/IArchipelagoSignFlow.cs index bce333cbde..8ac61f6f2e 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/IArchipelagoSignFlow.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/IArchipelagoSignFlow.cs @@ -11,7 +11,7 @@ public interface IArchipelagoSignFlow { UniTask EnsureConnectedAsync(string adapterUrl, CancellationToken token); - UniTask MessageForSignAsync(string ethereumAddress, CancellationToken token); + UniTask> MessageForSignAsync(string ethereumAddress, CancellationToken token); UniTask> WelcomePeerIdAsync(string signedMessageAuthChainJson, CancellationToken token); diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/LiveConnectionArchipelagoSignFlow.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/LiveConnectionArchipelagoSignFlow.cs index ad6f70afc0..d51a08fcac 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/LiveConnectionArchipelagoSignFlow.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/LiveConnectionArchipelagoSignFlow.cs @@ -46,23 +46,25 @@ public async UniTask EnsureConnectedAsync(string adapterUrl, CancellationToken t catch (Exception e) { ReportHub.LogException(new Exception($"Cannot ensure connection {adapterUrl}", e), ReportCategory.LIVEKIT); } } - public async UniTask MessageForSignAsync(string ethereumAddress, CancellationToken token) + public async UniTask> MessageForSignAsync(string ethereumAddress, CancellationToken token) { - try + using SmartWrap challenge = multiPool.TempResource(); + challenge.value.Address = ethereumAddress; + using SmartWrap clientPacket = multiPool.TempResource(); + clientPacket.value.ClearMessage(); + clientPacket.value.ChallengeRequest = challenge.value; + var result = await connection.SendAndReceiveAsync(clientPacket.value, memoryPool, token); + + if (result.Success == false) { - using SmartWrap challenge = multiPool.TempResource(); - challenge.value.Address = ethereumAddress; - using SmartWrap clientPacket = multiPool.TempResource(); - clientPacket.value.ClearMessage(); - clientPacket.value.ChallengeRequest = challenge.value; - using MemoryWrap response = await connection.SendAndReceiveAsync(clientPacket.value, memoryPool, token); - using var serverPacket = new SmartWrap(response.AsMessageServerPacket(), multiPool); - using var challengeResponse = new SmartWrap(serverPacket.value.ChallengeResponse!, multiPool); - return challengeResponse.value.ChallengeToSign!; + ReportHub.LogError(ReportCategory.LIVEKIT, $"Cannot message for sign for address {ethereumAddress}: {result.Error?.Message}"); + return LightResult.FAILURE; } - catch (Exception e) { ReportHub.LogException(new Exception($"Cannot message for sign for address {ethereumAddress}", e), ReportCategory.LIVEKIT); } - return string.Empty; + using MemoryWrap response = result.Value; + using var serverPacket = new SmartWrap(response.AsMessageServerPacket(), multiPool); + using var challengeResponse = new SmartWrap(serverPacket.value.ChallengeResponse!, multiPool); + return challengeResponse.value.ChallengeToSign!.AsSuccess(); } public async UniTask> WelcomePeerIdAsync(string signedMessageAuthChainJson, CancellationToken token) @@ -78,7 +80,7 @@ public async UniTask> WelcomePeerIdAsync(string signedMessag var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource().Token, token); - (bool hasResultLeft, MemoryWrap result) result = await UniTask.WhenAny( + (bool hasResultLeft, EnumResult result) result = await UniTask.WhenAny( connection.SendAndReceiveAsync(clientPacket.value, memoryPool, linkedToken.Token), connection.WaitDisconnectAsync(linkedToken.Token) ); @@ -87,7 +89,10 @@ public async UniTask> WelcomePeerIdAsync(string signedMessag if (result.hasResultLeft) { - using MemoryWrap response = result.result; + if (result.result.Success == false) + return LightResult.FAILURE; + + using MemoryWrap response = result.result.Value; using var serverPacket = new SmartWrap(response.AsMessageServerPacket(), multiPool); using var welcomeMessage = new SmartWrap(serverPacket.value.Welcome!, multiPool); return welcomeMessage.value.PeerId.AsSuccess(); @@ -140,7 +145,15 @@ public async UniTaskVoid StartListeningForConnectionStringAsync(Action o if (connection.IsConnected == false) throw new InvalidOperationException("Connection is not established"); - MemoryWrap response = await connection.ReceiveAsync(token); + var result = await connection.ReceiveAsync(token); + + if (result.Success == false) + { + ReportHub.LogError(ReportCategory.LIVEKIT, $"Cannot listen for connection string: {result.Error?.Message}"); + continue; + } + + using MemoryWrap response = result.Value; using var serverPacket = new SmartWrap(response.AsMessageServerPacket(), multiPool); if (serverPacket.value.MessageCase is ServerPacket.MessageOneofCase.IslandChanged) diff --git a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/LogArchipelagoSignFlow.cs b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/LogArchipelagoSignFlow.cs index 42488353da..aac1aaacd5 100644 --- a/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/LogArchipelagoSignFlow.cs +++ b/Explorer/Assets/DCL/Multiplayer/Connections/Archipelago/SignFlow/LogArchipelagoSignFlow.cs @@ -32,11 +32,11 @@ public async UniTask EnsureConnectedAsync(string adapterUrl, CancellationToken t log($"{PREFIX} Connect finish for {adapterUrl}"); } - public async UniTask MessageForSignAsync(string ethereumAddress, CancellationToken token) + public async UniTask> MessageForSignAsync(string ethereumAddress, CancellationToken token) { log($"{PREFIX} MessageForSignAsync start for address {ethereumAddress}"); - string result = await origin.MessageForSignAsync(ethereumAddress, token); - log($"{PREFIX} MessageForSignAsync finish for address {ethereumAddress} with result {result}"); + var result = await origin.MessageForSignAsync(ethereumAddress, token); + log($"{PREFIX} MessageForSignAsync finish for address {ethereumAddress} with result success: {result.Success}"); return result; } diff --git a/Explorer/Assets/Scripts/Utility/Types/Result.cs b/Explorer/Assets/Scripts/Utility/Types/Result.cs index ab063334d2..d08091dac7 100644 --- a/Explorer/Assets/Scripts/Utility/Types/Result.cs +++ b/Explorer/Assets/Scripts/Utility/Types/Result.cs @@ -37,4 +37,42 @@ public static Result SuccessResult(T value) => public static Result ErrorResult(string errorMessage) => new (default(T)!, errorMessage); } + + public readonly struct EnumResult + { + public readonly (TErrorEnum State, string Message)? Error; + + public bool Success => Error == null; + + private EnumResult((TErrorEnum State, string Message)? error) + { + this.Error = error; + } + + public static EnumResult SuccessResult() => + new (null); + + public static EnumResult ErrorResult(TErrorEnum state, string errorMessage) => + new ((state, errorMessage)); + } + + public readonly struct EnumResult + { + public readonly TValue Value; + public readonly (TErrorEnum State, string Message)? Error; + + public bool Success => Error == null; + + private EnumResult(TValue value, (TErrorEnum State, string Message)? error) + { + this.Value = value; + this.Error = error; + } + + public static EnumResult SuccessResult(TValue value) => + new (value, null); + + public static EnumResult ErrorResult(TErrorEnum state, string errorMessage) => + new (default(TValue)!, (state, errorMessage)); + } }