Started on UE4 server, semi working handshake
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
internal class CoreNet
|
||||
{
|
||||
public const int MaxPacketSize = 1024;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public enum EConnectionState
|
||||
{
|
||||
USOCK_Invalid = 0, // Connection is invalid, possibly uninitialized.
|
||||
USOCK_Closed = 1, // Connection permanently closed.
|
||||
USOCK_Pending = 2, // Connection is awaiting connection.
|
||||
USOCK_Open = 3, // Connection is open.
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public enum EIncomingResult
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public class FOutPacketTraits
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using Serilog;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public record FReceivedPacket(IPEndPoint Address, byte[] Buffer, DateTimeOffset Timestamp);
|
||||
|
||||
public class FReceiveThreadRunnable : IAsyncDisposable
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext<FReceiveThreadRunnable>();
|
||||
|
||||
private readonly UIpNetDriver _driver;
|
||||
private readonly CancellationTokenSource _cancellation;
|
||||
private readonly ConcurrentQueue<FReceivedPacket> _receiveQueue;
|
||||
|
||||
private Task? _receiveTask;
|
||||
|
||||
public FReceiveThreadRunnable(UIpNetDriver driver)
|
||||
{
|
||||
_driver = driver;
|
||||
_cancellation = new CancellationTokenSource();
|
||||
_receiveQueue = new ConcurrentQueue<FReceivedPacket>();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_receiveTask = ReceiveAsync();
|
||||
}
|
||||
|
||||
public bool TryReceive([MaybeNullWhen(false)] out FReceivedPacketView result)
|
||||
{
|
||||
if (_receiveQueue.TryDequeue(out var packet))
|
||||
{
|
||||
result = new FReceivedPacketView
|
||||
{
|
||||
DataView = new FPacketDataView(packet.Buffer, packet.Buffer.Length),
|
||||
Address = packet.Address,
|
||||
Traits = new FInPacketTraits()
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task ReceiveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_cancellation.IsCancellationRequested)
|
||||
{
|
||||
var result = await _driver.Socket.ReceiveAsync(_cancellation.Token);
|
||||
|
||||
if (result.Buffer.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.Buffer.Length > CoreNet.MaxPacketSize)
|
||||
{
|
||||
Logger.Warning("Received packet exceeding MaxPacketSize ({Size} > {Max}) from {Ip}",
|
||||
result.Buffer.Length, CoreNet.MaxPacketSize, result.RemoteEndPoint);
|
||||
continue;
|
||||
}
|
||||
|
||||
_receiveQueue.Enqueue(new FReceivedPacket(result.RemoteEndPoint, result.Buffer, DateTimeOffset.UtcNow));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is OperationCanceledException && !_cancellation.IsCancellationRequested)
|
||||
{
|
||||
Logger.Error(e, "Exception caught");
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Debug("Stopped FReceiveThreadRunnable");
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
_cancellation.Cancel();
|
||||
|
||||
if (_receiveTask != null)
|
||||
{
|
||||
await _receiveTask;
|
||||
}
|
||||
|
||||
_cancellation.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Net;
|
||||
using Prospect.Unreal.Serialization;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public abstract class HandlerComponent
|
||||
{
|
||||
private bool _bRequiresHandshake;
|
||||
private bool _bRequiresReliability;
|
||||
private bool _bActive;
|
||||
private bool _bInitialized;
|
||||
private string _name;
|
||||
|
||||
protected HandlerComponent(PacketHandler handler, string name)
|
||||
{
|
||||
_name = name;
|
||||
_bRequiresHandshake = false;
|
||||
_bRequiresReliability = false;
|
||||
_bActive = false;
|
||||
_bInitialized = false;
|
||||
|
||||
Handler = handler;
|
||||
State = HandlerComponentState.UnInitialized;
|
||||
}
|
||||
|
||||
protected PacketHandler Handler { get; }
|
||||
protected HandlerComponentState State { get; private set; }
|
||||
|
||||
public virtual bool IsActive()
|
||||
{
|
||||
return _bActive;
|
||||
}
|
||||
|
||||
public virtual bool IsValid()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsInitialized()
|
||||
{
|
||||
return _bInitialized;
|
||||
}
|
||||
|
||||
public bool RequiresHandshake()
|
||||
{
|
||||
return _bRequiresHandshake;
|
||||
}
|
||||
|
||||
public bool RequiresReliability()
|
||||
{
|
||||
return _bRequiresReliability;
|
||||
}
|
||||
|
||||
public virtual void Incoming(FBitReader packet)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Outgoing(FBitWriter packet, FOutPacketTraits traits)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void IncomingConnectionless(FIncomingPacketRef packetRef)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OutgoingConnectionless(IPEndPoint address, FBitWriter packet, FOutPacketTraits traits)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual bool CanReadUnaligned()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void Initialize();
|
||||
|
||||
public virtual void NotifiyHandshakeBegin()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Tick(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void SetActive(bool active)
|
||||
{
|
||||
_bActive = active;
|
||||
}
|
||||
|
||||
public virtual int GetReservedPacketBits()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual void CountBytes(FArchive ar)
|
||||
{
|
||||
}
|
||||
|
||||
protected void SetState(HandlerComponentState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
|
||||
protected void Initialized()
|
||||
{
|
||||
_bInitialized = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public enum HandlerComponentState
|
||||
{
|
||||
UnInitialized, // HandlerComponent not yet initialized
|
||||
InitializedOnLocal, // Initialized on local instance
|
||||
InitializeOnRemote, // Initialized on remote instance, not on local instance
|
||||
Initialized // Initialized on both local and remote instances
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public enum HandlerMode
|
||||
{
|
||||
Client, // Clientside PacketHandler
|
||||
Server // Serverside PacketHandler
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public enum HandlerState
|
||||
{
|
||||
Uninitialized, // PacketHandler is uninitialized
|
||||
InitializingComponents, // PacketHandler is initializing HandlerComponents
|
||||
Initialized // PacketHandler and all HandlerComponents (if any) are initialized
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
using System.Net;
|
||||
using Prospect.Unreal.Serialization;
|
||||
using Serilog;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public record FIncomingPacketRef(FBitReader Packet, IPEndPoint Address, FInPacketTraits Traits);
|
||||
|
||||
public record ProcessedPacket(byte[] Data, int CountBits, bool Error = false);
|
||||
|
||||
public class PacketHandler
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext<PacketHandler>();
|
||||
|
||||
private readonly List<HandlerComponent> _handlerComponents;
|
||||
|
||||
private bool _bConnectionlessHandler;
|
||||
private bool _bRawSend;
|
||||
private HandlerState _state;
|
||||
private ReliabilityHandlerComponent? _reliabilityComponent;
|
||||
|
||||
private FBitWriter _outgoingPacket;
|
||||
private FBitReader _incomingPacket;
|
||||
|
||||
public PacketHandler()
|
||||
{
|
||||
_bConnectionlessHandler = false;
|
||||
_state = HandlerState.Uninitialized;
|
||||
_handlerComponents = new List<HandlerComponent>();
|
||||
_reliabilityComponent = null;
|
||||
_outgoingPacket = new FBitWriter(0);
|
||||
_incomingPacket = new FBitReader(Array.Empty<byte>());
|
||||
|
||||
Mode = HandlerMode.Server;
|
||||
}
|
||||
|
||||
public HandlerMode Mode { get; }
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
foreach (var component in _handlerComponents)
|
||||
{
|
||||
component.Tick(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(bool bConnectionlessOnly)
|
||||
{
|
||||
_bConnectionlessHandler = bConnectionlessOnly;
|
||||
|
||||
if (!_bConnectionlessHandler)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// TODO: FEncryptionComponent.
|
||||
|
||||
// TODO: ReliabilityHandlerComponent.
|
||||
}
|
||||
|
||||
public void InitializeComponents()
|
||||
{
|
||||
if (_state == HandlerState.Uninitialized)
|
||||
{
|
||||
if (_handlerComponents.Count > 0)
|
||||
{
|
||||
SetState(HandlerState.InitializingComponents);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandlerInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var component in _handlerComponents)
|
||||
{
|
||||
if (component.IsValid() && !component.IsInitialized())
|
||||
{
|
||||
component.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IncomingConnectionless(FReceivedPacketView packetView)
|
||||
{
|
||||
packetView.Traits.ConnectionlessPacket = true;
|
||||
|
||||
return Incoming_Internal(packetView);
|
||||
}
|
||||
|
||||
public ProcessedPacket OutgoingConnectionless(IPEndPoint address, byte[] packet, int countBits, FOutPacketTraits traits)
|
||||
{
|
||||
return Outgoing_Internal(packet, countBits, traits, true, address);
|
||||
}
|
||||
|
||||
public HandlerComponent AddHandler<T>() where T : HandlerComponent
|
||||
{
|
||||
var result = (HandlerComponent) Activator.CreateInstance(typeof(T), this)!;
|
||||
|
||||
_handlerComponents.Add(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool Incoming_Internal(FReceivedPacketView packetView)
|
||||
{
|
||||
var returnVal = true;
|
||||
var dataView = packetView.DataView;
|
||||
var countBits = dataView.NumBits();
|
||||
|
||||
if (_handlerComponents.Count > 0)
|
||||
{
|
||||
var data = dataView.GetData();
|
||||
var lastByte = data[dataView.NumBytes() - 1];
|
||||
if (lastByte != 0)
|
||||
{
|
||||
countBits--;
|
||||
|
||||
while ((lastByte & 0x80) == 0)
|
||||
{
|
||||
lastByte *= 2;
|
||||
countBits--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
returnVal = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (returnVal)
|
||||
{
|
||||
var processPacketReader = new FBitReader(dataView.GetData(), countBits);
|
||||
var packetRef = new FIncomingPacketRef(processPacketReader, packetView.Address, packetView.Traits);
|
||||
|
||||
if (_state == HandlerState.Uninitialized)
|
||||
{
|
||||
UpdateInitialState();
|
||||
}
|
||||
|
||||
foreach (var component in _handlerComponents)
|
||||
{
|
||||
if (processPacketReader.GetPosBits() != 0 && !component.CanReadUnaligned())
|
||||
{
|
||||
RealignPacket(processPacketReader);
|
||||
}
|
||||
|
||||
if (packetView.Traits.ConnectionlessPacket)
|
||||
{
|
||||
component.IncomingConnectionless(packetRef);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.Incoming(processPacketReader);
|
||||
}
|
||||
}
|
||||
|
||||
if (!processPacketReader.IsError())
|
||||
{
|
||||
ReplaceIncomingPacket(processPacketReader);
|
||||
|
||||
packetView.DataView = new FPacketDataView(_incomingPacket.GetBuffer(), _incomingPacket.GetBitsLeft());
|
||||
}
|
||||
else
|
||||
{
|
||||
returnVal = false;
|
||||
}
|
||||
}
|
||||
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
private ProcessedPacket Outgoing_Internal(byte[] packet, int countBits, FOutPacketTraits traits, bool bConnectionLess, IPEndPoint address)
|
||||
{
|
||||
if (!_bRawSend)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ProcessedPacket(packet, countBits);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetState(HandlerState state)
|
||||
{
|
||||
if (state == _state)
|
||||
{
|
||||
Logger.Fatal("Set PacketHandler state to the state it is currently in ({State})", state);
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInitialState()
|
||||
{
|
||||
if (_state == HandlerState.Uninitialized)
|
||||
{
|
||||
if (_handlerComponents.Count > 0)
|
||||
{
|
||||
InitializeComponents();
|
||||
}
|
||||
else
|
||||
{
|
||||
HandlerInitialized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlerInitialized()
|
||||
{
|
||||
if (_reliabilityComponent != null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
SetState(HandlerState.Initialized);
|
||||
}
|
||||
|
||||
private void ReplaceIncomingPacket(FBitReader replacementPacket)
|
||||
{
|
||||
if (replacementPacket.GetPosBits() == 0 || replacementPacket.GetBitsLeft() == 0)
|
||||
{
|
||||
_incomingPacket = replacementPacket;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tempPacketData = new byte[replacementPacket.GetBytesLeft()];
|
||||
var newPacketSizeBits = replacementPacket.GetBitsLeft();
|
||||
|
||||
replacementPacket.SerializeBits(tempPacketData, newPacketSizeBits);
|
||||
|
||||
_incomingPacket = new FBitReader(tempPacketData, newPacketSizeBits);
|
||||
}
|
||||
}
|
||||
|
||||
private void RealignPacket(FBitReader packet)
|
||||
{
|
||||
Logger.Warning("Realigning packet, which is untested");
|
||||
|
||||
if (packet.GetPosBits() != 0)
|
||||
{
|
||||
var bitsLeft = packet.GetBitsLeft();
|
||||
if (bitsLeft > 0)
|
||||
{
|
||||
var tempPacketData = new byte[packet.GetBytesLeft()];
|
||||
|
||||
packet.SerializeBits(tempPacketData, bitsLeft);
|
||||
packet.SetData(tempPacketData, bitsLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRawSend(bool enabled)
|
||||
{
|
||||
_bRawSend = enabled;
|
||||
}
|
||||
|
||||
public bool GetRawSend()
|
||||
{
|
||||
return _bRawSend;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Net;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public class FInPacketTraits
|
||||
{
|
||||
public bool ConnectionlessPacket { get; set; }
|
||||
public bool FromRecentlyDisconnected { get; set; }
|
||||
}
|
||||
|
||||
public class FPacketDataView
|
||||
{
|
||||
private readonly byte[] _data;
|
||||
private readonly int _countBits;
|
||||
|
||||
public FPacketDataView(byte[] data, int length)
|
||||
{
|
||||
_data = data;
|
||||
_countBits = length * 8;
|
||||
}
|
||||
|
||||
public byte[] GetData()
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
public int NumBits()
|
||||
{
|
||||
return _countBits;
|
||||
}
|
||||
|
||||
public int NumBytes()
|
||||
{
|
||||
return _data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public class FReceivedPacketView
|
||||
{
|
||||
public FPacketDataView DataView { get; set; }
|
||||
public IPEndPoint Address { get; set; }
|
||||
public FInPacketTraits Traits { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public class ReliabilityHandlerComponent : HandlerComponent
|
||||
{
|
||||
public ReliabilityHandlerComponent(PacketHandler handler) : base(handler, nameof(ReliabilityHandlerComponent))
|
||||
{
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using Prospect.Unreal.Core;
|
||||
using Prospect.Unreal.Serialization;
|
||||
using Serilog;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public class StatelessConnectHandlerComponent : HandlerComponent
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext<StatelessConnectHandlerComponent>();
|
||||
|
||||
private const int SecretByteSize = 64;
|
||||
private const int SecretCount = 2;
|
||||
private const int CookieByteSize = 20;
|
||||
|
||||
private const int HandshakePacketSizeBits = 227;
|
||||
private const int RestartHandshakePacketSizeBits = 2;
|
||||
private const int RestartResponseSizeBits = 387;
|
||||
|
||||
private const float SecretUpdateTime = 15.0f;
|
||||
private const float SecretUpdateTimeVariance = 5.0f;
|
||||
|
||||
private const float MaxCookieLifetime = ((SecretUpdateTime + SecretUpdateTimeVariance) * SecretCount);
|
||||
private const float MinCookieLifetime = SecretUpdateTime;
|
||||
|
||||
private UNetDriver? _driver;
|
||||
|
||||
/// <summary>
|
||||
/// The serverside-only 'secret' value, used to help with generating cookies.
|
||||
/// </summary>
|
||||
private byte[][] _handshakeSecret;
|
||||
|
||||
/// <summary>
|
||||
/// Which of the two secret values above is active (values are changed frequently, to limit replay attacks)
|
||||
/// </summary>
|
||||
private byte _activeSecret;
|
||||
|
||||
/// <summary>
|
||||
/// The time of the last secret value update
|
||||
/// </summary>
|
||||
private double _lastSecretUpdateTimestamp;
|
||||
|
||||
/// <summary>
|
||||
/// The last address to successfully complete the handshake challenge
|
||||
/// </summary>
|
||||
private IPEndPoint? _lastChallengeSuccessAddress;
|
||||
|
||||
/// <summary>
|
||||
/// The initial server sequence value, from the last successful handshake
|
||||
/// </summary>
|
||||
private int _lastServerSequence;
|
||||
|
||||
/// <summary>
|
||||
/// The initial client sequence value, from the last successful handshake
|
||||
/// </summary>
|
||||
private int _lastClientSequence;
|
||||
|
||||
/// <summary>
|
||||
/// Client: Whether or not we are in the middle of a restarted handshake.
|
||||
/// Server: Whether or not the last handshake was a restarted handshake.
|
||||
/// </summary>
|
||||
private bool _bRestartedHandshake;
|
||||
|
||||
/// <summary>
|
||||
/// The cookie which completed the connection handshake.
|
||||
/// </summary>
|
||||
private byte[] _authorisedCookie;
|
||||
|
||||
/// <summary>
|
||||
/// The magic header which is prepended to all packets
|
||||
/// </summary>
|
||||
private BitArray _magicHeader;
|
||||
|
||||
public StatelessConnectHandlerComponent(PacketHandler handler) : base(handler, nameof(StatelessConnectHandlerComponent))
|
||||
{
|
||||
_handshakeSecret = new byte[2][];
|
||||
_activeSecret = byte.MaxValue;
|
||||
_lastChallengeSuccessAddress = null;
|
||||
_lastServerSequence = 0;
|
||||
_lastClientSequence = 0;
|
||||
_bRestartedHandshake = false;
|
||||
_authorisedCookie = new byte[CookieByteSize];
|
||||
_magicHeader = new BitArray(0);
|
||||
}
|
||||
|
||||
public void GetChallengeSequences(out int serverSequence, out int clientSequence)
|
||||
{
|
||||
serverSequence = _lastServerSequence;
|
||||
clientSequence = _lastClientSequence;
|
||||
}
|
||||
|
||||
public void ResetChallengeData()
|
||||
{
|
||||
_lastChallengeSuccessAddress = null;
|
||||
_bRestartedHandshake = false;
|
||||
_lastServerSequence = 0;
|
||||
_lastClientSequence = 0;
|
||||
|
||||
for (var i = 0; i < _authorisedCookie.Length; i++)
|
||||
{
|
||||
_authorisedCookie[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDriver(UNetDriver driver)
|
||||
{
|
||||
_driver = driver;
|
||||
|
||||
if (Handler.Mode == HandlerMode.Server)
|
||||
{
|
||||
var statelessComponent = _driver.StatelessConnectComponent;
|
||||
if (statelessComponent != null)
|
||||
{
|
||||
if (statelessComponent == this)
|
||||
{
|
||||
UpdateSecret();
|
||||
}
|
||||
else
|
||||
{
|
||||
InitFromConnectionless(statelessComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
Logger.Debug("Initializing");
|
||||
|
||||
if (Handler.Mode == HandlerMode.Server)
|
||||
{
|
||||
Initialized();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitFromConnectionless(StatelessConnectHandlerComponent connectionlessHandler)
|
||||
{
|
||||
Logger.Debug("InitFromConnectionless");
|
||||
|
||||
// Store the cookie/address used for the handshake, to enable server ack-retries
|
||||
_lastChallengeSuccessAddress = connectionlessHandler._lastChallengeSuccessAddress;
|
||||
|
||||
Buffer.BlockCopy(connectionlessHandler._authorisedCookie, 0, _authorisedCookie, 0, _authorisedCookie.Length);
|
||||
}
|
||||
|
||||
public override void CountBytes(FArchive ar)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Incoming(FBitReader packet)
|
||||
{
|
||||
base.Incoming(packet);
|
||||
}
|
||||
|
||||
public override void Outgoing(FBitWriter packet, FOutPacketTraits traits)
|
||||
{
|
||||
base.Outgoing(packet, traits);
|
||||
}
|
||||
|
||||
public override void IncomingConnectionless(FIncomingPacketRef packetRef)
|
||||
{
|
||||
var packet = packetRef.Packet;
|
||||
var address = packetRef.Address;
|
||||
|
||||
if (_magicHeader.Length > 0)
|
||||
{
|
||||
// Skip magic header.
|
||||
packet.Pos += _magicHeader.Length;
|
||||
}
|
||||
|
||||
var bHandshakePacket = packet.ReadBit() && !packet.IsError();
|
||||
|
||||
_lastChallengeSuccessAddress = null;
|
||||
|
||||
if (bHandshakePacket)
|
||||
{
|
||||
var bRestartHandshake = false;
|
||||
var secretId = (byte) 0;
|
||||
var timestamp = 1.0d;
|
||||
var cookie = new byte[CookieByteSize];
|
||||
var origCookie = new byte[CookieByteSize];
|
||||
|
||||
bHandshakePacket = ParseHandshakePacket(packet, ref bRestartHandshake, ref secretId, ref timestamp, cookie, origCookie);
|
||||
|
||||
if (bHandshakePacket)
|
||||
{
|
||||
if (Handler.Mode == HandlerMode.Server)
|
||||
{
|
||||
var bInitialConnect = timestamp == 0.0;
|
||||
if (bInitialConnect)
|
||||
{
|
||||
SendConnectChallenge(address);
|
||||
}
|
||||
else if (_driver != null)
|
||||
{
|
||||
var bChallengeSuccess = false;
|
||||
var cookieDelta = _driver.GetElapsedTime() - timestamp;
|
||||
var secretDelta = timestamp - _lastSecretUpdateTimestamp;
|
||||
var bValidCookieLifetime = cookieDelta >= 0.0 && (MaxCookieLifetime - cookieDelta) > 0.0;
|
||||
var bValidSecretIdTimestamp = (secretId == _activeSecret) ? (secretDelta >= 0.0) : (secretDelta <= 0.0);
|
||||
|
||||
if (bValidCookieLifetime && bValidSecretIdTimestamp)
|
||||
{
|
||||
// Regenerate the cookie from the packet info, and see if the received cookie matches the regenerated one
|
||||
var regenCookie = new byte[CookieByteSize];
|
||||
|
||||
GenerateCookie(address, secretId, timestamp, regenCookie);
|
||||
|
||||
bChallengeSuccess = cookie.SequenceEqual(regenCookie);
|
||||
|
||||
if (bChallengeSuccess)
|
||||
{
|
||||
if (bRestartHandshake)
|
||||
{
|
||||
Buffer.BlockCopy(origCookie, 0, _authorisedCookie, 0, _authorisedCookie.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
var curSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie);
|
||||
|
||||
_lastServerSequence = curSequence & (UNetConnection.MaxPacketId - 1);
|
||||
_lastClientSequence = (curSequence + 1) & (UNetConnection.MaxPacketId - 1);
|
||||
|
||||
Buffer.BlockCopy(cookie, 0, _authorisedCookie, 0, _authorisedCookie.Length);
|
||||
}
|
||||
|
||||
_bRestartedHandshake = bRestartHandshake;
|
||||
_lastChallengeSuccessAddress = address;
|
||||
|
||||
// Now ack the challenge response - the cookie is stored in AuthorisedCookie, to enable retries
|
||||
SendChallengeAck(address, _authorisedCookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.SetError();
|
||||
|
||||
Logger.Error("Error reading handshake packet");
|
||||
}
|
||||
}
|
||||
else if (packet.IsError())
|
||||
{
|
||||
Logger.Error("Error reading handshake bit from packet");
|
||||
}
|
||||
// Late packets from recently disconnected clients may incorrectly trigger this code path, so detect and exclude those packets
|
||||
else if (!packet.IsError() && !packetRef.Traits.FromRecentlyDisconnected)
|
||||
{
|
||||
// The packet was fine but not a handshake packet - an existing client might suddenly be communicating on a different address.
|
||||
// If we get them to resend their cookie, we can update the connection's info with their new address.
|
||||
SendRestartHandshakeRequest(address);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ParseHandshakePacket(
|
||||
FBitReader packet,
|
||||
ref bool bOutRestartHandshake,
|
||||
ref byte outSecretId,
|
||||
ref double outTimestamp, byte[] outCookie, byte[] outOrigCookie)
|
||||
{
|
||||
var bValidPacket = false;
|
||||
var bitsLeft = packet.GetBitsLeft();
|
||||
var bHandshakePacketSize = bitsLeft == (HandshakePacketSizeBits - 1);
|
||||
var bRestartResponsePacketSize = bitsLeft == (RestartHandshakePacketSizeBits - 1);
|
||||
|
||||
if (bHandshakePacketSize || bRestartResponsePacketSize)
|
||||
{
|
||||
bOutRestartHandshake = packet.ReadBit();
|
||||
outSecretId = (byte)(packet.ReadBit() ? 1 : 0);
|
||||
outTimestamp = packet.ReadDouble();
|
||||
packet.Serialize(outCookie, CookieByteSize);
|
||||
|
||||
if (bRestartResponsePacketSize)
|
||||
{
|
||||
packet.Serialize(outOrigCookie, CookieByteSize);
|
||||
}
|
||||
|
||||
bValidPacket = !packet.IsError();
|
||||
}
|
||||
else if (bitsLeft == (RestartHandshakePacketSizeBits - 1))
|
||||
{
|
||||
bOutRestartHandshake = packet.ReadBit();
|
||||
bValidPacket = !packet.IsError() && bOutRestartHandshake && Handler.Mode == HandlerMode.Client;
|
||||
}
|
||||
|
||||
return bValidPacket;
|
||||
}
|
||||
|
||||
private void GenerateCookie(IPEndPoint clientAddress, byte secretId, double timestamp, Span<byte> outCookie)
|
||||
{
|
||||
using var writer = new FBitWriter(64 * 8);
|
||||
|
||||
writer.WriteDouble(timestamp);
|
||||
writer.WriteString(clientAddress.ToString());
|
||||
|
||||
HMACSHA1.HashData(_handshakeSecret[secretId], writer.GetData().AsSpan((int)writer.GetNumBytes()), outCookie);
|
||||
}
|
||||
|
||||
private void SendConnectChallenge(IPEndPoint address)
|
||||
{
|
||||
if (_driver == null)
|
||||
{
|
||||
Logger.Warning("Tried to send connect challenge without driver");
|
||||
return;
|
||||
}
|
||||
|
||||
using var challengePacket = new FBitWriter(GetAdjustedSizeBits(HandshakePacketSizeBits) + 1);
|
||||
var bHandshakePacket = (byte)1;
|
||||
var bRestartHandshake = (byte)0;
|
||||
var timestamp = _driver.GetElapsedTime();
|
||||
var cookie = new byte[CookieByteSize];
|
||||
|
||||
GenerateCookie(address, _activeSecret, timestamp, cookie);
|
||||
|
||||
if (_magicHeader.Length > 0)
|
||||
{
|
||||
challengePacket.SerializeBits(_magicHeader, _magicHeader.Count);
|
||||
}
|
||||
|
||||
challengePacket.WriteBit(bHandshakePacket);
|
||||
challengePacket.WriteBit(bRestartHandshake);
|
||||
challengePacket.WriteBit(_activeSecret);
|
||||
challengePacket.WriteDouble(timestamp);
|
||||
challengePacket.Serialize(cookie, cookie.Length);
|
||||
|
||||
Logger.Verbose("SendConnectChallenge. Timestamp: {Timestamp}, Cookie: {Cookie}", timestamp, cookie);
|
||||
|
||||
CapHandshakePacket(challengePacket);
|
||||
|
||||
var connectionlessHandler = _driver.ConnectionlessHandler;
|
||||
|
||||
connectionlessHandler?.SetRawSend(true);
|
||||
|
||||
if (_driver.IsNetResourceValid())
|
||||
{
|
||||
_driver.LowLevelSend(address, challengePacket.GetData(), (int)challengePacket.GetNumBits(), new FOutPacketTraits());
|
||||
}
|
||||
|
||||
connectionlessHandler?.SetRawSend(false);
|
||||
}
|
||||
|
||||
private void SendChallengeAck(IPEndPoint address, byte[] inCookie)
|
||||
{
|
||||
if (_driver == null)
|
||||
{
|
||||
Logger.Warning("Tried to send challenge ack without driver");
|
||||
return;
|
||||
}
|
||||
|
||||
using var ackPacket = new FBitWriter(GetAdjustedSizeBits(HandshakePacketSizeBits) + 1);
|
||||
var bHandshakePacket = (byte)1;
|
||||
var bRestartHandshake = (byte)0;
|
||||
var timestamp = -1.0d;
|
||||
|
||||
if (_magicHeader.Length > 0)
|
||||
{
|
||||
ackPacket.SerializeBits(_magicHeader, _magicHeader.Count);
|
||||
}
|
||||
|
||||
ackPacket.WriteBit(bHandshakePacket);
|
||||
ackPacket.WriteBit(bRestartHandshake);
|
||||
ackPacket.WriteBit(bHandshakePacket);
|
||||
ackPacket.WriteDouble(timestamp);
|
||||
ackPacket.Serialize(inCookie, CookieByteSize);
|
||||
|
||||
Logger.Verbose("SendChallengeAck. InCookie: {Cookie}", inCookie);
|
||||
|
||||
CapHandshakePacket(ackPacket);
|
||||
|
||||
var connectionlessHandler = _driver.ConnectionlessHandler;
|
||||
|
||||
connectionlessHandler?.SetRawSend(true);
|
||||
|
||||
if (_driver.IsNetResourceValid())
|
||||
{
|
||||
_driver.LowLevelSend(address, ackPacket.GetData(), (int)ackPacket.GetNumBits(), new FOutPacketTraits());
|
||||
}
|
||||
|
||||
connectionlessHandler?.SetRawSend(false);
|
||||
}
|
||||
|
||||
private void SendRestartHandshakeRequest(IPEndPoint address)
|
||||
{
|
||||
if (_driver == null)
|
||||
{
|
||||
Logger.Warning("Tried to send restart handshake without driver");
|
||||
return;
|
||||
}
|
||||
|
||||
using var restartPacket = new FBitWriter(GetAdjustedSizeBits(RestartHandshakePacketSizeBits) + 1);
|
||||
var bHandshakePacket = (byte)1;
|
||||
var bRestartHandshake = (byte)1;
|
||||
|
||||
if (_magicHeader.Length > 0)
|
||||
{
|
||||
restartPacket.SerializeBits(_magicHeader, _magicHeader.Count);
|
||||
}
|
||||
|
||||
restartPacket.WriteBit(bHandshakePacket);
|
||||
restartPacket.WriteBit(bRestartHandshake);
|
||||
|
||||
CapHandshakePacket(restartPacket);
|
||||
|
||||
var connectionlessHandler = _driver.ConnectionlessHandler;
|
||||
|
||||
connectionlessHandler?.SetRawSend(true);
|
||||
|
||||
if (_driver.IsNetResourceValid())
|
||||
{
|
||||
_driver.LowLevelSend(address, restartPacket.GetData(), (int)restartPacket.GetNumBits(), new FOutPacketTraits());
|
||||
}
|
||||
|
||||
connectionlessHandler?.SetRawSend(false);
|
||||
}
|
||||
|
||||
public override bool CanReadUnaligned()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CapHandshakePacket(FBitWriter handshakePacket)
|
||||
{
|
||||
var numBits = handshakePacket.GetNumBits() - GetAdjustedSizeBits(0);
|
||||
|
||||
if (numBits != HandshakePacketSizeBits &&
|
||||
numBits != RestartHandshakePacketSizeBits &&
|
||||
numBits != RestartResponseSizeBits)
|
||||
{
|
||||
Logger.Warning("Invalid handshake packet size bits");
|
||||
}
|
||||
|
||||
// Termination bit.
|
||||
handshakePacket.WriteBit(1);
|
||||
}
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private int GetAdjustedSizeBits(int inSizeBits)
|
||||
{
|
||||
return _magicHeader.Length + inSizeBits;
|
||||
}
|
||||
|
||||
public bool HasPassedChallenge(IPEndPoint address, out bool bOutRestartedHandshake)
|
||||
{
|
||||
bOutRestartedHandshake = _bRestartedHandshake;
|
||||
|
||||
return _lastChallengeSuccessAddress != null &&
|
||||
_lastChallengeSuccessAddress.Equals(address);
|
||||
}
|
||||
|
||||
public void UpdateSecret()
|
||||
{
|
||||
_lastSecretUpdateTimestamp = _driver?.GetElapsedTime() ?? 0.0;
|
||||
|
||||
if (_activeSecret == byte.MaxValue)
|
||||
{
|
||||
_handshakeSecret[0] = new byte[SecretByteSize];
|
||||
_handshakeSecret[1] = new byte[SecretByteSize];
|
||||
|
||||
// Randomize other secret.
|
||||
var arr = _handshakeSecret[1];
|
||||
|
||||
for (var i = 0; i < SecretByteSize; i++)
|
||||
{
|
||||
arr[i] = (byte)(Random.Shared.Next() % 255);
|
||||
}
|
||||
|
||||
_activeSecret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_activeSecret = (byte)(_activeSecret == 1 ? 0 : 1);
|
||||
}
|
||||
|
||||
// Randomize current secret.
|
||||
var curArray = _handshakeSecret[_activeSecret];
|
||||
|
||||
for (var i = 0; i < SecretByteSize; i++)
|
||||
{
|
||||
curArray[i] = (byte)(Random.Shared.Next() % 255);
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetReservedPacketBits()
|
||||
{
|
||||
return _magicHeader.Length + 1;
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
if (Handler.Mode == HandlerMode.Client)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
var bConnectionlessHandler = _driver != null && _driver.StatelessConnectComponent == this;
|
||||
if (bConnectionlessHandler)
|
||||
{
|
||||
var curVariance = FMath.FRandRange(0, SecretUpdateTimeVariance);
|
||||
if (((_driver!.GetElapsedTime() - _lastSecretUpdateTimestamp) - (SecretUpdateTime + curVariance)) > 0.0)
|
||||
{
|
||||
UpdateSecret();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Prospect.Unreal.Core;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public class UIpConnection : UNetConnection
|
||||
{
|
||||
public override void InitBase(UNetDriver inDriver, UdpClient inSocket, FURL inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void InitRemoteConnection(UNetDriver inDriver, UdpClient inSocket, FURL inURL, IPEndPoint inRemoteAddr, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FURL inURL, EConnectionState inState,
|
||||
int inMaxPacket = 0, int inPacketOverhead = 0)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string LowLevelGetRemoteAddress(bool bAppendPort = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string LowLevelDescribe()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Tick(float deltaSeconds)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void CleanUp()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void ReceivedRawPacket(byte[] data, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetTimeoutValue()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Prospect.Unreal.Core;
|
||||
using Serilog;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public class UIpNetDriver : UNetDriver
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext<UIpNetDriver>();
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
public UIpNetDriver()
|
||||
{
|
||||
ServerIp = new IPEndPoint(IPAddress.Any, 7777);
|
||||
Socket = new UdpClient(ServerIp);
|
||||
ReceiveThread = new FReceiveThreadRunnable(this);
|
||||
}
|
||||
|
||||
public IPEndPoint ServerIp { get; }
|
||||
public UdpClient Socket { get; }
|
||||
public FReceiveThreadRunnable ReceiveThread { get; }
|
||||
|
||||
public bool Init()
|
||||
{
|
||||
// Initialize connectionless packet handler.
|
||||
InitConnectionlessHandler();
|
||||
|
||||
// Start receiving packets.
|
||||
ReceiveThread.Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void TickDispatch(float deltaTime)
|
||||
{
|
||||
base.TickDispatch(deltaTime);
|
||||
|
||||
while (ReceiveThread.TryReceive(out var packet))
|
||||
{
|
||||
Logger.Information("Received {Buffer} from {Adress}", packet.DataView.GetData(), packet.Address);
|
||||
|
||||
UNetConnection? connection = null;
|
||||
|
||||
if (Equals(packet.Address, ServerIp))
|
||||
{
|
||||
// TODO: Assign connection.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
if (connection == null)
|
||||
{
|
||||
MappedClientConnections.TryGetValue(packet.Address, out connection);
|
||||
}
|
||||
|
||||
// If we didn't find a client connection, maybe create a new one.
|
||||
if (connection == null)
|
||||
{
|
||||
connection = ProcessConnectionlessPacket(packet);
|
||||
// TODO: bIgnorePacket
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private UNetConnection? ProcessConnectionlessPacket(FReceivedPacketView packet)
|
||||
{
|
||||
UNetConnection? returnVal;
|
||||
var statelessConnect = StatelessConnectComponent;
|
||||
var address = packet.Address;
|
||||
var bPassedChallenge = false;
|
||||
var bRestartedHandshake = false;
|
||||
var bIgnorePacket = true;
|
||||
|
||||
if (ConnectionlessHandler != null && statelessConnect != null)
|
||||
{
|
||||
var result = ConnectionlessHandler.IncomingConnectionless(packet);
|
||||
if (result)
|
||||
{
|
||||
bPassedChallenge = statelessConnect.HasPassedChallenge(address, out bRestartedHandshake);
|
||||
|
||||
if (bPassedChallenge)
|
||||
{
|
||||
if (bRestartedHandshake)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var newCountBytes = packet.DataView.NumBytes();
|
||||
var workingData = new byte[newCountBytes];
|
||||
|
||||
if (newCountBytes > 0)
|
||||
{
|
||||
Buffer.BlockCopy(packet.DataView.GetData(), 0, workingData, 0, newCountBytes);
|
||||
bIgnorePacket = false;
|
||||
}
|
||||
|
||||
packet.DataView = new FPacketDataView(workingData, newCountBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning("Invalid ConnectionlessHandler or StatelessConnectComponent, can't accept connections");
|
||||
}
|
||||
|
||||
if (bPassedChallenge)
|
||||
{
|
||||
if (!bRestartedHandshake)
|
||||
{
|
||||
Logger.Verbose("Server accepting post-challenge connection from: {Address}", address);
|
||||
|
||||
returnVal = new UIpConnection();
|
||||
|
||||
if (statelessConnect != null)
|
||||
{
|
||||
statelessConnect.GetChallengeSequences(out var serverSequence, out var clientSequence);
|
||||
|
||||
returnVal.InitSequence(clientSequence, serverSequence);
|
||||
}
|
||||
|
||||
// TODO: World url
|
||||
returnVal.InitRemoteConnection(this, Socket, null, address, EConnectionState.USOCK_Open);
|
||||
|
||||
// if (returnVal.Handler)
|
||||
}
|
||||
|
||||
if (statelessConnect != null)
|
||||
{
|
||||
statelessConnect.ResetChallengeData();
|
||||
}
|
||||
}
|
||||
|
||||
if (bIgnorePacket)
|
||||
{
|
||||
packet.DataView = new FPacketDataView(packet.DataView.GetData(), 0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void LowLevelSend(IPEndPoint address, byte[] data, int countBits, FOutPacketTraits traits)
|
||||
{
|
||||
if (ConnectionlessHandler != null)
|
||||
{
|
||||
var processedData = ConnectionlessHandler.OutgoingConnectionless(address, data, countBits, traits);
|
||||
if (!processedData.Error)
|
||||
{
|
||||
data = processedData.Data;
|
||||
countBits = processedData.CountBits;
|
||||
}
|
||||
else
|
||||
{
|
||||
countBits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (countBits > 0)
|
||||
{
|
||||
Socket.Send(data, FMath.DivideAndRoundUp(countBits, 8), address);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsNetResourceValid()
|
||||
{
|
||||
return !_isDisposed;
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
_isDisposed = true;
|
||||
|
||||
await base.DisposeAsync();
|
||||
|
||||
Socket.Dispose();
|
||||
|
||||
await ReceiveThread.DisposeAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Prospect.Unreal.Core;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public abstract class UNetConnection
|
||||
{
|
||||
public const int ReliableBuffer = 256;
|
||||
public const int MaxPacketId = 16384;
|
||||
|
||||
public abstract void InitBase(UNetDriver inDriver, UdpClient inSocket, FURL inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0);
|
||||
public abstract void InitRemoteConnection(UNetDriver inDriver, UdpClient inSocket, FURL inURL, IPEndPoint inRemoteAddr, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0);
|
||||
public abstract void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FURL inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0);
|
||||
public abstract void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits);
|
||||
public abstract string LowLevelGetRemoteAddress(bool bAppendPort = false);
|
||||
public abstract string LowLevelDescribe();
|
||||
public abstract void Tick(float deltaSeconds);
|
||||
public abstract void CleanUp();
|
||||
public abstract void ReceivedRawPacket(byte[] data, int count);
|
||||
public abstract float GetTimeoutValue();
|
||||
|
||||
public void InitSequence(int clientSequence, int serverSequence)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
public abstract class UNetDriver : IAsyncDisposable
|
||||
{
|
||||
private double _elapsedTime;
|
||||
|
||||
protected UNetDriver()
|
||||
{
|
||||
MappedClientConnections = new ConcurrentDictionary<IPEndPoint, UNetConnection>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map of <see cref="IPEndPoint"/> to <see cref="UNetConnection"/>.
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<IPEndPoint, UNetConnection> MappedClientConnections { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Serverside PacketHandler for managing connectionless packets
|
||||
/// </summary>
|
||||
public PacketHandler? ConnectionlessHandler { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the PacketHandler component, for managing stateless connection handshakes
|
||||
/// </summary>
|
||||
public StatelessConnectHandlerComponent? StatelessConnectComponent { get; private set; }
|
||||
|
||||
public void InitConnectionlessHandler()
|
||||
{
|
||||
ConnectionlessHandler = new PacketHandler();
|
||||
ConnectionlessHandler.Initialize(true);
|
||||
|
||||
StatelessConnectComponent = (StatelessConnectHandlerComponent) ConnectionlessHandler.AddHandler<StatelessConnectHandlerComponent>();
|
||||
StatelessConnectComponent.SetDriver(this);
|
||||
|
||||
ConnectionlessHandler.InitializeComponents();
|
||||
}
|
||||
|
||||
public virtual void TickDispatch(float deltaTime)
|
||||
{
|
||||
_elapsedTime += deltaTime;
|
||||
}
|
||||
|
||||
public virtual void LowLevelSend(IPEndPoint address, byte[] data, int countBits, FOutPacketTraits traits)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public abstract bool IsNetResourceValid();
|
||||
|
||||
public double GetElapsedTime()
|
||||
{
|
||||
return _elapsedTime;
|
||||
}
|
||||
|
||||
public void ResetElapsedTime()
|
||||
{
|
||||
_elapsedTime = 0.0;
|
||||
}
|
||||
|
||||
public virtual ValueTask DisposeAsync()
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user