using System;
using System.Net;
using System.Threading.Tasks;
using Impostor.Api.Net.Messages;
using Serilog;
namespace Impostor.Hazel
{
///
/// Base class for all connections.
///
///
///
/// Connection is the base class for all connections that Hazel can make. It provides common functionality and a
/// standard interface to allow connections to be swapped easily.
///
///
/// Any class inheriting from Connection should provide the 3 standard guarantees that Hazel provides:
///
/// -
/// Thread Safe
///
/// -
/// Connection Orientated
///
/// -
/// Packet/Message Based
///
///
///
///
///
public abstract class Connection : IDisposable
{
private static readonly ILogger Logger = Log.ForContext();
///
/// Called when a message has been received.
///
///
///
/// DataReceived is invoked everytime a message is received from the end point of this connection, the message
/// that was received can be found in the alongside other information from the
/// event.
///
///
///
///
///
///
public Func DataReceived;
public int TestLagMs = -1;
public int TestDropRate = 0;
protected int testDropCount = 0;
///
/// Called when the end point disconnects or an error occurs.
///
///
///
/// Disconnected is invoked when the connection is closed due to an exception occuring or because the remote
/// end point disconnected. If it was invoked due to an exception occuring then the exception is available
/// in the passed with the event.
///
///
///
///
///
///
public Func Disconnected;
///
/// The remote end point of this Connection.
///
///
/// This is the end point that this connection is connected to (i.e. the other device). This returns an abstract
/// which can then be cast to an appropriate end point depending on the
/// connection type.
///
public IPEndPoint EndPoint { get; protected set; }
public IPMode IPMode { get; protected set; }
///
/// The traffic statistics about this Connection.
///
///
/// Contains statistics about the number of messages and bytes sent and received by this connection.
///
public ConnectionStatistics Statistics { get; protected set; }
///
/// The state of this connection.
///
///
/// All implementers should be aware that when this is set to ConnectionState.Connected it will
/// release all threads that are blocked on .
///
public ConnectionState State
{
get
{
return this._state;
}
protected set
{
this._state = value;
this.SetState(value);
}
}
protected ConnectionState _state;
protected virtual void SetState(ConnectionState state) { }
///
/// Constructor that initializes the ConnecitonStatistics object.
///
///
/// This constructor initialises with empty statistics and sets to
/// .
///
protected Connection()
{
this.Statistics = new ConnectionStatistics();
this.State = ConnectionState.NotConnected;
}
///
/// Sends a number of bytes to the end point of the connection using the specified .
///
/// The message to send.
///
///
///
/// The messageType parameter is only a request to use those options and the actual method used to send the
/// data is up to the implementation. There are circumstances where this parameter may be ignored but in
/// general any implementer should aim to always follow the user's request.
///
///
public abstract ValueTask SendAsync(IMessageWriter msg);
///
/// Sends a number of bytes to the end point of the connection using the specified .
///
/// The bytes of the message to send.
/// The option specifying how the message should be sent.
///
///
///
/// The messageType parameter is only a request to use those options and the actual method used to send the
/// data is up to the implementation. There are circumstances where this parameter may be ignored but in
/// general any implementer should aim to always follow the user's request.
///
///
public abstract ValueTask SendBytes(byte[] bytes, MessageType messageType = MessageType.Unreliable);
///
/// Connects the connection to a server and begins listening.
/// This method does not block.
///
/// The bytes of data to send in the handshake.
public abstract ValueTask ConnectAsync(byte[] bytes = null);
///
/// Invokes the DataReceived event.
///
/// The bytes received.
/// The the message was received with.
///
/// Invokes the event on this connection to alert subscribers a new message has been
/// received. The bytes and the send option that the message was sent with should be passed in to give to the
/// subscribers.
///
protected async ValueTask InvokeDataReceived(IMessageReader msg, MessageType messageType)
{
// Make a copy to avoid race condition between null check and invocation
var handler = DataReceived;
if (handler != null)
{
try
{
await handler(new DataReceivedEventArgs(this, msg, messageType));
}
catch (Exception e)
{
Logger.Error(e, "Invoking data received failed");
await Disconnect("Invoking data received failed");
}
}
}
///
/// Invokes the Disconnected event.
///
/// The exception, if any, that occurred to cause this.
/// Extra disconnect data
///
/// Invokes the event to alert subscribres this connection has been disconnected either
/// by the end point or because an error occurred. If an error occurred the error should be passed in in order to
/// pass to the subscribers, otherwise null can be passed in.
///
protected async ValueTask InvokeDisconnected(string e, IMessageReader reader)
{
// Make a copy to avoid race condition between null check and invocation
var handler = Disconnected;
if (handler != null)
{
try
{
await handler(new DisconnectedEventArgs(e, reader));
}
catch (Exception ex)
{
Logger.Error(ex, "Error in InvokeDisconnected");
}
}
}
///
/// For times when you want to force the disconnect handler to fire as well as close it.
/// If you only want to close it, just use Dispose.
///
public abstract ValueTask Disconnect(string reason, MessageWriter writer = null);
///
/// Disposes of this NetworkConnection.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Disposes of this NetworkConnection.
///
/// Are we currently disposing?
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.DataReceived = null;
this.Disconnected = null;
}
}
}
}