2024-10-17 17:23:05 +03:00

125 lines
5.0 KiB
C#

// server needs to store a separate KcpPeer for each connection.
// as well as remoteEndPoint so we know where to send data to.
using System;
using System.Net;
namespace kcp2k
{
public class KcpServerConnection : KcpPeer
{
public readonly EndPoint remoteEndPoint;
// callbacks
// even for errors, to allow liraries to show popups etc.
// instead of logging directly.
// (string instead of Exception for ease of use and to avoid user panic)
//
// events are readonly, set in constructor.
// this ensures they are always initialized when used.
// fixes https://github.com/MirrorNetworking/Mirror/issues/3337 and more
protected readonly Action<KcpServerConnection> OnConnectedCallback;
protected readonly Action<ArraySegment<byte>, KcpChannel> OnDataCallback;
protected readonly Action OnDisconnectedCallback;
protected readonly Action<ErrorCode, string> OnErrorCallback;
protected readonly Action<ArraySegment<byte>> RawSendCallback;
public KcpServerConnection(
Action<KcpServerConnection> OnConnected,
Action<ArraySegment<byte>, KcpChannel> OnData,
Action OnDisconnected,
Action<ErrorCode, string> OnError,
Action<ArraySegment<byte>> OnRawSend,
KcpConfig config,
uint cookie,
EndPoint remoteEndPoint)
: base(config, cookie)
{
OnConnectedCallback = OnConnected;
OnDataCallback = OnData;
OnDisconnectedCallback = OnDisconnected;
OnErrorCallback = OnError;
RawSendCallback = OnRawSend;
this.remoteEndPoint = remoteEndPoint;
}
// callbacks ///////////////////////////////////////////////////////////
protected override void OnAuthenticated()
{
// once we receive the first client hello,
// immediately reply with hello so the client knows the security cookie.
SendHello();
OnConnectedCallback(this);
}
protected override void OnData(ArraySegment<byte> message, KcpChannel channel) =>
OnDataCallback(message, channel);
protected override void OnDisconnected() =>
OnDisconnectedCallback();
protected override void OnError(ErrorCode error, string message) =>
OnErrorCallback(error, message);
protected override void RawSend(ArraySegment<byte> data) =>
RawSendCallback(data);
////////////////////////////////////////////////////////////////////////
// insert raw IO. usually from socket.Receive.
// offset is useful for relays, where we may parse a header and then
// feed the rest to kcp.
public void RawInput(ArraySegment<byte> segment)
{
// ensure valid size: at least 1 byte for channel + 4 bytes for cookie
if (segment.Count <= 5) return;
// parse channel
// byte channel = segment[0]; ArraySegment[i] isn't supported in some older Unity Mono versions
byte channel = segment.Array[segment.Offset + 0];
// all server->client messages include the server's security cookie.
// all client->server messages except for the initial 'hello' include it too.
// parse the cookie and make sure it matches (except for initial hello).
Utils.Decode32U(segment.Array, segment.Offset + 1, out uint messageCookie);
// compare cookie to protect against UDP spoofing.
// messages won't have a cookie until after handshake.
// so only compare if we are authenticated.
// simply drop the message if the cookie doesn't match.
if (state == KcpState.Authenticated)
{
if (messageCookie != cookie)
{
Log.Warning($"[KCP] ServerConnection: dropped message with invalid cookie: {messageCookie} expected: {cookie} state: {state}");
return;
}
}
// parse message
ArraySegment<byte> message = new ArraySegment<byte>(segment.Array, segment.Offset + 1+4, segment.Count - 1-4);
switch (channel)
{
case (byte)KcpChannel.Reliable:
{
OnRawInputReliable(message);
break;
}
case (byte)KcpChannel.Unreliable:
{
OnRawInputUnreliable(message);
break;
}
default:
{
// invalid channel indicates random internet noise.
// servers may receive random UDP data.
// just ignore it, but log for easier debugging.
Log.Warning($"[KCP] ServerConnection: invalid channel header: {channel}, likely internet noise");
break;
}
}
}
}
}