using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MinecraftClient.Crypto;
namespace MinecraftClient.Protocol.Handlers
{
///
/// Wrapper for handling unencrypted & encrypted socket
///
public class SocketWrapper
{
readonly TcpClient c;
AesCfb8Stream? s;
bool encrypted = false;
///
/// Initialize a new SocketWrapper
///
/// TcpClient connected to the server
public SocketWrapper(TcpClient client)
{
c = client;
}
///
/// Check if the socket is still connected
///
/// TRUE if still connected
/// Silently dropped connection can only be detected by attempting to read/write data
public bool IsConnected()
{
return c.Client is not null && c.Connected;
}
///
/// Check if the socket has data available to read
///
/// TRUE if data is available to read
public bool HasDataAvailable()
{
return c.Client.Available > 0;
}
///
/// Switch network reading/writing to an encrypted stream
///
/// AES secret key
public void SwitchToEncrypted(byte[] secretKey)
{
if (encrypted)
throw new InvalidOperationException("Stream is already encrypted!?");
s = new AesCfb8Stream(c.GetStream(), secretKey);
encrypted = true;
}
///
/// Network reading method. Read bytes from the socket or encrypted socket.
///
private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
{
int read = 0;
while (read < offset)
{
if (encrypted)
read += s!.Read(buffer, start + read, offset - read);
else
read += c.Client.Receive(buffer, start + read, offset - read, f);
}
}
private async Task ReceiveAsync(Memory buffer, CancellationToken cancellationToken)
{
int read = 0;
while (read < buffer.Length)
{
int currentRead = encrypted
? await s!.ReadAsync(buffer[read..], cancellationToken)
: await c.GetStream().ReadAsync(buffer[read..], cancellationToken);
if (currentRead == 0)
throw new IOException("Connection closed.");
read += currentRead;
}
}
///
/// Read some data from the server.
///
/// Amount of bytes to read
/// The data read from the network as an array
public byte[] ReadDataRAW(int length)
{
if (length > 0)
{
byte[] cache = new byte[length];
Receive(cache, 0, length, SocketFlags.None);
return cache;
}
return Array.Empty();
}
public async Task ReadDataRAWAsync(int length, CancellationToken cancellationToken)
{
if (length > 0)
{
byte[] cache = new byte[length];
await ReceiveAsync(cache, cancellationToken);
return cache;
}
return Array.Empty();
}
///
/// Send raw data to the server.
///
/// data to send
public void SendDataRAW(byte[] buffer)
{
if (!IsConnected())
throw new SocketException((int)SocketError.NotConnected);
if (encrypted)
s!.Write(buffer, 0, buffer.Length);
else
c.Client.Send(buffer);
}
public async Task SendDataRAWAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken)
{
if (!IsConnected())
throw new SocketException((int)SocketError.NotConnected);
if (encrypted)
await s!.WriteAsync(buffer, cancellationToken);
else
await c.GetStream().WriteAsync(buffer, cancellationToken);
}
///
/// Disconnect from the server
///
public void Disconnect()
{
try
{
c.Close();
}
catch (SocketException) { }
catch (System.IO.IOException) { }
catch (NullReferenceException) { }
catch (ObjectDisposedException) { }
}
}
}