mirror of
https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline
synced 2026-08-03 11:12:41 -04:00
225 lines
6.9 KiB
C#
225 lines
6.9 KiB
C#
using Arrowgene.Ddon.Shared.Entity;
|
|
using Arrowgene.Ddon.Shared.Entity.PacketStructure;
|
|
using Arrowgene.Ddon.Shared.Network;
|
|
using Arrowgene.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Arrowgene.Networking.SAEAServer;
|
|
|
|
namespace Arrowgene.Ddon.Server.Network
|
|
{
|
|
public class Client
|
|
{
|
|
private static uint IncrementingId = 0;
|
|
|
|
private readonly ServerLogger Logger;
|
|
|
|
protected readonly ClientHandle ClientHandle;
|
|
private readonly PacketFactory _packetFactory;
|
|
private Challenge _challenge;
|
|
|
|
/**
|
|
* Roundtrip Challenge completed
|
|
* The client considered to be initialized with crypto key
|
|
* and able to parse packet headers.
|
|
*/
|
|
private bool _challengeCompleted;
|
|
|
|
public Client(ClientHandle clientHandle, PacketFactory packetFactory)
|
|
{
|
|
Logger = LogProvider.Logger<ServerLogger>(GetType());
|
|
ClientHandle = clientHandle;
|
|
_packetFactory = packetFactory;
|
|
_challenge = null;
|
|
Identity = clientHandle.Identity;
|
|
_challengeCompleted = false;
|
|
Id = IncrementingId++;
|
|
}
|
|
|
|
public string Identity { get; protected set; }
|
|
public uint Id { get; protected set; }
|
|
|
|
public DateTime PingTime { get; set; }
|
|
|
|
public PacketId LastPacketSentToServer { get; set; }
|
|
public PacketId LastPacketSentToClient { get; set; }
|
|
|
|
public bool IsAlive
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
// ensure ClientHandle is still valid
|
|
return ClientHandle.IsAlive;
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
~Client()
|
|
{
|
|
// Something is funny in the event handling for disconnections, so I'm burying this logging here to make sure this absolutely gets called at some point.
|
|
// This may be divorced in the log from the actual event, but you can reconstruct this based on the IP address and the rough timing.
|
|
Logger.Debug(this,
|
|
$"Final Packets: {this?.LastPacketSentToServer.Name ?? "NULL"} / {this?.LastPacketSentToClient.Name ?? "NULL"}");
|
|
}
|
|
|
|
public void SetChallengeCompleted(bool challengeCompleted)
|
|
{
|
|
_challengeCompleted = challengeCompleted;
|
|
Logger.Info(this, $"SetChallengeCompleted:{_challengeCompleted}");
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
ClientHandle.Disconnect();
|
|
}
|
|
|
|
public List<IPacket> Receive(byte[] data)
|
|
{
|
|
List<IPacket> packets;
|
|
try
|
|
{
|
|
packets = _packetFactory.Read(data);
|
|
}
|
|
catch (ResponseErrorException ex)
|
|
{
|
|
// Usually thrown by the Camelia cipher complaining about misshapen packets.
|
|
// We shouldn't tolerate these connections and just kick them.
|
|
Logger.Exception(this, ex);
|
|
packets = [];
|
|
Close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Exception(this, ex);
|
|
packets = [];
|
|
}
|
|
|
|
if (packets.Count > 0)
|
|
{
|
|
LastPacketSentToServer = packets.Last().Id;
|
|
}
|
|
|
|
foreach (IPacket packet in packets)
|
|
{
|
|
Logger.LogPacket(this, packet);
|
|
}
|
|
|
|
return packets;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a Structure
|
|
/// </summary>
|
|
public void Send<TResStruct>(TResStruct res) where TResStruct : class, IPacketStructure, new()
|
|
{
|
|
StructurePacket<TResStruct> packet = new StructurePacket<TResStruct>(res);
|
|
|
|
Send(packet);
|
|
}
|
|
|
|
public void Enqueue<TResStruct>(TResStruct res, PacketQueue queue)
|
|
where TResStruct : class, IPacketStructure, new()
|
|
{
|
|
StructurePacket<TResStruct> packet = new StructurePacket<TResStruct>(res);
|
|
queue.Enqueue((this, packet));
|
|
}
|
|
|
|
public void Enqueue(Packet res, PacketQueue queue)
|
|
{
|
|
queue.Enqueue((this, res));
|
|
}
|
|
|
|
public void Send(Packet packet)
|
|
{
|
|
if (!_challengeCompleted
|
|
&& packet.Id != PacketId.S2C_CERT_CLIENT_CHALLENGE_RES
|
|
&& packet.Id != PacketId.L2C_CLIENT_CHALLENGE_RES
|
|
)
|
|
{
|
|
// at this point in time we only allow to send S2C_CERT_CLIENT_CHALLENGE_RES
|
|
// only after receiving the first client packet, we can assume the client is able
|
|
// to parse packets headers and process other packets.
|
|
Logger.Debug(this,
|
|
$"Tried to send Packet:\"{packet.PrintHeader()}\", while client not yet considered ready");
|
|
return;
|
|
}
|
|
|
|
byte[] data;
|
|
try
|
|
{
|
|
data = _packetFactory.Write(packet);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Exception(this, ex);
|
|
return;
|
|
}
|
|
|
|
|
|
LastPacketSentToClient = packet.Id;
|
|
|
|
SendRaw(data);
|
|
Logger.LogPacket(this, packet);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends raw bytes to the client, without any further processing
|
|
/// </summary>
|
|
public void SendRaw(byte[] data)
|
|
{
|
|
ClientHandle.Send(data);
|
|
}
|
|
|
|
public void InitializeChallenge()
|
|
{
|
|
_challenge = new Challenge();
|
|
byte[] challenge = _challenge.CreateClientCertChallenge();
|
|
byte[] data;
|
|
try
|
|
{
|
|
data = _packetFactory.WriteDataWithLengthPrefix(challenge);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Exception(this, ex);
|
|
return;
|
|
}
|
|
|
|
SendRaw(data);
|
|
}
|
|
|
|
|
|
public Challenge.Response HandleChallenge(C2SCertClientChallengeReq request)
|
|
{
|
|
Challenge.Response challenge = _challenge.HandleClientCertChallenge(request);
|
|
_challenge = null;
|
|
if (challenge.Error)
|
|
{
|
|
Logger.Error(this, "Failed CertChallenge");
|
|
}
|
|
|
|
_packetFactory.SetCamelliaKey(challenge.CamelliaKey);
|
|
return challenge;
|
|
}
|
|
|
|
public Challenge.Response HandleChallenge(C2LClientChallengeReq request)
|
|
{
|
|
Challenge.Response challenge = _challenge.HandleClientCertChallenge(request);
|
|
_challenge = null;
|
|
if (challenge.Error)
|
|
{
|
|
Logger.Error(this, "Failed CertChallenge");
|
|
}
|
|
|
|
_packetFactory.SetCamelliaKey(challenge.CamelliaKey);
|
|
return challenge;
|
|
}
|
|
}
|
|
}
|