CHANGED TO MIRROR

This commit is contained in:
DerTyp187
2021-10-25 09:20:01 +02:00
parent bd712107b7
commit e509a919b6
611 changed files with 38291 additions and 1216 deletions

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Concurrent;
using UnityEngine;
namespace Mirror.SimpleWeb
{
public enum ClientState
{
NotConnected = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
}
/// <summary>
/// Client used to control websockets
/// <para>Base class used by WebSocketClientWebGl and WebSocketClientStandAlone</para>
/// </summary>
public abstract class SimpleWebClient
{
public static SimpleWebClient Create(int maxMessageSize, int maxMessagesPerTick, TcpConfig tcpConfig)
{
#if UNITY_WEBGL && !UNITY_EDITOR
return new WebSocketClientWebGl(maxMessageSize, maxMessagesPerTick);
#else
return new WebSocketClientStandAlone(maxMessageSize, maxMessagesPerTick, tcpConfig);
#endif
}
readonly int maxMessagesPerTick;
protected readonly int maxMessageSize;
protected readonly ConcurrentQueue<Message> receiveQueue = new ConcurrentQueue<Message>();
protected readonly BufferPool bufferPool;
protected ClientState state;
protected SimpleWebClient(int maxMessageSize, int maxMessagesPerTick)
{
this.maxMessageSize = maxMessageSize;
this.maxMessagesPerTick = maxMessagesPerTick;
bufferPool = new BufferPool(5, 20, maxMessageSize);
}
public ClientState ConnectionState => state;
public event Action onConnect;
public event Action onDisconnect;
public event Action<ArraySegment<byte>> onData;
public event Action<Exception> onError;
public void ProcessMessageQueue(MonoBehaviour behaviour)
{
int processedCount = 0;
// check enabled every time in case behaviour was disabled after data
while (
behaviour.enabled &&
processedCount < maxMessagesPerTick &&
// Dequeue last
receiveQueue.TryDequeue(out Message next)
)
{
processedCount++;
switch (next.type)
{
case EventType.Connected:
onConnect?.Invoke();
break;
case EventType.Data:
onData?.Invoke(next.data.ToSegment());
next.data.Release();
break;
case EventType.Disconnected:
onDisconnect?.Invoke();
break;
case EventType.Error:
onError?.Invoke(next.exception);
break;
}
}
}
public abstract void Connect(Uri serverAddress);
public abstract void Disconnect();
public abstract void Send(ArraySegment<byte> segment);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 13131761a0bf5a64dadeccd700fe26e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a9c19d05220a87c4cbbe4d1e422da0aa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,77 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Mirror.SimpleWeb
{
/// <summary>
/// Handles Handshake to the server when it first connects
/// <para>The client handshake does not need buffers to reduce allocations since it only happens once</para>
/// </summary>
internal class ClientHandshake
{
public bool TryHandshake(Connection conn, Uri uri)
{
try
{
Stream stream = conn.stream;
byte[] keyBuffer = new byte[16];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(keyBuffer);
}
string key = Convert.ToBase64String(keyBuffer);
string keySum = key + Constants.HandshakeGUID;
byte[] keySumBytes = Encoding.ASCII.GetBytes(keySum);
Log.Verbose($"Handshake Hashing {Encoding.ASCII.GetString(keySumBytes)}");
byte[] keySumHash = SHA1.Create().ComputeHash(keySumBytes);
string expectedResponse = Convert.ToBase64String(keySumHash);
string handshake =
$"GET {uri.PathAndQuery} HTTP/1.1\r\n" +
$"Host: {uri.Host}:{uri.Port}\r\n" +
$"Upgrade: websocket\r\n" +
$"Connection: Upgrade\r\n" +
$"Sec-WebSocket-Key: {key}\r\n" +
$"Sec-WebSocket-Version: 13\r\n" +
"\r\n";
byte[] encoded = Encoding.ASCII.GetBytes(handshake);
stream.Write(encoded, 0, encoded.Length);
byte[] responseBuffer = new byte[1000];
int? lengthOrNull = ReadHelper.SafeReadTillMatch(stream, responseBuffer, 0, responseBuffer.Length, Constants.endOfHandshake);
if (!lengthOrNull.HasValue)
{
Log.Error("Connected closed before handshake");
return false;
}
string responseString = Encoding.ASCII.GetString(responseBuffer, 0, lengthOrNull.Value);
string acceptHeader = "Sec-WebSocket-Accept: ";
int startIndex = responseString.IndexOf(acceptHeader) + acceptHeader.Length;
int endIndex = responseString.IndexOf("\r\n", startIndex);
string responseKey = responseString.Substring(startIndex, endIndex - startIndex);
if (responseKey != expectedResponse)
{
Log.Error($"Response key incorrect, Response:{responseKey} Expected:{expectedResponse}");
return false;
}
return true;
}
catch (Exception e)
{
Log.Exception(e);
return false;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ffdcabc9e28f764a94fc4efc82d3e8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using System;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
namespace Mirror.SimpleWeb
{
internal class ClientSslHelper
{
internal bool TryCreateStream(Connection conn, Uri uri)
{
NetworkStream stream = conn.client.GetStream();
if (uri.Scheme != "wss")
{
conn.stream = stream;
return true;
}
try
{
conn.stream = CreateStream(stream, uri);
return true;
}
catch (Exception e)
{
Log.Error($"Create SSLStream Failed: {e}", false);
return false;
}
}
Stream CreateStream(NetworkStream stream, Uri uri)
{
SslStream sslStream = new SslStream(stream, true, ValidateServerCertificate);
sslStream.AuthenticateAsClient(uri.Host);
return sslStream;
}
static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// Do not allow this client to communicate with unauthenticated servers.
// only accept if no errors
return sslPolicyErrors == SslPolicyErrors.None;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46055a75559a79849a750f39a766db61
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,139 @@
using System;
using System.Net.Sockets;
using System.Threading;
namespace Mirror.SimpleWeb
{
public class WebSocketClientStandAlone : SimpleWebClient
{
readonly ClientSslHelper sslHelper;
readonly ClientHandshake handshake;
readonly TcpConfig tcpConfig;
Connection conn;
internal WebSocketClientStandAlone(int maxMessageSize, int maxMessagesPerTick, TcpConfig tcpConfig) : base(maxMessageSize, maxMessagesPerTick)
{
#if UNITY_WEBGL && !UNITY_EDITOR
throw new NotSupportedException();
#else
sslHelper = new ClientSslHelper();
handshake = new ClientHandshake();
this.tcpConfig = tcpConfig;
#endif
}
public override void Connect(Uri serverAddress)
{
state = ClientState.Connecting;
Thread receiveThread = new Thread(() => ConnectAndReceiveLoop(serverAddress));
receiveThread.IsBackground = true;
receiveThread.Start();
}
void ConnectAndReceiveLoop(Uri serverAddress)
{
try
{
TcpClient client = new TcpClient();
tcpConfig.ApplyTo(client);
// create connection object here so dispose correctly disconnects on failed connect
conn = new Connection(client, AfterConnectionDisposed);
conn.receiveThread = Thread.CurrentThread;
try
{
client.Connect(serverAddress.Host, serverAddress.Port);
}
catch (SocketException)
{
client.Dispose();
throw;
}
bool success = sslHelper.TryCreateStream(conn, serverAddress);
if (!success)
{
Log.Warn("Failed to create Stream");
conn.Dispose();
return;
}
success = handshake.TryHandshake(conn, serverAddress);
if (!success)
{
Log.Warn("Failed Handshake");
conn.Dispose();
return;
}
Log.Info("HandShake Successful");
state = ClientState.Connected;
receiveQueue.Enqueue(new Message(EventType.Connected));
Thread sendThread = new Thread(() =>
{
SendLoop.Config sendConfig = new SendLoop.Config(
conn,
bufferSize: Constants.HeaderSize + Constants.MaskSize + maxMessageSize,
setMask: true);
SendLoop.Loop(sendConfig);
});
conn.sendThread = sendThread;
sendThread.IsBackground = true;
sendThread.Start();
ReceiveLoop.Config config = new ReceiveLoop.Config(conn,
maxMessageSize,
false,
receiveQueue,
bufferPool);
ReceiveLoop.Loop(config);
}
catch (ThreadInterruptedException e) { Log.InfoException(e); }
catch (ThreadAbortException e) { Log.InfoException(e); }
catch (Exception e) { Log.Exception(e); }
finally
{
// close here in case connect fails
conn?.Dispose();
}
}
void AfterConnectionDisposed(Connection conn)
{
state = ClientState.NotConnected;
// make sure Disconnected event is only called once
receiveQueue.Enqueue(new Message(EventType.Disconnected));
}
public override void Disconnect()
{
state = ClientState.Disconnecting;
Log.Info("Disconnect Called");
if (conn == null)
{
state = ClientState.NotConnected;
}
else
{
conn?.Dispose();
}
}
public override void Send(ArraySegment<byte> segment)
{
ArrayBuffer buffer = bufferPool.Take(segment.Count);
buffer.CopyFrom(segment);
conn.sendQueue.Enqueue(buffer);
conn.sendPending.Set();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05a9c87dea309e241a9185e5aa0d72ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7142349d566213c4abc763afaf4d91a1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using System;
#if UNITY_WEBGL
using System.Runtime.InteropServices;
#endif
namespace Mirror.SimpleWeb
{
internal static class SimpleWebJSLib
{
#if UNITY_WEBGL
[DllImport("__Internal")]
internal static extern bool IsConnected(int index);
#pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments
[DllImport("__Internal")]
#pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments
internal static extern int Connect(string address, Action<int> openCallback, Action<int> closeCallBack, Action<int, IntPtr, int> messageCallback, Action<int> errorCallback);
[DllImport("__Internal")]
internal static extern void Disconnect(int index);
[DllImport("__Internal")]
internal static extern bool Send(int index, byte[] array, int offset, int length);
#else
internal static bool IsConnected(int index) => throw new NotSupportedException();
internal static int Connect(string address, Action<int> openCallback, Action<int> closeCallBack, Action<int, IntPtr, int> messageCallback, Action<int> errorCallback) => throw new NotSupportedException();
internal static void Disconnect(int index) => throw new NotSupportedException();
internal static bool Send(int index, byte[] array, int offset, int length) => throw new NotSupportedException();
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97b96a0b65c104443977473323c2ff35
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using AOT;
namespace Mirror.SimpleWeb
{
public class WebSocketClientWebGl : SimpleWebClient
{
static readonly Dictionary<int, WebSocketClientWebGl> instances = new Dictionary<int, WebSocketClientWebGl>();
/// <summary>
/// key for instances sent between c# and js
/// </summary>
int index;
internal WebSocketClientWebGl(int maxMessageSize, int maxMessagesPerTick) : base(maxMessageSize, maxMessagesPerTick)
{
#if !UNITY_WEBGL || UNITY_EDITOR
throw new NotSupportedException();
#endif
}
public bool CheckJsConnected() => SimpleWebJSLib.IsConnected(index);
public override void Connect(Uri serverAddress)
{
index = SimpleWebJSLib.Connect(serverAddress.ToString(), OpenCallback, CloseCallBack, MessageCallback, ErrorCallback);
instances.Add(index, this);
state = ClientState.Connecting;
}
public override void Disconnect()
{
state = ClientState.Disconnecting;
// disconnect should cause closeCallback and OnDisconnect to be called
SimpleWebJSLib.Disconnect(index);
}
public override void Send(ArraySegment<byte> segment)
{
if (segment.Count > maxMessageSize)
{
Log.Error($"Cant send message with length {segment.Count} because it is over the max size of {maxMessageSize}");
return;
}
SimpleWebJSLib.Send(index, segment.Array, 0, segment.Count);
}
void onOpen()
{
receiveQueue.Enqueue(new Message(EventType.Connected));
state = ClientState.Connected;
}
void onClose()
{
// this code should be last in this class
receiveQueue.Enqueue(new Message(EventType.Disconnected));
state = ClientState.NotConnected;
instances.Remove(index);
}
void onMessage(IntPtr bufferPtr, int count)
{
try
{
ArrayBuffer buffer = bufferPool.Take(count);
buffer.CopyFrom(bufferPtr, count);
receiveQueue.Enqueue(new Message(buffer));
}
catch (Exception e)
{
Log.Error($"onData {e.GetType()}: {e.Message}\n{e.StackTrace}");
receiveQueue.Enqueue(new Message(e));
}
}
void onErr()
{
receiveQueue.Enqueue(new Message(new Exception("Javascript Websocket error")));
Disconnect();
}
[MonoPInvokeCallback(typeof(Action<int>))]
static void OpenCallback(int index) => instances[index].onOpen();
[MonoPInvokeCallback(typeof(Action<int>))]
static void CloseCallBack(int index) => instances[index].onClose();
[MonoPInvokeCallback(typeof(Action<int, IntPtr, int>))]
static void MessageCallback(int index, IntPtr bufferPtr, int count) => instances[index].onMessage(bufferPtr, count);
[MonoPInvokeCallback(typeof(Action<int>))]
static void ErrorCallback(int index) => instances[index].onErr();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 015c5b1915fd1a64cbe36444d16b2f7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1999985791b91b9458059e88404885a7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,105 @@
// this will create a global object
const SimpleWeb = {
webSockets: [],
next: 1,
GetWebSocket: function (index) {
return SimpleWeb.webSockets[index]
},
AddNextSocket: function (webSocket) {
var index = SimpleWeb.next;
SimpleWeb.next++;
SimpleWeb.webSockets[index] = webSocket;
return index;
},
RemoveSocket: function (index) {
SimpleWeb.webSockets[index] = undefined;
},
};
function IsConnected(index) {
var webSocket = SimpleWeb.GetWebSocket(index);
if (webSocket) {
return webSocket.readyState === webSocket.OPEN;
}
else {
return false;
}
}
function Connect(addressPtr, openCallbackPtr, closeCallBackPtr, messageCallbackPtr, errorCallbackPtr) {
const address = Pointer_stringify(addressPtr);
console.log("Connecting to " + address);
// Create webSocket connection.
webSocket = new WebSocket(address);
webSocket.binaryType = 'arraybuffer';
const index = SimpleWeb.AddNextSocket(webSocket);
// Connection opened
webSocket.addEventListener('open', function (event) {
console.log("Connected to " + address);
Runtime.dynCall('vi', openCallbackPtr, [index]);
});
webSocket.addEventListener('close', function (event) {
console.log("Disconnected from " + address);
Runtime.dynCall('vi', closeCallBackPtr, [index]);
});
// Listen for messages
webSocket.addEventListener('message', function (event) {
if (event.data instanceof ArrayBuffer) {
// TODO dont alloc each time
var array = new Uint8Array(event.data);
var arrayLength = array.length;
var bufferPtr = _malloc(arrayLength);
var dataBuffer = new Uint8Array(HEAPU8.buffer, bufferPtr, arrayLength);
dataBuffer.set(array);
Runtime.dynCall('viii', messageCallbackPtr, [index, bufferPtr, arrayLength]);
_free(bufferPtr);
}
else {
console.error("message type not supported")
}
});
webSocket.addEventListener('error', function (event) {
console.error('Socket Error', event);
Runtime.dynCall('vi', errorCallbackPtr, [index]);
});
return index;
}
function Disconnect(index) {
var webSocket = SimpleWeb.GetWebSocket(index);
if (webSocket) {
webSocket.close(1000, "Disconnect Called by Mirror");
}
SimpleWeb.RemoveSocket(index);
}
function Send(index, arrayPtr, offset, length) {
var webSocket = SimpleWeb.GetWebSocket(index);
if (webSocket) {
const start = arrayPtr + offset;
const end = start + length;
const data = HEAPU8.buffer.slice(start, end);
webSocket.send(data);
return true;
}
return false;
}
const SimpleWebLib = {
$SimpleWeb: SimpleWeb,
IsConnected,
Connect,
Disconnect,
Send
};
autoAddDeps(SimpleWebLib, '$SimpleWeb');
mergeInto(LibraryManager.library, SimpleWebLib);

View File

@@ -0,0 +1,37 @@
fileFormatVersion: 2
guid: 54452a8c6d2ca9b49a8c79f81b50305c
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Facebook: WebGL
second:
enabled: 1
settings: {}
- first:
WebGL: WebGL
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant: