From 33dd93ef67aaddd4ed3ffc6cc1dc41393d47ea41 Mon Sep 17 00:00:00 2001 From: Vitalii Kotenko Date: Sat, 20 Jul 2024 20:08:36 +0300 Subject: [PATCH] link events --- .../Models/General/Player.cs | 6 +- .../Clientbound/PlayerInfoUpdatePacket.cs | 6 +- .../Events/Links/CreateLinkEvent.cs | 15 +++++ .../Events/Links/StartLinkEvent.cs | 8 +++ .../Events/Links/StopLinkEvent.cs | 8 +++ .../Events/Services/IEventService.cs | 4 +- .../ProtocolSupportPlugin.cs | 1 - src/Void.Proxy/Events/EventService.cs | 4 +- src/Void.Proxy/Links/Link.cs | 21 ++++--- src/Void.Proxy/Links/LinkService.cs | 63 ++++++++++++++----- src/Void.Proxy/Platform.cs | 3 +- 11 files changed, 105 insertions(+), 34 deletions(-) create mode 100644 src/Void.Proxy.API/Events/Links/CreateLinkEvent.cs create mode 100644 src/Void.Proxy.API/Events/Links/StartLinkEvent.cs create mode 100644 src/Void.Proxy.API/Events/Links/StopLinkEvent.cs diff --git a/src/Void.Proxy-before/Models/General/Player.cs b/src/Void.Proxy-before/Models/General/Player.cs index cb13cee..1aa17f6 100644 --- a/src/Void.Proxy-before/Models/General/Player.cs +++ b/src/Void.Proxy-before/Models/General/Player.cs @@ -87,10 +87,10 @@ static byte[] TwosComplement(byte[] data) for (var i = data.Length - 1; i >= 0; i--) { data[i] = unchecked((byte)~data[i]); - + if (!carry) continue; - + carry = data[i] == 0xFF; data[i]++; } @@ -127,7 +127,7 @@ static byte[] TwosComplement(byte[] data) if (GameProfile == null || IdentifiedKey == null || IdentifiedKey.Revision != IdentifiedKeyRevision.LINKED_V2) return GameProfile; - + if (!IdentifiedKey.AddGuid(GameProfile.Id)) throw new Exception("multiplayer.disconnect.invalid_public_key"); diff --git a/src/Void.Proxy-before/Network/Protocol/Packets/Clientbound/PlayerInfoUpdatePacket.cs b/src/Void.Proxy-before/Network/Protocol/Packets/Clientbound/PlayerInfoUpdatePacket.cs index edcac57..ff876aa 100644 --- a/src/Void.Proxy-before/Network/Protocol/Packets/Clientbound/PlayerInfoUpdatePacket.cs +++ b/src/Void.Proxy-before/Network/Protocol/Packets/Clientbound/PlayerInfoUpdatePacket.cs @@ -12,7 +12,7 @@ public struct PlayerInfoUpdatePacket : IMinecraftPacket public void Encode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) { // if no players available, notchian server still send actions bitset, we dont? - var actions = Players.SelectMany(player => player.Actions.Select(action => action.Type)); + var actions = Players.SelectMany(player => player.Actions.Select(action => action.Type)).ToArray(); var actionsFlags = (byte)actions.Aggregate(0, (current, action) => current | (byte)action); buffer.WriteUnsignedByte(actionsFlags); @@ -20,7 +20,7 @@ public void Encode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) foreach (var player in Players) { - var missingActions = actions.Where(actionType => player.Actions.All(action => action.Type != actionType)); + var missingActions = actions.Where(actionType => player.Actions.All(action => action.Type != actionType)).ToArray(); if (missingActions.Any()) throw new Exception($"Player {player.Guid} has missing actions: {string.Join(", ", missingActions)}"); @@ -54,6 +54,7 @@ public void Decode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) var playerActions = new List(); foreach (var action in actions) + { playerActions.Add(action switch { PlayerActionType.AddPlayer => new AddPlayerAction(ref buffer, protocolVersion), @@ -64,6 +65,7 @@ public void Decode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) PlayerActionType.UpdateDisplayName => new UpdateDisplayNameAction(ref buffer, protocolVersion), _ => throw new ArgumentOutOfRangeException($"Unknown update player action type {action}") }); + } Players.Add(new PlayerInfo(playerGuid, playerActions)); } diff --git a/src/Void.Proxy.API/Events/Links/CreateLinkEvent.cs b/src/Void.Proxy.API/Events/Links/CreateLinkEvent.cs new file mode 100644 index 0000000..4fce65c --- /dev/null +++ b/src/Void.Proxy.API/Events/Links/CreateLinkEvent.cs @@ -0,0 +1,15 @@ +using Void.Proxy.API.Links; +using Void.Proxy.API.Network.IO.Channels; +using Void.Proxy.API.Players; +using Void.Proxy.API.Servers; + +namespace Void.Proxy.API.Events.Links; + +public class CreateLinkEvent : IEventWithResult +{ + public required IPlayer Player { get; init; } + public required IServer Server { get; init; } + public required IMinecraftChannel PlayerChannel { get; init; } + public required IMinecraftChannel ServerChannel { get; init; } + public ILink? Result { get; set; } +} \ No newline at end of file diff --git a/src/Void.Proxy.API/Events/Links/StartLinkEvent.cs b/src/Void.Proxy.API/Events/Links/StartLinkEvent.cs new file mode 100644 index 0000000..290c875 --- /dev/null +++ b/src/Void.Proxy.API/Events/Links/StartLinkEvent.cs @@ -0,0 +1,8 @@ +using Void.Proxy.API.Links; + +namespace Void.Proxy.API.Events.Links; + +public class StartLinkEvent : IEvent +{ + public required ILink Link { get; init; } +} \ No newline at end of file diff --git a/src/Void.Proxy.API/Events/Links/StopLinkEvent.cs b/src/Void.Proxy.API/Events/Links/StopLinkEvent.cs new file mode 100644 index 0000000..dc9a280 --- /dev/null +++ b/src/Void.Proxy.API/Events/Links/StopLinkEvent.cs @@ -0,0 +1,8 @@ +using Void.Proxy.API.Links; + +namespace Void.Proxy.API.Events.Links; + +public class StopLinkEvent : IEvent +{ + public required ILink Link { get; init; } +} \ No newline at end of file diff --git a/src/Void.Proxy.API/Events/Services/IEventService.cs b/src/Void.Proxy.API/Events/Services/IEventService.cs index 2badff9..91b55d5 100644 --- a/src/Void.Proxy.API/Events/Services/IEventService.cs +++ b/src/Void.Proxy.API/Events/Services/IEventService.cs @@ -4,6 +4,6 @@ public interface IEventService { public ValueTask ThrowAsync(CancellationToken cancellationToken = default) where T : IEvent, new(); public ValueTask ThrowAsync(T @event, CancellationToken cancellationToken = default) where T : IEvent; - public void RegisterListeners(IEventListener[] listeners); - public void UnregisterListeners(IEventListener[] listeners); + public void RegisterListeners(params IEventListener[] listeners); + public void UnregisterListeners(params IEventListener[] listeners); } \ No newline at end of file diff --git a/src/Void.Proxy.Plugins.ProtocolSupport.Java.v1_20_2_to_latest/ProtocolSupportPlugin.cs b/src/Void.Proxy.Plugins.ProtocolSupport.Java.v1_20_2_to_latest/ProtocolSupportPlugin.cs index 6b63a01..560a01d 100644 --- a/src/Void.Proxy.Plugins.ProtocolSupport.Java.v1_20_2_to_latest/ProtocolSupportPlugin.cs +++ b/src/Void.Proxy.Plugins.ProtocolSupport.Java.v1_20_2_to_latest/ProtocolSupportPlugin.cs @@ -5,7 +5,6 @@ using Void.Proxy.API.Network.IO.Buffers; using Void.Proxy.API.Network.IO.Channels; using Void.Proxy.API.Network.IO.Streams; -using Void.Proxy.API.Network.IO.Streams.Codec; using Void.Proxy.API.Network.Protocol; using Void.Proxy.API.Plugins; diff --git a/src/Void.Proxy/Events/EventService.cs b/src/Void.Proxy/Events/EventService.cs index de6bd78..9be9415 100644 --- a/src/Void.Proxy/Events/EventService.cs +++ b/src/Void.Proxy/Events/EventService.cs @@ -37,7 +37,7 @@ public async ValueTask ThrowAsync(T @event, CancellationToken cancellationTok } } - public void RegisterListeners(IEventListener[] listeners) + public void RegisterListeners(params IEventListener[] listeners) { foreach (var listener in listeners) { @@ -56,7 +56,7 @@ public void RegisterListeners(IEventListener[] listeners) _listeners.AddRange(listeners); } - public void UnregisterListeners(IEventListener[] listeners) + public void UnregisterListeners(params IEventListener[] listeners) { foreach (var listener in listeners) { diff --git a/src/Void.Proxy/Links/Link.cs b/src/Void.Proxy/Links/Link.cs index 6e6de0d..159fc03 100644 --- a/src/Void.Proxy/Links/Link.cs +++ b/src/Void.Proxy/Links/Link.cs @@ -1,4 +1,7 @@ using Nito.AsyncEx; +using Void.Proxy.API.Events; +using Void.Proxy.API.Events.Links; +using Void.Proxy.API.Events.Services; using Void.Proxy.API.Links; using Void.Proxy.API.Network.IO.Channels; using Void.Proxy.API.Network.IO.Messages; @@ -15,23 +18,23 @@ public class Link : ILink private readonly CancellationTokenSource _ctsServerToPlayer; private readonly CancellationTokenSource _ctsServerToPlayerForce; - private readonly Func _finalizer; - private readonly AsyncLock _lock; private readonly ILogger _logger; + private readonly IEventService _events; + private readonly AsyncLock _lock; private readonly Task _playerToServerTask; private readonly Task _serverToPlayerTask; - public Link(IPlayer player, IServer server, IMinecraftChannel playerChannel, IMinecraftChannel serverChannel, Func finalize) + public Link(IPlayer player, IServer server, IMinecraftChannel playerChannel, IMinecraftChannel serverChannel, IEventService events) { Player = player; Server = server; PlayerChannel = playerChannel; ServerChannel = serverChannel; - _lock = new AsyncLock(); - _finalizer = finalize; _logger = player.Scope.ServiceProvider.GetRequiredService>(); + _events = events; + _lock = new AsyncLock(); _ctsPlayerToServer = new CancellationTokenSource(); _ctsPlayerToServerForce = new CancellationTokenSource(); @@ -62,7 +65,7 @@ public async ValueTask DisposeAsync() { _logger.LogInformation("Timed out waiting Server {Server} disconnection from Player {Player} manually, closing forcefully", Server, Player); await _ctsPlayerToServerForce.CancelAsync(); - + if (await WaitWithTimeout(_serverToPlayerTask)) throw new Exception($"Cannot dispose Link {this} (player=>server)"); } @@ -124,8 +127,8 @@ protected async Task ExecuteAsync(IMinecraftChannel sourceChannel, IMinecraftCha catch (Exception exception) when (exception is EndOfStreamException or IOException or TaskCanceledException or OperationCanceledException or ObjectDisposedException) { // client disconnected itself - // server catch unhandled exception - // link does server switch + // or server catch unhandled exception + // or link does server switch } catch (Exception exception) { @@ -136,7 +139,7 @@ protected async Task ExecuteAsync(IMinecraftChannel sourceChannel, IMinecraftCha await PlayerChannel.FlushAsync(); await ServerChannel.FlushAsync(); - _ = _finalizer(this); + _ = _events.ThrowAsync(new StopLinkEvent { Link = this }, forceCancellationToken); } } } \ No newline at end of file diff --git a/src/Void.Proxy/Links/LinkService.cs b/src/Void.Proxy/Links/LinkService.cs index ecbee4b..05404f0 100644 --- a/src/Void.Proxy/Links/LinkService.cs +++ b/src/Void.Proxy/Links/LinkService.cs @@ -1,38 +1,73 @@ -using Void.Proxy.API.Links; +using Void.Proxy.API.Events; +using Void.Proxy.API.Events.Links; +using Void.Proxy.API.Events.Services; +using Void.Proxy.API.Links; +using Void.Proxy.API.Network.IO.Channels; using Void.Proxy.API.Players; using Void.Proxy.API.Servers; namespace Void.Proxy.Links; -public class LinkService( - ILogger logger, - IServerService servers) : ILinkService +public class LinkService : ILinkService, IEventListener { - private readonly List _links = []; + private readonly List _links =[]; + + private readonly ILogger _logger; + private readonly IServerService _servers; + private readonly IEventService _events; + + public LinkService(ILogger logger, IServerService servers, IEventService events) + { + _logger = logger; + _servers = servers; + _events = events; + + events.RegisterListeners(this); + } public async ValueTask ConnectPlayerAnywhereAsync(IPlayer player) { - var server = servers.RegisteredServers.First(); + var server = _servers.RegisteredServers.First(); var playerChannel = await player.GetChannelAsync(); var serverChannel = await player.BuildServerChannelAsync(server); - var link = new Link(player, server, playerChannel, serverChannel, FinalizeAsync); + var link = await CreateLinkAsync(player, server, playerChannel, serverChannel); _links.Add(link); - logger.LogInformation("Started forwarding {Link} traffic", link); + _logger.LogInformation("Started forwarding {Link} traffic", link); } - private async ValueTask FinalizeAsync(ILink link) + [Subscribe] + public async ValueTask OnStopLinkEvent(StopLinkEvent @event) { - if (!_links.Remove(link)) + if (!_links.Remove(@event.Link)) return; - await link.DisposeAsync(); + await using var _ = @event.Link; + + if (@event.Link.IsAlive) + throw new Exception($"Link {@event.Link} is still alive"); - if (link.IsAlive) - throw new Exception($"Link {link} is still alive"); + _logger.LogInformation("Stopped forwarding {Link} traffic", @event.Link); + } + + private async ValueTask CreateLinkAsync(IPlayer player, IServer server, IMinecraftChannel playerChannel, IMinecraftChannel serverChannel) + { + var @event = new CreateLinkEvent + { + Player = player, + Server = server, + PlayerChannel = playerChannel, + ServerChannel = serverChannel + }; + + await _events.ThrowAsync(@event); + + var link = @event.Result ?? new Link(player, server, playerChannel, serverChannel, _events); - logger.LogInformation("Stopped forwarding {Link} traffic", link); + await _events.ThrowAsync(new StartLinkEvent { Link = link }); + + return link; } } \ No newline at end of file diff --git a/src/Void.Proxy/Platform.cs b/src/Void.Proxy/Platform.cs index 7784a75..64fc66a 100644 --- a/src/Void.Proxy/Platform.cs +++ b/src/Void.Proxy/Platform.cs @@ -1,6 +1,5 @@ using System.Net.Sockets; using System.Reflection; -using System.Security.Cryptography; using Serilog.Core; using Serilog.Events; using Void.Proxy.API; @@ -56,6 +55,7 @@ public async Task StopAsync(CancellationToken cancellationToken) // TODO disconnect everyone here if (_backgroundTask is not null) + { await _backgroundTask.ContinueWith(backgroundTask => { if (backgroundTask.IsCanceled) @@ -67,6 +67,7 @@ await _backgroundTask.ContinueWith(backgroundTask => throw backgroundTask.Exception?.Flatten() .InnerException ?? new Exception("Proxy stopped with unknown exception"); }, cancellationToken); + } _listener?.Stop();