1
0
mirror of https://github.com/SineVector241/VoiceCraft-MCBE_Proximity_Chat.git synced 2024-12-14 02:37:54 +00:00
2024-09-03 12:13:11 +10:00

637 lines
29 KiB
C#

using System.Net.Sockets;
using System.Net;
using VoiceCraft.Core;
using VoiceCraft.Core.Packets;
using System.Collections.Concurrent;
using VoiceCraft.Core.Packets.VoiceCraft;
namespace VoiceCraft.Network.Sockets
{
public class VoiceCraft : Disposable
{
public const long MaxSendTime = 100;
public const int SIO_UDP_CONNRESET = -1744830452;
#region Variables
//Public Variables
public PacketRegistry PacketRegistry { get; set; } = new PacketRegistry();
public Socket Socket { get; private set; } = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
public IPEndPoint RemoteEndpoint { get; private set; } = new IPEndPoint(IPAddress.Any, 0);
public VoiceCraftSocketState State { get; private set; }
public int Timeout { get; set; } = 8000;
public bool IsConnected { get; private set; }
//Private Variables
private CancellationTokenSource CTS { get; set; } = new CancellationTokenSource();
private ConcurrentDictionary<SocketAddress, NetPeer> NetPeers { get; set; } = new ConcurrentDictionary<SocketAddress, NetPeer>(); //Server Variable
private NetPeer? ClientNetPeer { get; set; } //Client Variable
private Task? ActivityChecker { get; set; }
private Task? Sender { get; set; }
#endregion
public VoiceCraft()
{
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Login, typeof(Login));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Logout, typeof(Logout));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Accept, typeof(Accept));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Deny, typeof(Deny));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Ack, typeof(Ack));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Ping, typeof(Ping));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.PingInfo, typeof(PingInfo));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Binded, typeof(Binded));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Unbinded, typeof(Unbinded));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.ParticipantJoined, typeof(ParticipantJoined));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.ParticipantLeft, typeof(ParticipantLeft));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Mute, typeof(Mute));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Unmute, typeof(Unmute));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Deafen, typeof(Deafen));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.Undeafen, typeof(Undeafen));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.JoinChannel, typeof(JoinChannel));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.LeaveChannel, typeof(LeaveChannel));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.AddChannel, typeof(AddChannel));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.RemoveChannel, typeof(RemoveChannel));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.UpdatePosition, typeof(UpdatePosition));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.FullUpdatePosition, typeof(FullUpdatePosition));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.UpdateEnvironmentId, typeof(UpdateEnvironmentId));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.ClientAudio, typeof(ClientAudio));
PacketRegistry.RegisterPacket((byte)VoiceCraftPacketTypes.ServerAudio, typeof(ServerAudio));
}
#region Debug Settings
public bool LogExceptions { get; set; } = false;
public bool LogInbound { get; set; } = false;
public bool LogOutbound { get; set; } = false;
public List<VoiceCraftPacketTypes> InboundFilter { get; set; } = [];
public List<VoiceCraftPacketTypes> OutboundFilter { get; set; } = [];
#endregion
#region Delegates
public delegate void Connected(short key);
public delegate void Disconnected(string? reason = null);
public delegate void Started();
public delegate void Stopped(string? reason = null);
public delegate void PeerConnected(NetPeer peer, Login packet);
public delegate void PeerDisconnected(NetPeer peer, string? reason = null);
public delegate void PacketData<T>(T data, NetPeer peer);
//Error and Debug Events
public delegate void OutboundPacket(VoiceCraftPacket packet, NetPeer peer);
public delegate void InboundPacket(VoiceCraftPacket packet, NetPeer peer);
public delegate void ExceptionError(Exception error);
public delegate void Failed(Exception ex);
#endregion
#region Events
//Client Events
public event Connected? OnConnected;
public event Disconnected? OnDisconnected;
//Server Events
public event Started? OnStarted;
public event Stopped? OnStopped;
public event PeerConnected? OnPeerConnected;
public event PeerDisconnected? OnPeerDisconnected;
//Packet Events
public event PacketData<Login>? OnLoginReceived;
public event PacketData<Logout>? OnLogoutReceived;
public event PacketData<Accept>? OnAcceptReceived;
public event PacketData<Deny>? OnDenyReceived;
public event PacketData<Ack>? OnAckReceived;
public event PacketData<Ping>? OnPingReceived;
public event PacketData<PingInfo>? OnPingInfoReceived;
public event PacketData<Binded>? OnBindedReceived;
public event PacketData<Unbinded>? OnUnbindedReceived;
public event PacketData<ParticipantJoined>? OnParticipantJoinedReceived;
public event PacketData<ParticipantLeft>? OnParticipantLeftReceived;
public event PacketData<Mute>? OnMuteReceived;
public event PacketData<Unmute>? OnUnmuteReceived;
public event PacketData<Deafen>? OnDeafenReceived;
public event PacketData<Undeafen>? OnUndeafenReceived;
public event PacketData<JoinChannel>? OnJoinChannelReceived;
public event PacketData<LeaveChannel>? OnLeaveChannelReceived;
public event PacketData<AddChannel>? OnAddChannelReceived;
public event PacketData<RemoveChannel>? OnRemoveChannelReceived;
public event PacketData<UpdatePosition>? OnUpdatePositionReceived;
public event PacketData<FullUpdatePosition>? OnFullUpdatePositionReceived;
public event PacketData<UpdateEnvironmentId>? OnUpdateEnvironmentIdReceived;
public event PacketData<ClientAudio>? OnClientAudioReceived;
public event PacketData<ServerAudio>? OnServerAudioReceived;
//Error and Debug Events
public event OutboundPacket? OnOutboundPacket;
public event InboundPacket? OnInboundPacket;
public event ExceptionError? OnExceptionError;
public event Failed? OnFailed;
#endregion
#region Methods
public async Task ConnectAsync(string IP, int port, short preferredKey, PositioningTypes positioningType, string version)
{
ObjectDisposedException.ThrowIf(IsDisposed, nameof(VoiceCraft));
if (State == VoiceCraftSocketState.Started || State == VoiceCraftSocketState.Starting) throw new Exception("Cannot start connection as socket is in a hosting state!");
if (State != VoiceCraftSocketState.Stopped) throw new Exception("You must disconnect before reconnecting!");
CTS.Dispose(); //Prevent memory leak for startup.
//Socket.IOControl((IOControlCode)SIO_UDP_CONNRESET, [0, 0, 0, 0], null); //I fucking hate this Windows Only
//Reset/Setup
State = VoiceCraftSocketState.Connecting;
CTS = new CancellationTokenSource();
ClientNetPeer = new NetPeer(RemoteEndpoint, long.MinValue);
ClientNetPeer.OnPacketReceived += HandlePacketReceived;
Socket.Bind(new IPEndPoint(IPAddress.Any, 0));
Sender = Task.Run(ClientSender);
//Register the Events
OnAcceptReceived += OnAccept;
OnDenyReceived += OnDeny;
OnLogoutReceived += OnLogout;
OnAckReceived += OnAck;
try
{
if (IPAddress.TryParse(IP, out var ip))
{
RemoteEndpoint = new IPEndPoint(ip, port);
}
else if (IP == "localhost")
{
RemoteEndpoint = new IPEndPoint(IPAddress.Loopback, port);
}
else
{
var addresses = await Dns.GetHostAddressesAsync(IP, CTS.Token);
if (addresses.Length == 0) throw new ArgumentException("Unable to retrieve address from the specified host name.", nameof(IP));
RemoteEndpoint = new IPEndPoint(addresses[0], port);
}
ActivityChecker = Task.Run(ActivityCheck);
Send(new Login() { Key = preferredKey, PositioningType = positioningType, Version = version });
await ClientReceiveAsync();
}
catch (Exception ex)
{
OnFailed?.Invoke(ex);
await DisconnectAsync(ex.Message, false);
}
}
public async Task DisconnectAsync(string? reason = null, bool notifyServer = true)
{
ObjectDisposedException.ThrowIf(IsDisposed && State == VoiceCraftSocketState.Stopped, nameof(VoiceCraft));
if (State == VoiceCraftSocketState.Starting || State == VoiceCraftSocketState.Started) throw new InvalidOperationException("Cannot stop hosting as the socket is in a connection state.");
if (State == VoiceCraftSocketState.Stopped || State == VoiceCraftSocketState.Disconnecting) return;
//We don't need to wait until we are connected because the Cancellation Token already takes care of cancelling other thread related requests.
if (notifyServer && State == VoiceCraftSocketState.Connected) //Only send if we are connected.
await SocketSendAsync(new Logout() { Id = ClientNetPeer?.Id ?? long.MinValue });
State = VoiceCraftSocketState.Disconnecting;
//Deregister the Events
OnAcceptReceived -= OnAccept;
OnDenyReceived -= OnDeny;
OnLogoutReceived -= OnLogout;
OnAckReceived -= OnAck;
if(ClientNetPeer != null)
ClientNetPeer.OnPacketReceived -= HandlePacketReceived;
CTS.Cancel();
CTS.Dispose();
Socket.Close();
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
ClientNetPeer = null;
ActivityChecker = null;
Sender = null;
State = VoiceCraftSocketState.Stopped;
OnDisconnected?.Invoke(reason);
}
public async Task HostAsync(int Port)
{
ObjectDisposedException.ThrowIf(IsDisposed, nameof(VoiceCraft));
if (State == VoiceCraftSocketState.Connected || State == VoiceCraftSocketState.Connecting) throw new Exception("Cannot start hosting as socket is in a connection state!");
if (State != VoiceCraftSocketState.Stopped) throw new Exception("You must stop hosting before starting a host!");
CTS.Dispose(); //Prevent memory leak for startup.
//Socket.IOControl((IOControlCode)SIO_UDP_CONNRESET, [0, 0, 0, 0], null); //I fucking hate this Windows Only
State = VoiceCraftSocketState.Starting;
CTS = new CancellationTokenSource();
OnLoginReceived += OnClientLogin;
OnLogoutReceived += OnClientLogout;
OnPingReceived += OnPing;
OnAckReceived += OnAck;
try
{
RemoteEndpoint = new IPEndPoint(IPAddress.Any, Port);
Socket.Bind(RemoteEndpoint);
ActivityChecker = Task.Run(ServerCheck);
Sender = Task.Run(ServerSender);
State = VoiceCraftSocketState.Started;
OnStarted?.Invoke();
await ReceiveAsync();
}
catch (Exception ex)
{
OnFailed?.Invoke(ex);
await StopAsync(ex.Message);
}
}
public async Task StopAsync(string? reason = null)
{
ObjectDisposedException.ThrowIf(IsDisposed && State == VoiceCraftSocketState.Stopped, nameof(VoiceCraft));
if (State == VoiceCraftSocketState.Connecting || State == VoiceCraftSocketState.Connected) throw new InvalidOperationException("Cannot stop hosting as the socket is in a connection state.");
if (State == VoiceCraftSocketState.Stopping || State == VoiceCraftSocketState.Stopped) return;
State = VoiceCraftSocketState.Stopping;
OnLoginReceived -= OnClientLogin;
OnLogoutReceived -= OnClientLogout;
OnAckReceived -= OnAck;
DisconnectPeers("Server Shutdown.");
while(NetPeers.Count > 0)
{
await Task.Delay(1); //Wait until all peers are disconnected.
}
CTS.Cancel();
CTS.Dispose();
Socket.Close();
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
ActivityChecker = null;
Sender = null;
State = VoiceCraftSocketState.Stopped;
OnStopped?.Invoke(reason);
}
public void Send(VoiceCraftPacket packet)
{
ObjectDisposedException.ThrowIf(IsDisposed, nameof(VoiceCraft));
if (State == VoiceCraftSocketState.Connecting || State == VoiceCraftSocketState.Connected)
{
ClientNetPeer?.AddToSendBuffer(packet);
}
else
throw new InvalidOperationException("Socket must be in a connecting or connected state to send packets!");
}
private void DisconnectPeers(string? reason = null)
{
foreach(var peerSocket in NetPeers)
{
peerSocket.Value.Disconnect(reason, true);
}
}
private async Task SocketSendToAsync(VoiceCraftPacket packet, EndPoint ep)
{
var buffer = new List<byte>();
packet.WritePacket(ref buffer);
await Socket.SendToAsync(buffer.ToArray(), ep, CTS.Token);
}
private async Task SocketSendAsync(VoiceCraftPacket packet)
{
var buffer = new List<byte>();
packet.WritePacket(ref buffer);
await Socket.SendToAsync(buffer.ToArray(), RemoteEndpoint, CTS.Token);
}
private NetPeer CreateNetPeer(SocketAddress receivedAddress)
{
// Create an EndPoint from the SocketAddress
var netPeer = new NetPeer(RemoteEndpoint.Create(receivedAddress), long.MinValue, NetPeerState.Requesting);
netPeer.OnPacketReceived += HandlePacketReceived;
var lookupCopy = new SocketAddress(receivedAddress.Family, receivedAddress.Size);
receivedAddress.Buffer.CopyTo(lookupCopy.Buffer);
NetPeers.TryAdd(lookupCopy, netPeer);
return netPeer;
}
private async Task ReceiveAsync()
{
byte[] buffer = GC.AllocateArray<byte>(length: 500, pinned: true);
Memory<byte> bufferMem = buffer.AsMemory();
var receivedAddress = new SocketAddress(Socket.AddressFamily);
while (!CTS.IsCancellationRequested)
{
try
{
var receivedBytes = await Socket.ReceiveFromAsync(bufferMem, SocketFlags.None, receivedAddress, CTS.Token);
var packet = PacketRegistry.GetPacketFromDataStream(bufferMem.ToArray());
NetPeers.TryGetValue(receivedAddress, out var netPeer);
if (netPeer?.State == NetPeerState.Connected)
{
if (LogInbound && (InboundFilter.Count == 0 || InboundFilter.Contains((VoiceCraftPacketTypes)packet.PacketId)))
OnInboundPacket?.Invoke(packet, netPeer);
netPeer.AddToReceiveBuffer(packet); //Only add packets if the client was accepted.
}
else if(packet.PacketId == (byte)VoiceCraftPacketTypes.Login || packet.PacketId == (byte)VoiceCraftPacketTypes.PingInfo) //Null or not connected, we only accept the login or pinginfo packets to try an prevent unauthorized overload spam.
{
var peer = netPeer ?? CreateNetPeer(receivedAddress);
if (LogInbound && (InboundFilter.Count == 0 || InboundFilter.Contains((VoiceCraftPacketTypes)packet.PacketId)))
OnInboundPacket?.Invoke(packet, peer);
peer.AddToReceiveBuffer(packet);
}
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.ConnectionReset || ex.SocketErrorCode == SocketError.ConnectionAborted || ex.SocketErrorCode == SocketError.TimedOut) continue;
await StopAsync(ex.Message);
return;
}
catch(OperationCanceledException)
{
return;
}
catch(Exception ex)
{
if (LogExceptions)
OnExceptionError?.Invoke(ex);
}
}
}
private async Task ClientReceiveAsync()
{
byte[] buffer = GC.AllocateArray<byte>(length: 500, pinned: true);
Memory<byte> bufferMem = buffer.AsMemory();
while (!CTS.IsCancellationRequested)
{
try
{
var receivedBytes = await Socket.ReceiveFromAsync(bufferMem, SocketFlags.None, RemoteEndpoint, CTS.Token);
var packet = PacketRegistry.GetPacketFromDataStream(bufferMem.ToArray());
ClientNetPeer?.AddToReceiveBuffer(packet); //We don't care about wether the client is connected or not, We'll just accept the packet into the buffer.
if (ClientNetPeer != null && LogInbound && (InboundFilter.Count == 0 || InboundFilter.Contains((VoiceCraftPacketTypes)packet.PacketId)))
OnInboundPacket?.Invoke(packet, ClientNetPeer);
}
catch (SocketException ex)
{
await DisconnectAsync(ex.Message, false); //Socket is basically closed at this point, We can't send a message.
return;
}
catch (OperationCanceledException)
{
return;
}
catch (Exception ex)
{
if (LogExceptions)
OnExceptionError?.Invoke(ex);
}
}
}
private async Task ActivityCheck()
{
var time = Environment.TickCount64;
while (!CTS.IsCancellationRequested)
{
var dist = Environment.TickCount64 - ClientNetPeer?.LastActive;
if (dist > Timeout)
{
await DisconnectAsync($"Connection timed out!\nTime since last active {Environment.TickCount - ClientNetPeer?.LastActive}ms.", false);
break;
}
ClientNetPeer?.ResendPackets();
if (Environment.TickCount64 - time >= 1000) //1 second ping interval.
{
Send(new Ping());
time = Environment.TickCount64;
}
await Task.Delay(1).ConfigureAwait(false);
}
}
private async Task ServerCheck()
{
while (!CTS.IsCancellationRequested)
{
for (int i = NetPeers.Count - 1; i >= 0; i--)
{
var peer = NetPeers.ElementAt(i);
var diff = Environment.TickCount64 - peer.Value.LastActive;
if ((diff > Timeout || diff < 0) && peer.Value.State != NetPeerState.Disconnected) //Negative values are pretty much invalid.
{
peer.Value.Disconnect($"Timeout - Last Active: {Environment.TickCount64 - peer.Value.LastActive}ms", true);
}
}
foreach (var peer in NetPeers)
{
peer.Value.ResendPackets();
}
await Task.Delay(1).ConfigureAwait(false);
}
}
private async Task ServerSender()
{
while (!CTS.IsCancellationRequested)
{
foreach (var peer in NetPeers)
{
var maxSendTime = Environment.TickCount64 + MaxSendTime;
while (peer.Value.SendQueue.TryDequeue(out VoiceCraftPacket? packet) && Environment.TickCount64 < maxSendTime && !CTS.IsCancellationRequested)
{
if (packet.Retries > NetPeer.MaxSendRetries)
{
peer.Value.Disconnect("Unstable Connection.", true);
continue;
}
await SocketSendToAsync(packet, peer.Value.RemoteEndPoint);
if (LogOutbound && (OutboundFilter.Count == 0 || OutboundFilter.Contains((VoiceCraftPacketTypes)packet.PacketId)))
OnOutboundPacket?.Invoke(packet, peer.Value);
}
if (peer.Value.State == NetPeerState.Disconnected)
{
NetPeers.TryRemove(peer);
OnPeerDisconnected?.Invoke(peer.Value, peer.Value.DisconnectReason);
}
}
await Task.Delay(1); //1ms to not destroy the CPU.
}
}
private async Task ClientSender()
{
while (!CTS.IsCancellationRequested && ClientNetPeer != null)
{
while (ClientNetPeer.SendQueue.TryDequeue(out VoiceCraftPacket? packet) && !CTS.IsCancellationRequested)
{
if (packet.Retries > NetPeer.MaxSendRetries)
{
await DisconnectAsync("Unstable Connection.");
continue;
}
await SocketSendAsync(packet);
if (LogOutbound && (OutboundFilter.Count == 0 || OutboundFilter.Contains((VoiceCraftPacketTypes)packet.PacketId)))
OnOutboundPacket?.Invoke(packet, ClientNetPeer);
}
await Task.Delay(1); //1ms to not destroy the CPU.
}
}
private long GetAvailableId()
{
var Id = NetPeer.GenerateId();
while(IdExists(Id))
{
Id = NetPeer.GenerateId();
}
return Id;
}
private bool IdExists(long id)
{
foreach(var peer in NetPeers)
{
if(peer.Value.Id == id) return true;
}
return false;
}
private void HandlePacketReceived(NetPeer peer, VoiceCraftPacket packet)
{
switch ((VoiceCraftPacketTypes)packet.PacketId)
{
case VoiceCraftPacketTypes.Login: OnLoginReceived?.Invoke((Login)packet, peer); break;
case VoiceCraftPacketTypes.Logout: OnLogoutReceived?.Invoke((Logout)packet, peer); break;
case VoiceCraftPacketTypes.Accept: OnAcceptReceived?.Invoke((Accept)packet, peer); break;
case VoiceCraftPacketTypes.Deny: OnDenyReceived?.Invoke((Deny)packet, peer); break;
case VoiceCraftPacketTypes.Ack: OnAckReceived?.Invoke((Ack)packet, peer); break;
case VoiceCraftPacketTypes.Ping: OnPingReceived?.Invoke((Ping)packet, peer); break;
case VoiceCraftPacketTypes.PingInfo: OnPingInfoReceived?.Invoke((PingInfo)packet, peer); break;
case VoiceCraftPacketTypes.Binded: OnBindedReceived?.Invoke((Binded)packet, peer); break;
case VoiceCraftPacketTypes.Unbinded: OnUnbindedReceived?.Invoke((Unbinded)packet, peer); break;
case VoiceCraftPacketTypes.ParticipantJoined: OnParticipantJoinedReceived?.Invoke((ParticipantJoined)packet, peer); break;
case VoiceCraftPacketTypes.ParticipantLeft: OnParticipantLeftReceived?.Invoke((ParticipantLeft)packet, peer); break;
case VoiceCraftPacketTypes.Mute: OnMuteReceived?.Invoke((Mute)packet, peer); break;
case VoiceCraftPacketTypes.Unmute: OnUnmuteReceived?.Invoke((Unmute)packet, peer); break;
case VoiceCraftPacketTypes.Deafen: OnDeafenReceived?.Invoke((Deafen)packet, peer); break;
case VoiceCraftPacketTypes.Undeafen: OnUndeafenReceived?.Invoke((Undeafen)packet, peer); break;
case VoiceCraftPacketTypes.JoinChannel: OnJoinChannelReceived?.Invoke((JoinChannel)packet, peer); break;
case VoiceCraftPacketTypes.LeaveChannel: OnLeaveChannelReceived?.Invoke((LeaveChannel)packet, peer); break;
case VoiceCraftPacketTypes.AddChannel: OnAddChannelReceived?.Invoke((AddChannel)packet, peer); break;
case VoiceCraftPacketTypes.RemoveChannel: OnRemoveChannelReceived?.Invoke((RemoveChannel)packet, peer); break;
case VoiceCraftPacketTypes.UpdatePosition: OnUpdatePositionReceived?.Invoke((UpdatePosition)packet, peer); break;
case VoiceCraftPacketTypes.FullUpdatePosition: OnFullUpdatePositionReceived?.Invoke((FullUpdatePosition)packet, peer); break;
case VoiceCraftPacketTypes.UpdateEnvironmentId: OnUpdateEnvironmentIdReceived?.Invoke((UpdateEnvironmentId)packet, peer); break;
case VoiceCraftPacketTypes.ClientAudio: OnClientAudioReceived?.Invoke((ClientAudio)packet, peer); break;
case VoiceCraftPacketTypes.ServerAudio: OnServerAudioReceived?.Invoke((ServerAudio)packet, peer); break;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (State == VoiceCraftSocketState.Started || State == VoiceCraftSocketState.Starting)
StopAsync().Wait();
if (State == VoiceCraftSocketState.Connected || State == VoiceCraftSocketState.Connecting)
DisconnectAsync().Wait();
Socket.Dispose();
}
}
#endregion
#region Client Event Methods
private void OnAccept(Accept data, NetPeer peer)
{
if(ClientNetPeer != null)
{
ClientNetPeer.Id = data.Id;
IsConnected = true;
}
State = VoiceCraftSocketState.Connected;
OnConnected?.Invoke(data.Key);
}
private void OnDeny(Deny data, NetPeer peer)
{
if(!IsConnected)
DisconnectAsync(data.Reason, false).Wait();
}
private void OnLogout(Logout data, NetPeer peer)
{
DisconnectAsync(data.Reason, false).Wait();
}
#endregion
#region Server Event Methods
private void OnClientLogin(Login data, NetPeer peer)
{
if (peer.State == NetPeerState.Connected)
{
peer.AddToSendBuffer(new Accept());
return; //Already Connected
}
var Id = GetAvailableId();
peer.Id = Id;
OnPeerConnected?.Invoke(peer, data); //Leave wether the client should be accepted or denied by the application.
}
private void OnClientLogout(Logout data, NetPeer peer)
{
peer.Disconnect(null, false);
}
private void OnPing(Ping data, NetPeer peer)
{
peer.AddToSendBuffer(new Ping());
}
#endregion
#region Global Event Methods
private void OnAck(Ack data, NetPeer peer)
{
peer.AcknowledgePacket(data.PacketSequence);
}
#endregion
}
public enum VoiceCraftSocketState
{
Stopped,
//Client
Connecting,
Connected,
Disconnecting,
//Hoster
Starting,
Started,
Stopping
}
}