111
This commit is contained in:
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/AssemblyInfo.cs
vendored
Normal file
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/AssemblyInfo.cs
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyVersion("1.6.0")]
|
||||
|
||||
[assembly: InternalsVisibleTo("SimpleWebTransport.Tests.Runtime")]
|
||||
[assembly: InternalsVisibleTo("SimpleWebTransport.Tests.Editor")]
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/AssemblyInfo.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/AssemblyInfo.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee9e76201f7665244bd6ab8ea343a83f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
48
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/CHANGELOG.md
vendored
Normal file
48
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# [1.3.0](https://github.com/James-Frowen/SimpleWebTransport/compare/v1.2.7...v1.3.0) (2022-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allowing max message size to be increase to int32.max ([#2](https://github.com/James-Frowen/SimpleWebTransport/issues/2)) ([4cc60fd](https://github.com/James-Frowen/SimpleWebTransport/commit/4cc60fd67f3c65d90ced0e6f9f97d15d0368076d))
|
||||
|
||||
## [1.2.7](https://github.com/James-Frowen/SimpleWebTransport/compare/v1.2.6...v1.2.7) (2022-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixing ObjectDisposedException in toString ([426de52](https://github.com/James-Frowen/SimpleWebTransport/commit/426de52ee4e98ac6212713b2b2272e3affb8fc99))
|
||||
|
||||
## [1.2.6](https://github.com/James-Frowen/SimpleWebTransport/compare/v1.2.5...v1.2.6) (2022-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixing Runtime is not defined for unity 2021 ([945b50d](https://github.com/James-Frowen/SimpleWebTransport/commit/945b50dbad5b71c43e2bdaa4033f87d3f62c5572))
|
||||
|
||||
## [1.2.5](https://github.com/James-Frowen/SimpleWebTransport/compare/v1.2.4...v1.2.5) (2022-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* updating Pointer_stringify to UTF8ToString ([2f5a74b](https://github.com/James-Frowen/SimpleWebTransport/commit/2f5a74ba10865e934be8d3b54ebfdeb14ca491f6))
|
||||
|
||||
## [1.2.4](https://github.com/James-Frowen/SimpleWebTransport/compare/v1.2.3...v1.2.4) (2021-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adding meta file for changelog ([ba5b164](https://github.com/James-Frowen/SimpleWebTransport/commit/ba5b1647aa5cc69ca80f5b52c542a9b5ee749c7f))
|
||||
|
||||
## [1.2.3](https://github.com/James-Frowen/SimpleWebTransport/compare/v1.2.2...v1.2.3) (2021-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixing compile error in assemblyInfo ([7ee8380](https://github.com/James-Frowen/SimpleWebTransport/commit/7ee8380b4daf34d4e12017de55d8be481690046f))
|
||||
|
||||
## [1.2.2](https://github.com/James-Frowen/SimpleWebTransport/compare/v1.2.1...v1.2.2) (2021-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixing release with empty commit ([068af74](https://github.com/James-Frowen/SimpleWebTransport/commit/068af74f7399354081f25181f90fb060b0fa1524))
|
||||
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/CHANGELOG.md.meta
vendored
Normal file
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/CHANGELOG.md.meta
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0ef23ac1c6a62546bbad5529b3bfdad
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client.meta
vendored
Normal file
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5faa957b8d9fc314ab7596ccf14750d9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/ClientWebsocketSettings.cs
vendored
Normal file
17
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/ClientWebsocketSettings.cs
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
[Serializable]
|
||||
public struct ClientWebsocketSettings
|
||||
{
|
||||
public WebsocketPortOption ClientPortOption;
|
||||
public ushort CustomClientPort;
|
||||
}
|
||||
public enum WebsocketPortOption
|
||||
{
|
||||
DefaultSameAsServer,
|
||||
MatchWebpageProtocol,
|
||||
SpecifyPort
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20ed869c5ba54a56b750ac9e82be069f
|
||||
timeCreated: 1700425326
|
||||
103
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/SimpleWebClient.cs
vendored
Normal file
103
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/SimpleWebClient.cs
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
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
|
||||
{
|
||||
readonly int maxMessagesPerTick;
|
||||
|
||||
protected ClientState state;
|
||||
protected readonly int maxMessageSize;
|
||||
protected readonly BufferPool bufferPool;
|
||||
|
||||
public readonly ConcurrentQueue<Message> receiveQueue = new ConcurrentQueue<Message>();
|
||||
|
||||
public ClientState ConnectionState => state;
|
||||
|
||||
public event Action onConnect;
|
||||
public event Action onDisconnect;
|
||||
public event Action<ArraySegment<byte>> onData;
|
||||
public event Action<Exception> onError;
|
||||
|
||||
public abstract void Connect(Uri serverAddress);
|
||||
public abstract void Disconnect();
|
||||
public abstract void Send(ArraySegment<byte> segment);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
protected SimpleWebClient(int maxMessageSize, int maxMessagesPerTick)
|
||||
{
|
||||
this.maxMessageSize = maxMessageSize;
|
||||
this.maxMessagesPerTick = maxMessagesPerTick;
|
||||
bufferPool = new BufferPool(5, 20, maxMessageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes all new messages
|
||||
/// </summary>
|
||||
public void ProcessMessageQueue()
|
||||
{
|
||||
ProcessMessageQueue(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes all messages while <paramref name="behaviour"/> is enabled
|
||||
/// </summary>
|
||||
/// <param name="behaviour"></param>
|
||||
public void ProcessMessageQueue(MonoBehaviour behaviour)
|
||||
{
|
||||
int processedCount = 0;
|
||||
bool skipEnabled = behaviour == null;
|
||||
// check enabled every time in case behaviour was disabled after data
|
||||
while (
|
||||
(skipEnabled || 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;
|
||||
}
|
||||
}
|
||||
if (receiveQueue.Count > 0)
|
||||
Log.Warn("[SWT-SimpleWebClient]: ProcessMessageQueue has {0} remaining.", receiveQueue.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/SimpleWebClient.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/SimpleWebClient.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13131761a0bf5a64dadeccd700fe26e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/StandAlone.meta
vendored
Normal file
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/StandAlone.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9c19d05220a87c4cbbe4d1e422da0aa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
88
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/StandAlone/ClientHandshake.cs
vendored
Normal file
88
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/StandAlone/ClientHandshake.cs
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
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("[SWT-ClientHandshake]: Handshake Hashing {0}", Encoding.ASCII.GetString(keySumBytes));
|
||||
|
||||
// SHA-1 is the websocket standard:
|
||||
// https://www.rfc-editor.org/rfc/rfc6455
|
||||
// we should follow the standard, even though SHA1 is considered weak:
|
||||
// https://stackoverflow.com/questions/38038841/why-is-sha-1-considered-insecure
|
||||
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("[SWT-ClientHandshake]: Connection closed before handshake");
|
||||
return false;
|
||||
}
|
||||
|
||||
string responseString = Encoding.ASCII.GetString(responseBuffer, 0, lengthOrNull.Value);
|
||||
Log.Verbose("[SWT-ClientHandshake]: Handshake Response {0}", responseString);
|
||||
|
||||
string acceptHeader = "Sec-WebSocket-Accept: ";
|
||||
int startIndex = responseString.IndexOf(acceptHeader, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
if (startIndex < 0)
|
||||
{
|
||||
Log.Error("[SWT-ClientHandshake]: Unexpected Handshake Response {0}", responseString);
|
||||
return false;
|
||||
}
|
||||
|
||||
startIndex += acceptHeader.Length;
|
||||
int endIndex = responseString.IndexOf("\r\n", startIndex);
|
||||
string responseKey = responseString.Substring(startIndex, endIndex - startIndex);
|
||||
|
||||
if (responseKey != expectedResponse)
|
||||
{
|
||||
Log.Error("[SWT-ClientHandshake]: Response key incorrect\nExpected:{0}\nResponse:{1}\nThis can happen if Websocket Protocol is not installed in Windows Server Roles.", expectedResponse, responseKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ffdcabc9e28f764a94fc4efc82d3e8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
47
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/StandAlone/ClientSslHelper.cs
vendored
Normal file
47
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/StandAlone/ClientSslHelper.cs
vendored
Normal 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" && uri.Scheme != "https")
|
||||
{
|
||||
conn.stream = stream;
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
conn.stream = CreateStream(stream, uri);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("[SWT-ClientSslHelper]: Create SSLStream Failed: {0}\n{1}\n\n", e.Message, e.StackTrace);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46055a75559a79849a750f39a766db61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,140 @@
|
||||
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;
|
||||
|
||||
// create connection here before thread so that send queue exist before connected
|
||||
TcpClient client = new TcpClient();
|
||||
tcpConfig.ApplyTo(client);
|
||||
|
||||
// create connection object here so dispose correctly disconnects on failed connect
|
||||
conn = new Connection(client, AfterConnectionDisposed);
|
||||
|
||||
Thread receiveThread = new Thread(() => ConnectAndReceiveLoop(serverAddress));
|
||||
receiveThread.IsBackground = true;
|
||||
receiveThread.Start();
|
||||
}
|
||||
|
||||
void ConnectAndReceiveLoop(Uri serverAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
// connection created above
|
||||
TcpClient client = conn.client;
|
||||
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("[SWT-WebSocketClientStandAlone]: Failed to create Stream with {0}", serverAddress);
|
||||
conn.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
success = handshake.TryHandshake(conn, serverAddress);
|
||||
if (!success)
|
||||
{
|
||||
Log.Warn("[SWT-WebSocketClientStandAlone]: Failed Handshake with {0}", serverAddress);
|
||||
conn.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info("[SWT-WebSocketClientStandAlone]: HandShake Successful with {0}", serverAddress);
|
||||
|
||||
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) { Log.Error("[SWT-WebSocketClientStandAlone]: Thread Abort Exception"); }
|
||||
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.Verbose("[SWT-WebSocketClientStandAlone]: 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05a9c87dea309e241a9185e5aa0d72ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl.meta
vendored
Normal file
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7142349d566213c4abc763afaf4d91a1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/SimpleWebJSLib.cs
vendored
Normal file
34
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/SimpleWebJSLib.cs
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/SimpleWebJSLib.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/SimpleWebJSLib.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97b96a0b65c104443977473323c2ff35
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
142
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/WebSocketClientWebGl.cs
vendored
Normal file
142
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/WebSocketClientWebGl.cs
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AOT;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
#if !UNITY_2021_3_OR_NEWER
|
||||
|
||||
// Unity 2019 doesn't have ArraySegment.ToArray() yet.
|
||||
public static class Extensions
|
||||
{
|
||||
public static byte[] ToArray(this ArraySegment<byte> segment)
|
||||
{
|
||||
byte[] array = new byte[segment.Count];
|
||||
Array.Copy(segment.Array, segment.Offset, array, 0, segment.Count);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public class WebSocketClientWebGl : SimpleWebClient
|
||||
{
|
||||
static readonly Dictionary<int, WebSocketClientWebGl> instances = new Dictionary<int, WebSocketClientWebGl>();
|
||||
|
||||
[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();
|
||||
|
||||
/// <summary>
|
||||
/// key for instances sent between c# and js
|
||||
/// </summary>
|
||||
int index;
|
||||
|
||||
/// <summary>
|
||||
/// Queue for messages sent by high level while still connecting, they will be sent after onOpen is called.
|
||||
/// <para>
|
||||
/// This is a workaround for anything that calls Send immediately after Connect.
|
||||
/// Without this the JS websocket will give errors.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
Queue<byte[]> ConnectingSendQueue;
|
||||
|
||||
public bool CheckJsConnected() => SimpleWebJSLib.IsConnected(index);
|
||||
|
||||
internal WebSocketClientWebGl(int maxMessageSize, int maxMessagesPerTick) : base(maxMessageSize, maxMessagesPerTick)
|
||||
{
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
throw new NotSupportedException();
|
||||
#endif
|
||||
}
|
||||
|
||||
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("[SWT-WebSocketClientWebGl]: Cant send message with length {0} because it is over the max size of {1}", segment.Count, maxMessageSize);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == ClientState.Connected)
|
||||
{
|
||||
SimpleWebJSLib.Send(index, segment.Array, segment.Offset, segment.Count);
|
||||
}
|
||||
else if (ConnectingSendQueue == null)
|
||||
{
|
||||
ConnectingSendQueue = new Queue<byte[]>();
|
||||
ConnectingSendQueue.Enqueue(segment.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
void onOpen()
|
||||
{
|
||||
receiveQueue.Enqueue(new Message(EventType.Connected));
|
||||
state = ClientState.Connected;
|
||||
|
||||
if (ConnectingSendQueue != null)
|
||||
{
|
||||
while (ConnectingSendQueue.Count > 0)
|
||||
{
|
||||
byte[] next = ConnectingSendQueue.Dequeue();
|
||||
SimpleWebJSLib.Send(index, next, 0, next.Length);
|
||||
}
|
||||
|
||||
ConnectingSendQueue = null;
|
||||
}
|
||||
}
|
||||
|
||||
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("[SWT-WebSocketClientWebGl]: onMessage {0}: {1}\n{2}", e.GetType(), e.Message, e.StackTrace);
|
||||
receiveQueue.Enqueue(new Message(e));
|
||||
}
|
||||
}
|
||||
|
||||
void onErr()
|
||||
{
|
||||
receiveQueue.Enqueue(new Message(new Exception("Javascript Websocket error")));
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 015c5b1915fd1a64cbe36444d16b2f7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/plugin.meta
vendored
Normal file
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/plugin.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1999985791b91b9458059e88404885a7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
123
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/plugin/SimpleWeb.jslib
vendored
Normal file
123
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/plugin/SimpleWeb.jslib
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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)
|
||||
{
|
||||
// fix for unity 2021 because unity bug in .jslib
|
||||
if (typeof Runtime === "undefined")
|
||||
{
|
||||
// if unity doesn't create Runtime, then make it here
|
||||
// dont ask why this works, just be happy that it does
|
||||
var Runtime = { dynCall: dynCall }
|
||||
}
|
||||
|
||||
const address = UTF8ToString(addressPtr);
|
||||
console.log("Connecting to " + address);
|
||||
|
||||
// Create webSocket connection.
|
||||
var webSocket = new WebSocket(address);
|
||||
webSocket.binaryType = 'arraybuffer';
|
||||
|
||||
const index = SimpleWeb.AddNextSocket(webSocket);
|
||||
|
||||
// Connection opened
|
||||
webSocket.onopen = function(event)
|
||||
{
|
||||
console.log("Connected to " + address);
|
||||
Runtime.dynCall('vi', openCallbackPtr, [index]);
|
||||
};
|
||||
|
||||
webSocket.onclose = function(event)
|
||||
{
|
||||
console.log("Disconnected from " + address);
|
||||
Runtime.dynCall('vi', closeCallBackPtr, [index]);
|
||||
};
|
||||
|
||||
webSocket.onmessage = function(event)
|
||||
{
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
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.onerror = 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);
|
||||
37
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/plugin/SimpleWeb.jslib.meta
vendored
Normal file
37
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Client/Webgl/plugin/SimpleWeb.jslib.meta
vendored
Normal 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:
|
||||
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common.meta
vendored
Normal file
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 564d2cd3eee5b21419553c0528739d1b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
249
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/BufferPool.cs
vendored
Normal file
249
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/BufferPool.cs
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public interface IBufferOwner
|
||||
{
|
||||
void Return(ArrayBuffer buffer);
|
||||
}
|
||||
|
||||
public sealed class ArrayBuffer : IDisposable
|
||||
{
|
||||
readonly IBufferOwner owner;
|
||||
|
||||
public readonly byte[] array;
|
||||
|
||||
/// <summary>
|
||||
/// number of bytes written to buffer
|
||||
/// </summary>
|
||||
public int count { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// How many times release needs to be called before buffer is returned to pool
|
||||
/// <para>This allows the buffer to be used in multiple places at the same time</para>
|
||||
/// </summary>
|
||||
public void SetReleasesRequired(int required)
|
||||
{
|
||||
releasesRequired = required;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many times release needs to be called before buffer is returned to pool
|
||||
/// <para>This allows the buffer to be used in multiple places at the same time</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is normally 0, but can be changed to require release to be called multiple times
|
||||
/// </remarks>
|
||||
int releasesRequired;
|
||||
|
||||
public ArrayBuffer(IBufferOwner owner, int size)
|
||||
{
|
||||
this.owner = owner;
|
||||
array = new byte[size];
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
int newValue = Interlocked.Decrement(ref releasesRequired);
|
||||
if (newValue <= 0)
|
||||
{
|
||||
count = 0;
|
||||
owner?.Return(this);
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Release();
|
||||
}
|
||||
|
||||
public void CopyTo(byte[] target, int offset)
|
||||
{
|
||||
if (count > (target.Length + offset))
|
||||
throw new ArgumentException($"{nameof(count)} was greater than {nameof(target)}.length", nameof(target));
|
||||
|
||||
Buffer.BlockCopy(array, 0, target, offset, count);
|
||||
}
|
||||
|
||||
public void CopyFrom(ArraySegment<byte> segment)
|
||||
{
|
||||
CopyFrom(segment.Array, segment.Offset, segment.Count);
|
||||
}
|
||||
|
||||
public void CopyFrom(byte[] source, int offset, int length)
|
||||
{
|
||||
if (length > array.Length)
|
||||
throw new ArgumentException($"{nameof(length)} was greater than {nameof(array)}.length", nameof(length));
|
||||
|
||||
count = length;
|
||||
Buffer.BlockCopy(source, offset, array, 0, length);
|
||||
}
|
||||
|
||||
public void CopyFrom(IntPtr bufferPtr, int length)
|
||||
{
|
||||
if (length > array.Length)
|
||||
throw new ArgumentException($"{nameof(length)} was greater than {nameof(array)}.length", nameof(length));
|
||||
|
||||
count = length;
|
||||
Marshal.Copy(bufferPtr, array, 0, length);
|
||||
}
|
||||
|
||||
public ArraySegment<byte> ToSegment() => new ArraySegment<byte>(array, 0, count);
|
||||
|
||||
[Conditional("UNITY_ASSERTIONS")]
|
||||
internal void Validate(int arraySize)
|
||||
{
|
||||
if (array.Length != arraySize)
|
||||
Log.Error("[SWT-ArrayBuffer]: Buffer that was returned had an array of the wrong size");
|
||||
}
|
||||
}
|
||||
|
||||
internal class BufferBucket : IBufferOwner
|
||||
{
|
||||
public readonly int arraySize;
|
||||
readonly ConcurrentQueue<ArrayBuffer> buffers;
|
||||
|
||||
/// <summary>
|
||||
/// keeps track of how many arrays are taken vs returned
|
||||
/// </summary>
|
||||
internal int _current = 0;
|
||||
|
||||
public BufferBucket(int arraySize)
|
||||
{
|
||||
this.arraySize = arraySize;
|
||||
buffers = new ConcurrentQueue<ArrayBuffer>();
|
||||
}
|
||||
|
||||
public ArrayBuffer Take()
|
||||
{
|
||||
IncrementCreated();
|
||||
if (buffers.TryDequeue(out ArrayBuffer buffer))
|
||||
return buffer;
|
||||
else
|
||||
{
|
||||
Log.Flood($"[SWT-BufferBucket]: BufferBucket({arraySize}) create new");
|
||||
return new ArrayBuffer(this, arraySize);
|
||||
}
|
||||
}
|
||||
|
||||
public void Return(ArrayBuffer buffer)
|
||||
{
|
||||
DecrementCreated();
|
||||
buffer.Validate(arraySize);
|
||||
buffers.Enqueue(buffer);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
void IncrementCreated()
|
||||
{
|
||||
int next = Interlocked.Increment(ref _current);
|
||||
Log.Flood($"[SWT-BufferBucket]: BufferBucket({arraySize}) count:{next}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
void DecrementCreated()
|
||||
{
|
||||
int next = Interlocked.Decrement(ref _current);
|
||||
Log.Flood($"[SWT-BufferBucket]: BufferBucket({arraySize}) count:{next}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection of different sized buffers
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Problem: <br/>
|
||||
/// * Need to cached byte[] so that new ones aren't created each time <br/>
|
||||
/// * Arrays sent are multiple different sizes <br/>
|
||||
/// * Some message might be big so need buffers to cover that size <br/>
|
||||
/// * Most messages will be small compared to max message size <br/>
|
||||
/// </para>
|
||||
/// <br/>
|
||||
/// <para>
|
||||
/// Solution: <br/>
|
||||
/// * Create multiple groups of buffers covering the range of allowed sizes <br/>
|
||||
/// * Split range exponentially (using math.log) so that there are more groups for small buffers <br/>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class BufferPool
|
||||
{
|
||||
internal readonly BufferBucket[] buckets;
|
||||
readonly int bucketCount;
|
||||
readonly int smallest;
|
||||
readonly int largest;
|
||||
|
||||
public BufferPool(int bucketCount, int smallest, int largest)
|
||||
{
|
||||
if (bucketCount < 2) throw new ArgumentException("Count must be at least 2");
|
||||
if (smallest < 1) throw new ArgumentException("Smallest must be at least 1");
|
||||
if (largest < smallest) throw new ArgumentException("Largest must be greater than smallest");
|
||||
|
||||
this.bucketCount = bucketCount;
|
||||
this.smallest = smallest;
|
||||
this.largest = largest;
|
||||
|
||||
// split range over log scale (more buckets for smaller sizes)
|
||||
double minLog = Math.Log(this.smallest);
|
||||
double maxLog = Math.Log(this.largest);
|
||||
double range = maxLog - minLog;
|
||||
double each = range / (bucketCount - 1);
|
||||
|
||||
buckets = new BufferBucket[bucketCount];
|
||||
|
||||
for (int i = 0; i < bucketCount; i++)
|
||||
{
|
||||
double size = smallest * Math.Pow(Math.E, each * i);
|
||||
buckets[i] = new BufferBucket((int)Math.Ceiling(size));
|
||||
}
|
||||
|
||||
Validate();
|
||||
|
||||
// Example
|
||||
// 5 count
|
||||
// 20 smallest
|
||||
// 16400 largest
|
||||
|
||||
// 3.0 log 20
|
||||
// 9.7 log 16400
|
||||
|
||||
// 6.7 range 9.7 - 3
|
||||
// 1.675 each 6.7 / (5-1)
|
||||
|
||||
// 20 e^ (3 + 1.675 * 0)
|
||||
// 107 e^ (3 + 1.675 * 1)
|
||||
// 572 e^ (3 + 1.675 * 2)
|
||||
// 3056 e^ (3 + 1.675 * 3)
|
||||
// 16,317 e^ (3 + 1.675 * 4)
|
||||
|
||||
// precision wont be lose when using doubles
|
||||
}
|
||||
|
||||
[Conditional("UNITY_ASSERTIONS")]
|
||||
void Validate()
|
||||
{
|
||||
if (buckets[0].arraySize != smallest)
|
||||
Log.Error("[SWT-BufferPool]: BufferPool Failed to create bucket for smallest. bucket:{0} smallest:{1}", buckets[0].arraySize, smallest);
|
||||
|
||||
int largestBucket = buckets[bucketCount - 1].arraySize;
|
||||
// rounded using Ceiling, so allowed to be 1 more that largest
|
||||
if (largestBucket != largest && largestBucket != largest + 1)
|
||||
Log.Error("[SWT-BufferPool]: BufferPool Failed to create bucket for largest. bucket:{0} smallest:{1}", largestBucket, largest);
|
||||
}
|
||||
|
||||
public ArrayBuffer Take(int size)
|
||||
{
|
||||
if (size > largest)
|
||||
throw new ArgumentException($"Size ({size}) is greater than largest ({largest})");
|
||||
|
||||
for (int i = 0; i < bucketCount; i++)
|
||||
if (size <= buckets[i].arraySize)
|
||||
return buckets[i].Take();
|
||||
|
||||
throw new ArgumentException($"Size ({size}) is greater than largest ({largest})");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/BufferPool.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/BufferPool.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94ae50f3ec35667469b861b12cd72f92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
134
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Connection.cs
vendored
Normal file
134
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Connection.cs
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
internal sealed class Connection : IDisposable
|
||||
{
|
||||
readonly object disposedLock = new object();
|
||||
|
||||
public const int IdNotSet = -1;
|
||||
public TcpClient client;
|
||||
public int connId = IdNotSet;
|
||||
|
||||
/// <summary>
|
||||
/// Connect request, sent from client to start handshake
|
||||
/// <para>Only valid on server</para>
|
||||
/// </summary>
|
||||
public Request request;
|
||||
/// <summary>
|
||||
/// RemoteEndpoint address or address from request header
|
||||
/// <para>Only valid on server</para>
|
||||
/// </summary>
|
||||
public string remoteAddress;
|
||||
|
||||
public Stream stream;
|
||||
public Thread receiveThread;
|
||||
public Thread sendThread;
|
||||
|
||||
public ManualResetEventSlim sendPending = new ManualResetEventSlim(false);
|
||||
public ConcurrentQueue<ArrayBuffer> sendQueue = new ConcurrentQueue<ArrayBuffer>();
|
||||
|
||||
public Action<Connection> onDispose;
|
||||
volatile bool hasDisposed;
|
||||
|
||||
public Connection(TcpClient client, Action<Connection> onDispose)
|
||||
{
|
||||
this.client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
this.onDispose = onDispose;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// disposes client and stops threads
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Log.Verbose("[SWT-Connection]: Dispose {0}", ToString());
|
||||
|
||||
// check hasDisposed first to stop ThreadInterruptedException on lock
|
||||
if (hasDisposed) return;
|
||||
|
||||
Log.Verbose("[SWT-Connection]: Connection Close: {0}", ToString());
|
||||
|
||||
lock (disposedLock)
|
||||
{
|
||||
// check hasDisposed again inside lock to make sure no other object has called this
|
||||
if (hasDisposed) return;
|
||||
|
||||
hasDisposed = true;
|
||||
|
||||
// stop threads first so they don't try to use disposed objects
|
||||
receiveThread.Interrupt();
|
||||
sendThread?.Interrupt();
|
||||
|
||||
try
|
||||
{
|
||||
// stream
|
||||
stream?.Dispose();
|
||||
stream = null;
|
||||
client.Dispose();
|
||||
client = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
|
||||
sendPending.Dispose();
|
||||
|
||||
// release all buffers in send queue
|
||||
while (sendQueue.TryDequeue(out ArrayBuffer buffer))
|
||||
buffer.Release();
|
||||
|
||||
onDispose.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// remoteAddress isn't set until after handshake
|
||||
if (hasDisposed)
|
||||
return $"[Conn:{connId}, Disposed]";
|
||||
else if (!string.IsNullOrWhiteSpace(remoteAddress))
|
||||
return $"[Conn:{connId}, endPoint:{remoteAddress}]";
|
||||
else
|
||||
try
|
||||
{
|
||||
EndPoint endpoint = client?.Client?.RemoteEndPoint;
|
||||
return $"[Conn:{connId}, endPoint:{endpoint}]";
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return $"[Conn:{connId}, endPoint:n/a]";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address based on the <see cref="request"/> and RemoteEndPoint
|
||||
/// <para>Called after ServerHandShake is accepted</para>
|
||||
/// </summary>
|
||||
internal string CalculateAddress()
|
||||
{
|
||||
if (request.Headers.TryGetValue("X-Forwarded-For", out string forwardFor))
|
||||
{
|
||||
string actualClientIP = forwardFor.ToString().Split(',').First();
|
||||
// Remove the port number from the address
|
||||
return actualClientIP.Split(':').First();
|
||||
}
|
||||
else
|
||||
{
|
||||
IPEndPoint ipEndPoint = (IPEndPoint)client.Client.RemoteEndPoint;
|
||||
IPAddress ipAddress = ipEndPoint.Address;
|
||||
if (ipAddress.IsIPv4MappedToIPv6)
|
||||
ipAddress = ipAddress.MapToIPv4();
|
||||
|
||||
return ipAddress.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Connection.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Connection.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a13073c2b49d39943888df45174851bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
76
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Constants.cs
vendored
Normal file
76
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Constants.cs
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant values that should never change
|
||||
/// <para>
|
||||
/// Some values are from https://tools.ietf.org/html/rfc6455
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Header is at most 4 bytes
|
||||
/// <para>
|
||||
/// If message is less than 125 then header is 2 bytes, else header is 4 bytes
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public const int HeaderSize = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Smallest size of header
|
||||
/// <para>
|
||||
/// If message is less than 125 then header is 2 bytes, else header is 4 bytes
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public const int HeaderMinSize = 2;
|
||||
|
||||
/// <summary>
|
||||
/// bytes for short length
|
||||
/// </summary>
|
||||
public const int ShortLength = 2;
|
||||
|
||||
/// <summary>
|
||||
/// bytes for long length
|
||||
/// </summary>
|
||||
public const int LongLength = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Message mask is always 4 bytes
|
||||
/// </summary>
|
||||
public const int MaskSize = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Max size of a message for length to be 1 byte long
|
||||
/// <para>
|
||||
/// payload length between 0-125
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public const int BytePayloadLength = 125;
|
||||
|
||||
/// <summary>
|
||||
/// if payload length is 126 when next 2 bytes will be the length
|
||||
/// </summary>
|
||||
public const int UshortPayloadLength = 126;
|
||||
|
||||
/// <summary>
|
||||
/// if payload length is 127 when next 8 bytes will be the length
|
||||
/// </summary>
|
||||
public const int UlongPayloadLength = 127;
|
||||
|
||||
/// <summary>
|
||||
/// Guid used for WebSocket Protocol
|
||||
/// </summary>
|
||||
public const string HandshakeGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
public static readonly int HandshakeGUIDLength = HandshakeGUID.Length;
|
||||
|
||||
public static readonly byte[] HandshakeGUIDBytes = Encoding.ASCII.GetBytes(HandshakeGUID);
|
||||
|
||||
/// <summary>
|
||||
/// Handshake messages will end with \r\n\r\n
|
||||
/// </summary>
|
||||
public static readonly byte[] endOfHandshake = new byte[4] { (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Constants.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Constants.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85d110a089d6ad348abf2d073ebce7cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
10
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/EventType.cs
vendored
Normal file
10
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/EventType.cs
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public enum EventType
|
||||
{
|
||||
Connected,
|
||||
Data,
|
||||
Disconnected,
|
||||
Error
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/EventType.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/EventType.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d9cd7d2b5229ab42a12e82ae17d0347
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
290
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Log.cs
vendored
Normal file
290
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Log.cs
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Conditional = System.Diagnostics.ConditionalAttribute;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public static class Log
|
||||
{
|
||||
// The.NET console color names map to the following approximate CSS color names:
|
||||
|
||||
// Black: Black
|
||||
// Blue: Blue
|
||||
// Cyan: Aqua or Cyan
|
||||
// DarkBlue: DarkBlue
|
||||
// DarkCyan: DarkCyan
|
||||
// DarkGray: DarkGray
|
||||
// DarkGreen: DarkGreen
|
||||
// DarkMagenta: DarkMagenta
|
||||
// DarkRed: DarkRed
|
||||
// DarkYellow: DarkOrange or DarkGoldenRod
|
||||
// Gray: Gray
|
||||
// Green: Green
|
||||
// Magenta: Magenta
|
||||
// Red: Red
|
||||
// White: White
|
||||
// Yellow: Yellow
|
||||
|
||||
// We can't use colors that are close to white or black because
|
||||
// they won't show up well in the server console or browser console
|
||||
|
||||
public enum Levels
|
||||
{
|
||||
Flood,
|
||||
Verbose,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
None
|
||||
}
|
||||
|
||||
public static ILogger logger = Debug.unityLogger;
|
||||
public static Levels minLogLevel = Levels.None;
|
||||
|
||||
/// <summary>
|
||||
/// Logs all exceptions to console
|
||||
/// </summary>
|
||||
/// <param name="e">Exception to log</param>
|
||||
public static void Exception(Exception e)
|
||||
{
|
||||
string timeStamp = $"[{DateTime.Now:HH:mm:ss}]";
|
||||
|
||||
#if UNITY_SERVER || UNITY_WEBGL
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"{timeStamp} [SWT:Exception] {e.GetType().Name}: {e.Message}\n{e.StackTrace}\n\n");
|
||||
Console.ResetColor();
|
||||
#else
|
||||
logger.Log(LogType.Exception, $"{timeStamp} [SWT:Exception] {e.GetType().Name}: {e.Message}\n{e.StackTrace}\n\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs flood to console if minLogLevel is set to Flood or lower
|
||||
/// </summary>
|
||||
/// <param name="msg">Message text to log</param>
|
||||
[Conditional("DEBUG")]
|
||||
public static void Flood(string msg)
|
||||
{
|
||||
if (minLogLevel > Levels.Flood) return;
|
||||
|
||||
string timedMessage = $"[{DateTime.Now:HH:mm:ss}] {msg.Trim()}";
|
||||
|
||||
#if UNITY_SERVER || UNITY_WEBGL
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
logger.Log(LogType.Log, timedMessage);
|
||||
Console.ResetColor();
|
||||
#else
|
||||
logger.Log(LogType.Log, timedMessage);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs buffer to console if minLogLevel is set to Flood or lower
|
||||
/// <para>Debug mode requrired, e.g. Unity Editor of Develpment Build</para>
|
||||
/// </summary>
|
||||
/// <param name="label">Source of the log message</param>
|
||||
/// <param name="buffer">Byte array to be logged</param>
|
||||
/// <param name="offset">starting point of byte array</param>
|
||||
/// <param name="length">number of bytes to read</param>
|
||||
[Conditional("DEBUG")]
|
||||
public static void DumpBuffer(string label, byte[] buffer, int offset, int length)
|
||||
{
|
||||
if (minLogLevel > Levels.Flood) return;
|
||||
|
||||
string timeStamp = $"[{DateTime.Now:HH:mm:ss}]";
|
||||
|
||||
#if UNITY_SERVER || UNITY_WEBGL
|
||||
Console.ForegroundColor = ConsoleColor.DarkBlue;
|
||||
logger.Log(LogType.Log, $"{timeStamp} {label}: {BufferToString(buffer, offset, length)}");
|
||||
Console.ResetColor();
|
||||
#else
|
||||
logger.Log(LogType.Log, $"<color=cyan>{timeStamp} {label}: {BufferToString(buffer, offset, length)}</color>");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs buffer to console if minLogLevel is set to Flood or lower
|
||||
/// <para>Debug mode requrired, e.g. Unity Editor of Develpment Build</para>
|
||||
/// </summary>
|
||||
/// <param name="label">Source of the log message</param>
|
||||
/// <param name="arrayBuffer">ArrayBuffer to show details for</param>
|
||||
[Conditional("DEBUG")]
|
||||
public static void DumpBuffer(string label, ArrayBuffer arrayBuffer)
|
||||
{
|
||||
if (minLogLevel > Levels.Flood) return;
|
||||
|
||||
string timeStamp = $"[{DateTime.Now:HH:mm:ss}]";
|
||||
|
||||
#if UNITY_SERVER || UNITY_WEBGL
|
||||
Console.ForegroundColor = ConsoleColor.DarkBlue;
|
||||
logger.Log(LogType.Log, $"{timeStamp} {label}: {BufferToString(arrayBuffer.array, 0, arrayBuffer.count)}");
|
||||
Console.ResetColor();
|
||||
#else
|
||||
logger.Log(LogType.Log, $"<color=cyan>{timeStamp} {label}: {BufferToString(arrayBuffer.array, 0, arrayBuffer.count)}</color>");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs verbose to console if minLogLevel is set to Verbose or lower
|
||||
/// </summary>
|
||||
/// <param name="msg">Message text to log</param>
|
||||
public static void Verbose(string msg)
|
||||
{
|
||||
if (minLogLevel > Levels.Verbose) return;
|
||||
|
||||
string timedMessage = $"[{DateTime.Now:HH:mm:ss}] {msg.Trim()}";
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Log, timedMessage);
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
Console.WriteLine(timedMessage);
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Verbose<T>(string msg, T arg1)
|
||||
{
|
||||
if (minLogLevel > Levels.Verbose) return;
|
||||
Verbose(String.Format(msg, arg1));
|
||||
}
|
||||
|
||||
public static void Verbose<T1, T2>(string msg, T1 arg1, T2 arg2)
|
||||
{
|
||||
if (minLogLevel > Levels.Verbose) return;
|
||||
Verbose(String.Format(msg, arg1, arg2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs info to console if minLogLevel is set to Info or lower
|
||||
/// </summary>
|
||||
/// <param name="msg">Message text to log</param>
|
||||
/// <param name="consoleColor">Default Cyan works in server and browser consoles</param>
|
||||
static void Info(string msg, ConsoleColor consoleColor = ConsoleColor.Cyan)
|
||||
{
|
||||
if (minLogLevel > Levels.Info) return;
|
||||
|
||||
string timedMessage = $"[{DateTime.Now:HH:mm:ss}] {msg.Trim()}";
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Log, timedMessage);
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = consoleColor;
|
||||
Console.WriteLine(timedMessage);
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Info<T>(string msg, T arg1, ConsoleColor consoleColor = ConsoleColor.Cyan)
|
||||
{
|
||||
if (minLogLevel > Levels.Info) return;
|
||||
Info(String.Format(msg, arg1), consoleColor);
|
||||
}
|
||||
|
||||
public static void Info<T1, T2>(string msg, T1 arg1, T2 arg2, ConsoleColor consoleColor = ConsoleColor.Cyan)
|
||||
{
|
||||
if (minLogLevel > Levels.Info) return;
|
||||
Info(String.Format(msg, arg1, arg2), consoleColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs info to console if minLogLevel is set to Info or lower
|
||||
/// </summary>
|
||||
/// <param name="e">Exception to log</param>
|
||||
public static void InfoException(Exception e)
|
||||
{
|
||||
if (minLogLevel > Levels.Info) return;
|
||||
|
||||
string timedMessage = $"[{DateTime.Now:HH:mm:ss}] {e.Message}";
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Exception, timedMessage);
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = ConsoleColor.DarkRed;
|
||||
Console.WriteLine(timedMessage);
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs info to console if minLogLevel is set to Warn or lower
|
||||
/// </summary>
|
||||
/// <param name="msg">Message text to log</param>
|
||||
public static void Warn(string msg)
|
||||
{
|
||||
if (minLogLevel > Levels.Warn) return;
|
||||
|
||||
string timedMessage = $"[{DateTime.Now:HH:mm:ss}] {msg.Trim()}";
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Warning, timedMessage);
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(timedMessage);
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Warn<T>(string msg, T arg1)
|
||||
{
|
||||
if (minLogLevel > Levels.Warn) return;
|
||||
Warn(String.Format(msg, arg1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs info to console if minLogLevel is set to Error or lower
|
||||
/// </summary>
|
||||
/// <param name="msg">Message text to log</param>
|
||||
public static void Error(string msg)
|
||||
{
|
||||
if (minLogLevel > Levels.Error) return;
|
||||
|
||||
string timedMessage = $"[{DateTime.Now:HH:mm:ss}] {msg.Trim()}";
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Error, timedMessage);
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(timedMessage);
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Error<T>(string msg, T arg1)
|
||||
{
|
||||
if (minLogLevel > Levels.Error) return;
|
||||
Error(String.Format(msg, arg1));
|
||||
}
|
||||
|
||||
public static void Error<T1, T2>(string msg, T1 arg1, T2 arg2)
|
||||
{
|
||||
if (minLogLevel > Levels.Error) return;
|
||||
Error(String.Format(msg, arg1, arg2));
|
||||
}
|
||||
|
||||
public static void Error<T1, T2, T3>(string msg, T1 arg1, T2 arg2, T3 arg3)
|
||||
{
|
||||
if (minLogLevel > Levels.Error) return;
|
||||
Error(String.Format(msg, arg1, arg2, arg3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the byte array starting from offset for length bytes
|
||||
/// </summary>
|
||||
/// <param name="buffer">Byte array to read</param>
|
||||
/// <param name="offset">starting point in the byte array</param>
|
||||
/// <param name="length">number of bytes to read from offset</param>
|
||||
/// <returns></returns>
|
||||
public static string BufferToString(byte[] buffer, int offset = 0, int? length = null) => BitConverter.ToString(buffer, offset, length ?? buffer.Length);
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Log.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Log.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cf1521098e04f74fbea0fe2aa0439f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
49
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Message.cs
vendored
Normal file
49
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Message.cs
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public struct Message
|
||||
{
|
||||
public readonly int connId;
|
||||
public readonly EventType type;
|
||||
public readonly ArrayBuffer data;
|
||||
public readonly Exception exception;
|
||||
|
||||
public Message(EventType type) : this()
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Message(ArrayBuffer data) : this()
|
||||
{
|
||||
type = EventType.Data;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Message(Exception exception) : this()
|
||||
{
|
||||
type = EventType.Error;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
public Message(int connId, EventType type) : this()
|
||||
{
|
||||
this.connId = connId;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Message(int connId, ArrayBuffer data) : this()
|
||||
{
|
||||
this.connId = connId;
|
||||
type = EventType.Data;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Message(int connId, Exception exception) : this()
|
||||
{
|
||||
this.connId = connId;
|
||||
type = EventType.Error;
|
||||
this.exception = exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Message.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Message.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5d05d71b09d2714b96ffe80bc3d2a77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
175
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/MessageProcessor.cs
vendored
Normal file
175
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/MessageProcessor.cs
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public static class MessageProcessor
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static byte FirstLengthByte(byte[] buffer) => (byte)(buffer[1] & 0b0111_1111);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool NeedToReadShortLength(byte[] buffer)
|
||||
{
|
||||
byte lenByte = FirstLengthByte(buffer);
|
||||
return lenByte == Constants.UshortPayloadLength;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool NeedToReadLongLength(byte[] buffer)
|
||||
{
|
||||
byte lenByte = FirstLengthByte(buffer);
|
||||
return lenByte == Constants.UlongPayloadLength;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetOpcode(byte[] buffer) => buffer[0] & 0b0000_1111;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetPayloadLength(byte[] buffer) => GetMessageLength(buffer, 0, FirstLengthByte(buffer));
|
||||
|
||||
/// <summary>
|
||||
/// Has full message been sent
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Finished(byte[] buffer) => (buffer[0] & 0b1000_0000) != 0;
|
||||
|
||||
public static void ValidateHeader(byte[] buffer, int maxLength, bool expectMask, bool opCodeContinuation = false)
|
||||
{
|
||||
bool finished = Finished(buffer);
|
||||
bool hasMask = (buffer[1] & 0b1000_0000) != 0; // true from clients, false from server, "All messages from the client to the server have this bit set"
|
||||
|
||||
int opcode = buffer[0] & 0b0000_1111; // expecting 1 - text message
|
||||
byte lenByte = FirstLengthByte(buffer);
|
||||
|
||||
ThrowIfMaskNotExpected(hasMask, expectMask);
|
||||
ThrowIfBadOpCode(opcode, finished, opCodeContinuation);
|
||||
|
||||
int msglen = GetMessageLength(buffer, 0, lenByte);
|
||||
|
||||
ThrowIfLengthZero(msglen);
|
||||
ThrowIfMsgLengthTooLong(msglen, maxLength);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToggleMask(byte[] src, int sourceOffset, int messageLength, byte[] maskBuffer, int maskOffset)
|
||||
{
|
||||
ToggleMask(src, sourceOffset, src, sourceOffset, messageLength, maskBuffer, maskOffset);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToggleMask(byte[] src, int sourceOffset, ArrayBuffer dst, int messageLength, byte[] maskBuffer, int maskOffset)
|
||||
{
|
||||
ToggleMask(src, sourceOffset, dst.array, 0, messageLength, maskBuffer, maskOffset);
|
||||
dst.count = messageLength;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToggleMask(byte[] src, int srcOffset, byte[] dst, int dstOffset, int messageLength, byte[] maskBuffer, int maskOffset)
|
||||
{
|
||||
for (int i = 0; i < messageLength; i++)
|
||||
{
|
||||
byte maskByte = maskBuffer[maskOffset + i % Constants.MaskSize];
|
||||
dst[dstOffset + i] = (byte)(src[srcOffset + i] ^ maskByte);
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
static int GetMessageLength(byte[] buffer, int offset, byte lenByte)
|
||||
{
|
||||
if (lenByte == Constants.UshortPayloadLength)
|
||||
{
|
||||
// header is 2 bytes
|
||||
ushort value = 0;
|
||||
value |= (ushort)(buffer[offset + 2] << 8);
|
||||
value |= buffer[offset + 3];
|
||||
|
||||
return value;
|
||||
}
|
||||
else if (lenByte == Constants.UlongPayloadLength)
|
||||
{
|
||||
// header is 8 bytes
|
||||
ulong value = 0;
|
||||
value |= ((ulong)buffer[offset + 2] << 56);
|
||||
value |= ((ulong)buffer[offset + 3] << 48);
|
||||
value |= ((ulong)buffer[offset + 4] << 40);
|
||||
value |= ((ulong)buffer[offset + 5] << 32);
|
||||
value |= ((ulong)buffer[offset + 6] << 24);
|
||||
value |= ((ulong)buffer[offset + 7] << 16);
|
||||
value |= ((ulong)buffer[offset + 8] << 8);
|
||||
value |= ((ulong)buffer[offset + 9] << 0);
|
||||
|
||||
if (value > int.MaxValue)
|
||||
throw new NotSupportedException($"Can't receive payloads larger that int.max: {int.MaxValue}");
|
||||
|
||||
return (int)value;
|
||||
}
|
||||
else // is less than 126
|
||||
{
|
||||
// header is 2 bytes long
|
||||
return lenByte;
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
static void ThrowIfMaskNotExpected(bool hasMask, bool expectMask)
|
||||
{
|
||||
if (hasMask != expectMask)
|
||||
throw new InvalidDataException($"Message expected mask to be {expectMask} but was {hasMask}");
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
static void ThrowIfBadOpCode(int opcode, bool finished, bool opCodeContinuation)
|
||||
{
|
||||
// 0 = continuation
|
||||
// 2 = binary
|
||||
// 8 = close
|
||||
|
||||
// do we expect Continuation?
|
||||
if (opCodeContinuation)
|
||||
{
|
||||
// good it was Continuation
|
||||
if (opcode == 0)
|
||||
return;
|
||||
|
||||
// bad, wasn't Continuation
|
||||
throw new InvalidDataException("Expected opcode to be Continuation");
|
||||
}
|
||||
else if (!finished)
|
||||
{
|
||||
// fragmented message, should be binary
|
||||
if (opcode == 2)
|
||||
return;
|
||||
|
||||
throw new InvalidDataException("Expected opcode to be binary");
|
||||
}
|
||||
else
|
||||
{
|
||||
// normal message, should be binary or close
|
||||
if (opcode == 2 || opcode == 8)
|
||||
return;
|
||||
|
||||
throw new InvalidDataException("Expected opcode to be binary or close");
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
static void ThrowIfLengthZero(int msglen)
|
||||
{
|
||||
if (msglen == 0)
|
||||
throw new InvalidDataException("Message length was zero");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// need to check this so that data from previous buffer isn't used
|
||||
/// </summary>
|
||||
public static void ThrowIfMsgLengthTooLong(int msglen, int maxLength)
|
||||
{
|
||||
if (msglen > maxLength)
|
||||
throw new InvalidDataException("Message length is greater than max length");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/MessageProcessor.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/MessageProcessor.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c1f218a2b16ca846aaf23260078e549
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
123
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/ReadHelper.cs
vendored
Normal file
123
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/ReadHelper.cs
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public static class ReadHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads exactly length from stream
|
||||
/// </summary>
|
||||
/// <returns>outOffset + length</returns>
|
||||
/// <exception cref="ReadHelperException"></exception>
|
||||
public static int Read(Stream stream, byte[] outBuffer, int outOffset, int length)
|
||||
{
|
||||
int received = 0;
|
||||
try
|
||||
{
|
||||
while (received < length)
|
||||
{
|
||||
int read = stream.Read(outBuffer, outOffset + received, length - received);
|
||||
if (read == 0)
|
||||
throw new ReadHelperException("[SWT-ReadHelper]: Read returned 0");
|
||||
|
||||
received += read;
|
||||
}
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
// if interrupt is called we don't care about Exceptions
|
||||
Utils.CheckForInterupt();
|
||||
|
||||
// rethrow
|
||||
ae.Handle(e => false);
|
||||
}
|
||||
|
||||
if (received != length)
|
||||
throw new ReadHelperException("[SWT-ReadHelper]: received not equal to length");
|
||||
|
||||
return outOffset + received;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns results. This should never throw an exception
|
||||
/// </summary>
|
||||
public static bool TryRead(Stream stream, byte[] outBuffer, int outOffset, int length)
|
||||
{
|
||||
try
|
||||
{
|
||||
Read(stream, outBuffer, outOffset, length);
|
||||
return true;
|
||||
}
|
||||
catch (ReadHelperException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int? SafeReadTillMatch(Stream stream, byte[] outBuffer, int outOffset, int maxLength, byte[] endOfHeader)
|
||||
{
|
||||
try
|
||||
{
|
||||
int read = 0;
|
||||
int endIndex = 0;
|
||||
int endLength = endOfHeader.Length;
|
||||
while (true)
|
||||
{
|
||||
int next = stream.ReadByte();
|
||||
if (next == -1) // closed
|
||||
return null;
|
||||
|
||||
if (read >= maxLength)
|
||||
{
|
||||
Log.Error("[SWT-ReadHelper]: SafeReadTillMatch exceeded maxLength");
|
||||
return null;
|
||||
}
|
||||
|
||||
outBuffer[outOffset + read] = (byte)next;
|
||||
read++;
|
||||
|
||||
// if n is match, check n+1 next
|
||||
if (endOfHeader[endIndex] == next)
|
||||
{
|
||||
endIndex++;
|
||||
// when all is match return with read length
|
||||
if (endIndex >= endLength)
|
||||
return read;
|
||||
}
|
||||
// if n not match reset to 0
|
||||
else
|
||||
endIndex = 0;
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.InfoException(e);
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ReadHelperException : Exception
|
||||
{
|
||||
public ReadHelperException(string message) : base(message) { }
|
||||
|
||||
protected ReadHelperException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/ReadHelper.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/ReadHelper.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f4fa5d324e708c46a55810a97de75bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
250
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/ReceiveLoop.cs
vendored
Normal file
250
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/ReceiveLoop.cs
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
internal static class ReceiveLoop
|
||||
{
|
||||
public struct Config
|
||||
{
|
||||
public readonly Connection conn;
|
||||
public readonly int maxMessageSize;
|
||||
public readonly bool expectMask;
|
||||
public readonly ConcurrentQueue<Message> queue;
|
||||
public readonly BufferPool bufferPool;
|
||||
|
||||
public Config(Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue<Message> queue, BufferPool bufferPool)
|
||||
{
|
||||
this.conn = conn ?? throw new ArgumentNullException(nameof(conn));
|
||||
this.maxMessageSize = maxMessageSize;
|
||||
this.expectMask = expectMask;
|
||||
this.queue = queue ?? throw new ArgumentNullException(nameof(queue));
|
||||
this.bufferPool = bufferPool ?? throw new ArgumentNullException(nameof(bufferPool));
|
||||
}
|
||||
|
||||
public void Deconstruct(out Connection conn, out int maxMessageSize, out bool expectMask, out ConcurrentQueue<Message> queue, out BufferPool bufferPool)
|
||||
{
|
||||
conn = this.conn;
|
||||
maxMessageSize = this.maxMessageSize;
|
||||
expectMask = this.expectMask;
|
||||
queue = this.queue;
|
||||
bufferPool = this.bufferPool;
|
||||
}
|
||||
}
|
||||
|
||||
struct Header
|
||||
{
|
||||
public int payloadLength;
|
||||
public int offset;
|
||||
public int opcode;
|
||||
public bool finished;
|
||||
}
|
||||
|
||||
public static void Loop(Config config)
|
||||
{
|
||||
(Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue<Message> queue, BufferPool _) = config;
|
||||
|
||||
Profiler.BeginThreadProfiling("SimpleWeb", $"ReceiveLoop {conn.connId}");
|
||||
|
||||
byte[] readBuffer = new byte[Constants.HeaderSize + (expectMask ? Constants.MaskSize : 0) + maxMessageSize];
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
TcpClient client = conn.client;
|
||||
|
||||
while (client.Connected)
|
||||
ReadOneMessage(config, readBuffer);
|
||||
|
||||
Log.Verbose("[SWT-ReceiveLoop]: {0} Not Connected", conn);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// if interrupted we don't care about other exceptions
|
||||
Utils.CheckForInterupt();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-ReceiveLoop]: Thread Abort Exception"); }
|
||||
catch (ObjectDisposedException e) { Log.InfoException(e); }
|
||||
catch (ReadHelperException e) { Log.InfoException(e); }
|
||||
catch (SocketException e)
|
||||
{
|
||||
// this could happen if wss client closes stream
|
||||
Log.Warn("[SWT-ReceiveLoop]: ReceiveLoop SocketException\n{0}", e.Message);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// this could happen if client disconnects
|
||||
Log.Warn("[SWT-ReceiveLoop]: ReceiveLoop IOException\n{0}", e.Message);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
Log.Error("[SWT-ReceiveLoop]: Invalid data from {0}\n{1}\n{2}\n\n", conn, e.Message, e.StackTrace);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Profiler.EndThreadProfiling();
|
||||
conn.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
static void ReadOneMessage(Config config, byte[] buffer)
|
||||
{
|
||||
(Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue<Message> queue, BufferPool bufferPool) = config;
|
||||
Stream stream = conn.stream;
|
||||
|
||||
Header header = ReadHeader(config, buffer);
|
||||
|
||||
int msgOffset = header.offset;
|
||||
header.offset = ReadHelper.Read(stream, buffer, header.offset, header.payloadLength);
|
||||
|
||||
if (header.finished)
|
||||
{
|
||||
switch (header.opcode)
|
||||
{
|
||||
case 2:
|
||||
HandleArrayMessage(config, buffer, msgOffset, header.payloadLength);
|
||||
break;
|
||||
case 8:
|
||||
HandleCloseMessage(config, buffer, msgOffset, header.payloadLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo cache this to avoid allocations
|
||||
Queue<ArrayBuffer> fragments = new Queue<ArrayBuffer>();
|
||||
fragments.Enqueue(CopyMessageToBuffer(bufferPool, expectMask, buffer, msgOffset, header.payloadLength));
|
||||
int totalSize = header.payloadLength;
|
||||
|
||||
while (!header.finished)
|
||||
{
|
||||
header = ReadHeader(config, buffer, opCodeContinuation: true);
|
||||
|
||||
msgOffset = header.offset;
|
||||
header.offset = ReadHelper.Read(stream, buffer, header.offset, header.payloadLength);
|
||||
fragments.Enqueue(CopyMessageToBuffer(bufferPool, expectMask, buffer, msgOffset, header.payloadLength));
|
||||
|
||||
totalSize += header.payloadLength;
|
||||
MessageProcessor.ThrowIfMsgLengthTooLong(totalSize, maxMessageSize);
|
||||
}
|
||||
|
||||
ArrayBuffer msg = bufferPool.Take(totalSize);
|
||||
msg.count = 0;
|
||||
while (fragments.Count > 0)
|
||||
{
|
||||
ArrayBuffer part = fragments.Dequeue();
|
||||
|
||||
part.CopyTo(msg.array, msg.count);
|
||||
msg.count += part.count;
|
||||
|
||||
part.Release();
|
||||
}
|
||||
|
||||
// dump after mask off
|
||||
Log.DumpBuffer($"[SWT-ReceiveLoop]: Message", msg);
|
||||
|
||||
queue.Enqueue(new Message(conn.connId, msg));
|
||||
}
|
||||
}
|
||||
|
||||
static Header ReadHeader(Config config, byte[] buffer, bool opCodeContinuation = false)
|
||||
{
|
||||
(Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue<Message> queue, BufferPool bufferPool) = config;
|
||||
Stream stream = conn.stream;
|
||||
Header header = new Header();
|
||||
|
||||
// read 2
|
||||
header.offset = ReadHelper.Read(stream, buffer, header.offset, Constants.HeaderMinSize);
|
||||
// log after first blocking call
|
||||
Log.Flood($"[SWT-ReceiveLoop]: Message From {conn}");
|
||||
|
||||
if (MessageProcessor.NeedToReadShortLength(buffer))
|
||||
header.offset = ReadHelper.Read(stream, buffer, header.offset, Constants.ShortLength);
|
||||
if (MessageProcessor.NeedToReadLongLength(buffer))
|
||||
header.offset = ReadHelper.Read(stream, buffer, header.offset, Constants.LongLength);
|
||||
|
||||
Log.DumpBuffer($"[SWT-ReceiveLoop]: Raw Header", buffer, 0, header.offset);
|
||||
|
||||
MessageProcessor.ValidateHeader(buffer, maxMessageSize, expectMask, opCodeContinuation);
|
||||
|
||||
if (expectMask)
|
||||
header.offset = ReadHelper.Read(stream, buffer, header.offset, Constants.MaskSize);
|
||||
|
||||
header.opcode = MessageProcessor.GetOpcode(buffer);
|
||||
header.payloadLength = MessageProcessor.GetPayloadLength(buffer);
|
||||
header.finished = MessageProcessor.Finished(buffer);
|
||||
|
||||
Log.Flood($"[SWT-ReceiveLoop]: Header ln:{header.payloadLength} op:{header.opcode} mask:{expectMask}");
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
static void HandleArrayMessage(Config config, byte[] buffer, int msgOffset, int payloadLength)
|
||||
{
|
||||
(Connection conn, int _, bool expectMask, ConcurrentQueue<Message> queue, BufferPool bufferPool) = config;
|
||||
|
||||
ArrayBuffer arrayBuffer = CopyMessageToBuffer(bufferPool, expectMask, buffer, msgOffset, payloadLength);
|
||||
|
||||
// dump after mask off
|
||||
Log.DumpBuffer($"[SWT-ReceiveLoop]: Message", arrayBuffer);
|
||||
|
||||
queue.Enqueue(new Message(conn.connId, arrayBuffer));
|
||||
}
|
||||
|
||||
static ArrayBuffer CopyMessageToBuffer(BufferPool bufferPool, bool expectMask, byte[] buffer, int msgOffset, int payloadLength)
|
||||
{
|
||||
ArrayBuffer arrayBuffer = bufferPool.Take(payloadLength);
|
||||
|
||||
if (expectMask)
|
||||
{
|
||||
int maskOffset = msgOffset - Constants.MaskSize;
|
||||
// write the result of toggle directly into arrayBuffer to avoid 2nd copy call
|
||||
MessageProcessor.ToggleMask(buffer, msgOffset, arrayBuffer, payloadLength, buffer, maskOffset);
|
||||
}
|
||||
else
|
||||
arrayBuffer.CopyFrom(buffer, msgOffset, payloadLength);
|
||||
|
||||
return arrayBuffer;
|
||||
}
|
||||
|
||||
static void HandleCloseMessage(Config config, byte[] buffer, int msgOffset, int payloadLength)
|
||||
{
|
||||
(Connection conn, int _, bool expectMask, ConcurrentQueue<Message> _, BufferPool _) = config;
|
||||
|
||||
if (expectMask)
|
||||
{
|
||||
int maskOffset = msgOffset - Constants.MaskSize;
|
||||
MessageProcessor.ToggleMask(buffer, msgOffset, payloadLength, buffer, maskOffset);
|
||||
}
|
||||
|
||||
// dump after mask off
|
||||
Log.DumpBuffer($"[SWT-ReceiveLoop]: Message", buffer, msgOffset, payloadLength);
|
||||
Log.Verbose("[SWT-ReceiveLoop]: Close: {0} message:{1}", GetCloseCode(buffer, msgOffset), GetCloseMessage(buffer, msgOffset, payloadLength));
|
||||
|
||||
conn.Dispose();
|
||||
}
|
||||
|
||||
static string GetCloseMessage(byte[] buffer, int msgOffset, int payloadLength)
|
||||
=> Encoding.UTF8.GetString(buffer, msgOffset + 2, payloadLength - 2);
|
||||
|
||||
static int GetCloseCode(byte[] buffer, int msgOffset)
|
||||
=> buffer[msgOffset + 0] << 8 | buffer[msgOffset + 1];
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/ReceiveLoop.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/ReceiveLoop.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a26c2815f58431c4a98c158c8b655ffd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
26
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Request.cs
vendored
Normal file
26
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Request.cs
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a client's request to the Websockets server, which is the first message from the client.
|
||||
/// </summary>
|
||||
public class Request
|
||||
{
|
||||
static readonly char[] lineSplitChars = new char[] { '\r', '\n' };
|
||||
static readonly char[] headerSplitChars = new char[] { ':' };
|
||||
public string RequestLine;
|
||||
public Dictionary<string, string> Headers = new Dictionary<string, string>();
|
||||
|
||||
public Request(string message)
|
||||
{
|
||||
string[] all = message.Split(lineSplitChars, StringSplitOptions.RemoveEmptyEntries);
|
||||
RequestLine = all.First();
|
||||
Headers = all.Skip(1)
|
||||
.Select(header => header.Split(headerSplitChars, 2, StringSplitOptions.RemoveEmptyEntries))
|
||||
.ToDictionary(split => split[0].Trim(), split => split[1].Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Request.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Request.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50b41ad63d4956a42a073bad5158fe09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
222
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/SendLoop.cs
vendored
Normal file
222
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/SendLoop.cs
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public static class SendLoopConfig
|
||||
{
|
||||
public static volatile bool batchSend = false;
|
||||
public static volatile bool sleepBeforeSend = false;
|
||||
}
|
||||
internal static class SendLoop
|
||||
{
|
||||
public struct Config
|
||||
{
|
||||
public readonly Connection conn;
|
||||
public readonly int bufferSize;
|
||||
public readonly bool setMask;
|
||||
|
||||
public Config(Connection conn, int bufferSize, bool setMask)
|
||||
{
|
||||
this.conn = conn ?? throw new ArgumentNullException(nameof(conn));
|
||||
this.bufferSize = bufferSize;
|
||||
this.setMask = setMask;
|
||||
}
|
||||
|
||||
public void Deconstruct(out Connection conn, out int bufferSize, out bool setMask)
|
||||
{
|
||||
conn = this.conn;
|
||||
bufferSize = this.bufferSize;
|
||||
setMask = this.setMask;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Loop(Config config)
|
||||
{
|
||||
(Connection conn, int bufferSize, bool setMask) = config;
|
||||
|
||||
Profiler.BeginThreadProfiling("SimpleWeb", $"SendLoop {conn.connId}");
|
||||
|
||||
// create write buffer for this thread
|
||||
byte[] writeBuffer = new byte[bufferSize];
|
||||
MaskHelper maskHelper = setMask ? new MaskHelper() : null;
|
||||
try
|
||||
{
|
||||
TcpClient client = conn.client;
|
||||
Stream stream = conn.stream;
|
||||
|
||||
// null check in case disconnect while send thread is starting
|
||||
if (client == null)
|
||||
return;
|
||||
|
||||
while (client.Connected)
|
||||
{
|
||||
// wait for message
|
||||
conn.sendPending.Wait();
|
||||
// wait for 1ms for mirror to send other messages
|
||||
if (SendLoopConfig.sleepBeforeSend)
|
||||
Thread.Sleep(1);
|
||||
|
||||
conn.sendPending.Reset();
|
||||
|
||||
if (SendLoopConfig.batchSend)
|
||||
{
|
||||
int offset = 0;
|
||||
while (conn.sendQueue.TryDequeue(out ArrayBuffer msg))
|
||||
{
|
||||
// check if connected before sending message
|
||||
if (!client.Connected)
|
||||
{
|
||||
Log.Verbose("[SWT-SendLoop]: SendLoop {0} not connected", conn);
|
||||
msg.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
int maxLength = msg.count + Constants.HeaderSize + Constants.MaskSize;
|
||||
|
||||
// if next writer could overflow, write to stream and clear buffer
|
||||
if (offset + maxLength > bufferSize)
|
||||
{
|
||||
stream.Write(writeBuffer, 0, offset);
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
offset = SendMessage(writeBuffer, offset, msg, setMask, maskHelper);
|
||||
msg.Release();
|
||||
}
|
||||
|
||||
// after no message in queue, send remaining messages
|
||||
// don't need to check offset > 0 because last message in queue will always be sent here
|
||||
|
||||
stream.Write(writeBuffer, 0, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (conn.sendQueue.TryDequeue(out ArrayBuffer msg))
|
||||
{
|
||||
// check if connected before sending message
|
||||
if (!client.Connected)
|
||||
{
|
||||
Log.Verbose("[SWT-SendLoop]: SendLoop {0} not connected", conn);
|
||||
msg.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
int length = SendMessage(writeBuffer, 0, msg, setMask, maskHelper);
|
||||
stream.Write(writeBuffer, 0, length);
|
||||
msg.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.Verbose("[SWT-SendLoop]: {0} Not Connected", conn);
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-SendLoop]: Thread Abort Exception"); }
|
||||
catch (Exception e) { Log.Exception(e); }
|
||||
finally
|
||||
{
|
||||
Profiler.EndThreadProfiling();
|
||||
conn.Dispose();
|
||||
maskHelper?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>new offset in buffer</returns>
|
||||
static int SendMessage(byte[] buffer, int startOffset, ArrayBuffer msg, bool setMask, MaskHelper maskHelper)
|
||||
{
|
||||
int msgLength = msg.count;
|
||||
int offset = WriteHeader(buffer, startOffset, msgLength, setMask);
|
||||
|
||||
if (setMask)
|
||||
{
|
||||
offset = maskHelper.WriteMask(buffer, offset);
|
||||
}
|
||||
|
||||
msg.CopyTo(buffer, offset);
|
||||
offset += msgLength;
|
||||
|
||||
// dump before mask on
|
||||
Log.DumpBuffer("[SWT-SendLoop]: Send", buffer, startOffset, offset);
|
||||
|
||||
if (setMask)
|
||||
{
|
||||
int messageOffset = offset - msgLength;
|
||||
MessageProcessor.ToggleMask(buffer, messageOffset, msgLength, buffer, messageOffset - Constants.MaskSize);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static int WriteHeader(byte[] buffer, int startOffset, int msgLength, bool setMask)
|
||||
{
|
||||
int sendLength = 0;
|
||||
const byte finished = 128;
|
||||
const byte byteOpCode = 2;
|
||||
|
||||
buffer[startOffset + 0] = finished | byteOpCode;
|
||||
sendLength++;
|
||||
|
||||
if (msgLength <= Constants.BytePayloadLength)
|
||||
{
|
||||
buffer[startOffset + 1] = (byte)msgLength;
|
||||
sendLength++;
|
||||
}
|
||||
else if (msgLength <= ushort.MaxValue)
|
||||
{
|
||||
buffer[startOffset + 1] = 126;
|
||||
buffer[startOffset + 2] = (byte)(msgLength >> 8);
|
||||
buffer[startOffset + 3] = (byte)msgLength;
|
||||
sendLength += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[startOffset + 1] = 127;
|
||||
// must be 64 bytes, but we only have 32 bit length, so first 4 bits are 0
|
||||
buffer[startOffset + 2] = 0;
|
||||
buffer[startOffset + 3] = 0;
|
||||
buffer[startOffset + 4] = 0;
|
||||
buffer[startOffset + 5] = 0;
|
||||
buffer[startOffset + 6] = (byte)(msgLength >> 24);
|
||||
buffer[startOffset + 7] = (byte)(msgLength >> 16);
|
||||
buffer[startOffset + 8] = (byte)(msgLength >> 8);
|
||||
buffer[startOffset + 9] = (byte)msgLength;
|
||||
|
||||
sendLength += 9;
|
||||
}
|
||||
|
||||
if (setMask)
|
||||
buffer[startOffset + 1] |= 0b1000_0000;
|
||||
|
||||
return sendLength + startOffset;
|
||||
}
|
||||
|
||||
}
|
||||
sealed class MaskHelper : IDisposable
|
||||
{
|
||||
readonly byte[] maskBuffer;
|
||||
readonly RNGCryptoServiceProvider random;
|
||||
|
||||
public MaskHelper()
|
||||
{
|
||||
maskBuffer = new byte[4];
|
||||
random = new RNGCryptoServiceProvider();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
random.Dispose();
|
||||
}
|
||||
|
||||
public int WriteMask(byte[] buffer, int offset)
|
||||
{
|
||||
random.GetBytes(maskBuffer);
|
||||
Buffer.BlockCopy(maskBuffer, 0, buffer, offset, 4);
|
||||
|
||||
return offset + 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/SendLoop.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/SendLoop.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f87dd81736d9c824db67f808ac71841d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
26
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/TcpConfig.cs
vendored
Normal file
26
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/TcpConfig.cs
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct TcpConfig
|
||||
{
|
||||
public readonly bool noDelay;
|
||||
public readonly int sendTimeout;
|
||||
public readonly int receiveTimeout;
|
||||
|
||||
public TcpConfig(bool noDelay, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
this.noDelay = noDelay;
|
||||
this.sendTimeout = sendTimeout;
|
||||
this.receiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
public void ApplyTo(TcpClient client)
|
||||
{
|
||||
client.SendTimeout = sendTimeout;
|
||||
client.ReceiveTimeout = receiveTimeout;
|
||||
client.NoDelay = noDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/TcpConfig.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/TcpConfig.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81ac8d35f28fab14b9edda5cd9d4fc86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
13
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Utils.cs
vendored
Normal file
13
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Utils.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
internal static class Utils
|
||||
{
|
||||
public static void CheckForInterupt()
|
||||
{
|
||||
// sleep in order to check for ThreadInterruptedException
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Utils.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Common/Utils.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4643ffb4cb0562847b1ae925d07e15b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
21
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/LICENSE
vendored
Normal file
21
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 James Frowen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/LICENSE.meta
vendored
Normal file
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/LICENSE.meta
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a0cf751b4a201242ac60b4adbc54657
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
19
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/README.txt
vendored
Normal file
19
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/README.txt
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
SimpleWebTransport is a Transport that implements websocket for Webgl
|
||||
Can be used in High level networking solution like Mirror or Mirage
|
||||
This transport can also work on standalone builds and has support for
|
||||
encryption with websocket secure.
|
||||
|
||||
Requirements:
|
||||
Unity 2019.4 LTS
|
||||
|
||||
Documentation:
|
||||
https://mirror-networking.gitbook.io/docs/
|
||||
https://github.com/James-Frowen/SimpleWebTransport/blob/master/README.md
|
||||
|
||||
Support:
|
||||
Discord: https://discord.gg/BZTQcftBkE
|
||||
Bug Reports: https://github.com/James-Frowen/SimpleWebTransport/issues
|
||||
|
||||
|
||||
**To get most recent updates and fixes download from github**
|
||||
https://github.com/James-Frowen/SimpleWebTransport/releases
|
||||
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/README.txt.meta
vendored
Normal file
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/README.txt.meta
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e3971d5783109f4d9ce93c7a689d701
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server.meta
vendored
Normal file
8
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e599e92544d43344a9a9060052add28
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
156
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/ServerHandshake.cs
vendored
Normal file
156
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/ServerHandshake.cs
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles Handshakes from new clients on the server
|
||||
/// <para>The server handshake has buffers to reduce allocations when clients connect</para>
|
||||
/// </summary>
|
||||
internal class ServerHandshake
|
||||
{
|
||||
const int GetSize = 3;
|
||||
const int ResponseLength = 129;
|
||||
const int KeyLength = 24;
|
||||
const int MergedKeyLength = 60;
|
||||
const string KeyHeaderString = "\r\nSec-WebSocket-Key: ";
|
||||
// this isn't an official max, just a reasonable size for a websocket handshake
|
||||
readonly int maxHttpHeaderSize = 3000;
|
||||
|
||||
// SHA-1 is the websocket standard:
|
||||
// https://www.rfc-editor.org/rfc/rfc6455
|
||||
// we should follow the standard, even though SHA1 is considered weak:
|
||||
// https://stackoverflow.com/questions/38038841/why-is-sha-1-considered-insecure
|
||||
readonly SHA1 sha1 = SHA1.Create();
|
||||
readonly BufferPool bufferPool;
|
||||
|
||||
public ServerHandshake(BufferPool bufferPool, int handshakeMaxSize)
|
||||
{
|
||||
this.bufferPool = bufferPool;
|
||||
maxHttpHeaderSize = handshakeMaxSize;
|
||||
}
|
||||
|
||||
~ServerHandshake()
|
||||
{
|
||||
sha1.Dispose();
|
||||
}
|
||||
|
||||
public bool TryHandshake(Connection conn)
|
||||
{
|
||||
Stream stream = conn.stream;
|
||||
|
||||
using (ArrayBuffer getHeader = bufferPool.Take(GetSize))
|
||||
{
|
||||
if (!ReadHelper.TryRead(stream, getHeader.array, 0, GetSize))
|
||||
return false;
|
||||
|
||||
getHeader.count = GetSize;
|
||||
|
||||
if (!IsGet(getHeader.array))
|
||||
{
|
||||
Log.Warn("[SWT-ServerHandshake]: First bytes from client was not 'GET' for handshake, instead was {0}", Log.BufferToString(getHeader.array, 0, GetSize));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string msg = ReadToEndForHandshake(stream);
|
||||
|
||||
if (string.IsNullOrEmpty(msg))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
AcceptHandshake(stream, msg);
|
||||
|
||||
conn.request = new Request(msg);
|
||||
conn.remoteAddress = conn.CalculateAddress();
|
||||
Log.Info($"[SWT-ServerHandshake]: A client connected from {0}", conn);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
Log.InfoException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string ReadToEndForHandshake(Stream stream)
|
||||
{
|
||||
using (ArrayBuffer readBuffer = bufferPool.Take(maxHttpHeaderSize))
|
||||
{
|
||||
int? readCountOrFail = ReadHelper.SafeReadTillMatch(stream, readBuffer.array, 0, maxHttpHeaderSize, Constants.endOfHandshake);
|
||||
if (!readCountOrFail.HasValue)
|
||||
return null;
|
||||
|
||||
int readCount = readCountOrFail.Value;
|
||||
|
||||
string msg = Encoding.ASCII.GetString(readBuffer.array, 0, readCount);
|
||||
// GET isn't in the bytes we read here, so we need to add it back
|
||||
msg = $"GET{msg}";
|
||||
Log.Verbose("[SWT-ServerHandshake]: Client Handshake Message:\r\n{0}", msg);
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsGet(byte[] getHeader)
|
||||
{
|
||||
// just check bytes here instead of using Encoding.ASCII
|
||||
return getHeader[0] == 71 && // G
|
||||
getHeader[1] == 69 && // E
|
||||
getHeader[2] == 84; // T
|
||||
}
|
||||
|
||||
void AcceptHandshake(Stream stream, string msg)
|
||||
{
|
||||
using (ArrayBuffer keyBuffer = bufferPool.Take(KeyLength + Constants.HandshakeGUIDLength),
|
||||
responseBuffer = bufferPool.Take(ResponseLength))
|
||||
{
|
||||
GetKey(msg, keyBuffer.array);
|
||||
AppendGuid(keyBuffer.array);
|
||||
byte[] keyHash = CreateHash(keyBuffer.array);
|
||||
CreateResponse(keyHash, responseBuffer.array);
|
||||
|
||||
stream.Write(responseBuffer.array, 0, ResponseLength);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetKey(string msg, byte[] keyBuffer)
|
||||
{
|
||||
int start = msg.IndexOf(KeyHeaderString, StringComparison.InvariantCultureIgnoreCase) + KeyHeaderString.Length;
|
||||
|
||||
Log.Verbose("[SWT-ServerHandshake]: Handshake Key: {0}", msg.Substring(start, KeyLength));
|
||||
Encoding.ASCII.GetBytes(msg, start, KeyLength, keyBuffer, 0);
|
||||
}
|
||||
|
||||
static void AppendGuid(byte[] keyBuffer)
|
||||
{
|
||||
Buffer.BlockCopy(Constants.HandshakeGUIDBytes, 0, keyBuffer, KeyLength, Constants.HandshakeGUIDLength);
|
||||
}
|
||||
|
||||
byte[] CreateHash(byte[] keyBuffer)
|
||||
{
|
||||
Log.Verbose("[SWT-ServerHandshake]: Handshake Hashing {0}", Encoding.ASCII.GetString(keyBuffer, 0, MergedKeyLength));
|
||||
return sha1.ComputeHash(keyBuffer, 0, MergedKeyLength);
|
||||
}
|
||||
|
||||
static void CreateResponse(byte[] keyHash, byte[] responseBuffer)
|
||||
{
|
||||
string keyHashString = Convert.ToBase64String(keyHash);
|
||||
|
||||
// compiler should merge these strings into 1 string before format
|
||||
string message = string.Format(
|
||||
"HTTP/1.1 101 Switching Protocols\r\n" +
|
||||
"Connection: Upgrade\r\n" +
|
||||
"Upgrade: websocket\r\n" +
|
||||
"Sec-WebSocket-Accept: {0}\r\n\r\n",
|
||||
keyHashString);
|
||||
|
||||
Log.Verbose("[SWT-ServerHandshake]: Handshake Response length {0}, IsExpected {1}", message.Length, message.Length == ResponseLength);
|
||||
Encoding.ASCII.GetBytes(message, 0, ResponseLength, responseBuffer, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/ServerHandshake.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/ServerHandshake.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6268509ac4fb48141b9944c03295da11
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
74
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/ServerSslHelper.cs
vendored
Normal file
74
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/ServerSslHelper.cs
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public struct SslConfig
|
||||
{
|
||||
public readonly bool enabled;
|
||||
public readonly string certPath;
|
||||
public readonly string certPassword;
|
||||
public readonly SslProtocols sslProtocols;
|
||||
|
||||
public SslConfig(bool enabled, string certPath, string certPassword, SslProtocols sslProtocols)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
this.certPath = certPath;
|
||||
this.certPassword = certPassword;
|
||||
this.sslProtocols = sslProtocols;
|
||||
}
|
||||
}
|
||||
internal class ServerSslHelper
|
||||
{
|
||||
readonly SslConfig config;
|
||||
readonly X509Certificate2 certificate;
|
||||
|
||||
public ServerSslHelper(SslConfig sslConfig)
|
||||
{
|
||||
config = sslConfig;
|
||||
if (config.enabled)
|
||||
{
|
||||
certificate = new X509Certificate2(config.certPath, config.certPassword);
|
||||
Log.Info($"[SWT-ServerSslHelper]: SSL Certificate {0} loaded with expiration of {1}", certificate.Subject, certificate.GetExpirationDateString());
|
||||
}
|
||||
}
|
||||
|
||||
internal bool TryCreateStream(Connection conn)
|
||||
{
|
||||
NetworkStream stream = conn.client.GetStream();
|
||||
if (config.enabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
conn.stream = CreateStream(stream);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("[SWT-ServerSslHelper]: Create SSLStream Failed: {0}", e.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
conn.stream = stream;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Stream CreateStream(NetworkStream stream)
|
||||
{
|
||||
SslStream sslStream = new SslStream(stream, true, acceptClient);
|
||||
sslStream.AuthenticateAsServer(certificate, false, config.sslProtocols, false);
|
||||
|
||||
return sslStream;
|
||||
}
|
||||
|
||||
// always accept client
|
||||
bool acceptClient(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => true;
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/ServerSslHelper.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/ServerSslHelper.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11061fee528ebdd43817a275b1e4a317
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
115
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/SimpleWebServer.cs
vendored
Normal file
115
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/SimpleWebServer.cs
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public class SimpleWebServer
|
||||
{
|
||||
public event Action<int, string> onConnect;
|
||||
public event Action<int> onDisconnect;
|
||||
public event Action<int, ArraySegment<byte>> onData;
|
||||
public event Action<int, Exception> onError;
|
||||
|
||||
readonly int maxMessagesPerTick;
|
||||
readonly WebSocketServer server;
|
||||
readonly BufferPool bufferPool;
|
||||
|
||||
public bool Active { get; private set; }
|
||||
|
||||
public SimpleWebServer(int maxMessagesPerTick, TcpConfig tcpConfig, int maxMessageSize, int handshakeMaxSize, SslConfig sslConfig)
|
||||
{
|
||||
this.maxMessagesPerTick = maxMessagesPerTick;
|
||||
// use max because bufferpool is used for both messages and handshake
|
||||
int max = Math.Max(maxMessageSize, handshakeMaxSize);
|
||||
bufferPool = new BufferPool(5, 20, max);
|
||||
server = new WebSocketServer(tcpConfig, maxMessageSize, handshakeMaxSize, sslConfig, bufferPool);
|
||||
}
|
||||
|
||||
public void Start(ushort port)
|
||||
{
|
||||
server.Listen(port);
|
||||
Active = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
server.Stop();
|
||||
Active = false;
|
||||
}
|
||||
|
||||
public void SendAll(List<int> connectionIds, ArraySegment<byte> source)
|
||||
{
|
||||
ArrayBuffer buffer = bufferPool.Take(source.Count);
|
||||
buffer.CopyFrom(source);
|
||||
buffer.SetReleasesRequired(connectionIds.Count);
|
||||
|
||||
// make copy of array before for each, data sent to each client is the same
|
||||
foreach (int id in connectionIds)
|
||||
server.Send(id, buffer);
|
||||
}
|
||||
|
||||
public void SendOne(int connectionId, ArraySegment<byte> source)
|
||||
{
|
||||
ArrayBuffer buffer = bufferPool.Take(source.Count);
|
||||
buffer.CopyFrom(source);
|
||||
server.Send(connectionId, buffer);
|
||||
}
|
||||
|
||||
public bool KickClient(int connectionId) => server.CloseConnection(connectionId);
|
||||
|
||||
public string GetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
|
||||
|
||||
public Request GetClientRequest(int connectionId) => server.GetClientRequest(connectionId);
|
||||
|
||||
/// <summary>
|
||||
/// Processes all new messages
|
||||
/// </summary>
|
||||
public void ProcessMessageQueue()
|
||||
{
|
||||
ProcessMessageQueue(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes all messages while <paramref name="behaviour"/> is enabled
|
||||
/// </summary>
|
||||
/// <param name="behaviour"></param>
|
||||
public void ProcessMessageQueue(MonoBehaviour behaviour)
|
||||
{
|
||||
int processedCount = 0;
|
||||
bool skipEnabled = behaviour == null;
|
||||
// check enabled every time in case behaviour was disabled after data
|
||||
while (
|
||||
(skipEnabled || behaviour.enabled) &&
|
||||
processedCount < maxMessagesPerTick &&
|
||||
// Dequeue last
|
||||
server.receiveQueue.TryDequeue(out Message next)
|
||||
)
|
||||
{
|
||||
processedCount++;
|
||||
|
||||
switch (next.type)
|
||||
{
|
||||
case EventType.Connected:
|
||||
onConnect?.Invoke(next.connId, GetClientAddress(next.connId));
|
||||
break;
|
||||
case EventType.Data:
|
||||
onData?.Invoke(next.connId, next.data.ToSegment());
|
||||
next.data.Release();
|
||||
break;
|
||||
case EventType.Disconnected:
|
||||
onDisconnect?.Invoke(next.connId);
|
||||
break;
|
||||
case EventType.Error:
|
||||
onError?.Invoke(next.connId, next.exception);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.receiveQueue.Count > 0)
|
||||
{
|
||||
Log.Warn("[SWT-SimpleWebServer]: ProcessMessageQueue has {0} remaining.", server.receiveQueue.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/SimpleWebServer.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/SimpleWebServer.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd51d7896f55a5e48b41a4b526562b0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
230
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/WebSocketServer.cs
vendored
Normal file
230
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/WebSocketServer.cs
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public class WebSocketServer
|
||||
{
|
||||
public readonly ConcurrentQueue<Message> receiveQueue = new ConcurrentQueue<Message>();
|
||||
|
||||
readonly TcpConfig tcpConfig;
|
||||
readonly int maxMessageSize;
|
||||
|
||||
TcpListener listener;
|
||||
Thread acceptThread;
|
||||
bool serverStopped;
|
||||
readonly ServerHandshake handShake;
|
||||
readonly ServerSslHelper sslHelper;
|
||||
readonly BufferPool bufferPool;
|
||||
readonly ConcurrentDictionary<int, Connection> connections = new ConcurrentDictionary<int, Connection>();
|
||||
|
||||
int _idCounter = 0;
|
||||
|
||||
public WebSocketServer(TcpConfig tcpConfig, int maxMessageSize, int handshakeMaxSize, SslConfig sslConfig, BufferPool bufferPool)
|
||||
{
|
||||
this.tcpConfig = tcpConfig;
|
||||
this.maxMessageSize = maxMessageSize;
|
||||
sslHelper = new ServerSslHelper(sslConfig);
|
||||
this.bufferPool = bufferPool;
|
||||
handShake = new ServerHandshake(this.bufferPool, handshakeMaxSize);
|
||||
}
|
||||
|
||||
public void Listen(int port)
|
||||
{
|
||||
listener = TcpListener.Create(port);
|
||||
listener.Start();
|
||||
|
||||
Log.Verbose("[SWT-WebSocketServer]: Server Started on {0}", port);
|
||||
|
||||
acceptThread = new Thread(acceptLoop);
|
||||
acceptThread.IsBackground = true;
|
||||
acceptThread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
serverStopped = true;
|
||||
|
||||
// Interrupt then stop so that Exception is handled correctly
|
||||
acceptThread?.Interrupt();
|
||||
listener?.Stop();
|
||||
acceptThread = null;
|
||||
|
||||
Log.Verbose("[SWT-WebSocketServer]: Server stopped...closing all connections.");
|
||||
|
||||
// make copy so that foreach doesn't break if values are removed
|
||||
Connection[] connectionsCopy = connections.Values.ToArray();
|
||||
foreach (Connection conn in connectionsCopy)
|
||||
conn.Dispose();
|
||||
|
||||
connections.Clear();
|
||||
}
|
||||
|
||||
void acceptLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
TcpClient client = listener.AcceptTcpClient();
|
||||
tcpConfig.ApplyTo(client);
|
||||
|
||||
// TODO keep track of connections before they are in connections dictionary
|
||||
// this might not be a problem as HandshakeAndReceiveLoop checks for stop
|
||||
// and returns/disposes before sending message to queue
|
||||
Connection conn = new Connection(client, AfterConnectionDisposed);
|
||||
Log.Verbose("[SWT-WebSocketServer]: A client connected from {0}", conn);
|
||||
|
||||
// handshake needs its own thread as it needs to wait for message from client
|
||||
Thread receiveThread = new Thread(() => HandshakeAndReceiveLoop(conn));
|
||||
|
||||
conn.receiveThread = receiveThread;
|
||||
|
||||
receiveThread.IsBackground = true;
|
||||
receiveThread.Start();
|
||||
}
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// check for Interrupted/Abort
|
||||
Utils.CheckForInterupt();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-WebSocketServer]: Thread Abort Exception"); }
|
||||
catch (Exception e) { Log.Exception(e); }
|
||||
}
|
||||
|
||||
void HandshakeAndReceiveLoop(Connection conn)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool success = sslHelper.TryCreateStream(conn);
|
||||
if (!success)
|
||||
{
|
||||
Log.Warn("[SWT-WebSocketServer]: Failed to create SSL Stream {0}", conn);
|
||||
conn.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
success = handShake.TryHandshake(conn);
|
||||
|
||||
if (success)
|
||||
Log.Verbose("[SWT-WebSocketServer]: Sent Handshake {0}, false", conn);
|
||||
else
|
||||
{
|
||||
Log.Warn("[SWT-WebSocketServer]: Handshake Failed {0}", conn);
|
||||
conn.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
// check if Stop has been called since accepting this client
|
||||
if (serverStopped)
|
||||
{
|
||||
Log.Warn("[SWT-WebSocketServer]: Server stopped after successful handshake");
|
||||
return;
|
||||
}
|
||||
|
||||
conn.connId = Interlocked.Increment(ref _idCounter);
|
||||
connections.TryAdd(conn.connId, conn);
|
||||
|
||||
receiveQueue.Enqueue(new Message(conn.connId, EventType.Connected));
|
||||
|
||||
Thread sendThread = new Thread(() =>
|
||||
{
|
||||
SendLoop.Config sendConfig = new SendLoop.Config(
|
||||
conn,
|
||||
bufferSize: Constants.HeaderSize + maxMessageSize,
|
||||
setMask: false);
|
||||
|
||||
SendLoop.Loop(sendConfig);
|
||||
});
|
||||
|
||||
conn.sendThread = sendThread;
|
||||
sendThread.IsBackground = true;
|
||||
sendThread.Name = $"SendThread {conn.connId}";
|
||||
sendThread.Start();
|
||||
|
||||
ReceiveLoop.Config receiveConfig = new ReceiveLoop.Config(
|
||||
conn,
|
||||
maxMessageSize,
|
||||
expectMask: true,
|
||||
receiveQueue,
|
||||
bufferPool);
|
||||
|
||||
ReceiveLoop.Loop(receiveConfig);
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-WebSocketServer]: Thread Abort Exception"); }
|
||||
catch (Exception e) { Log.Exception(e); }
|
||||
finally
|
||||
{
|
||||
// close here in case connect fails
|
||||
conn.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
void AfterConnectionDisposed(Connection conn)
|
||||
{
|
||||
if (conn.connId != Connection.IdNotSet)
|
||||
{
|
||||
receiveQueue.Enqueue(new Message(conn.connId, EventType.Disconnected));
|
||||
connections.TryRemove(conn.connId, out Connection _);
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(int id, ArrayBuffer buffer)
|
||||
{
|
||||
if (connections.TryGetValue(id, out Connection conn))
|
||||
{
|
||||
conn.sendQueue.Enqueue(buffer);
|
||||
conn.sendPending.Set();
|
||||
}
|
||||
else
|
||||
Log.Warn("[SWT-WebSocketServer]: Cannot send message to {0} because connection was not found in dictionary. Maybe it disconnected.", id);
|
||||
}
|
||||
|
||||
public bool CloseConnection(int id)
|
||||
{
|
||||
if (connections.TryGetValue(id, out Connection conn))
|
||||
{
|
||||
Log.Info($"[SWT-WebSocketServer]: Disconnecting connection {0}", id);
|
||||
conn.Dispose();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn("[SWT-WebSocketServer]: Failed to kick {0} because id not found.", id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetClientAddress(int id)
|
||||
{
|
||||
if (!connections.TryGetValue(id, out Connection conn))
|
||||
{
|
||||
Log.Warn("[SWT-WebSocketServer]: Cannot get address of connection {0} because connection was not found in dictionary.", id);
|
||||
return null;
|
||||
}
|
||||
|
||||
return conn.remoteAddress;
|
||||
}
|
||||
|
||||
public Request GetClientRequest(int id)
|
||||
{
|
||||
if (!connections.TryGetValue(id, out Connection conn))
|
||||
{
|
||||
Log.Warn("[SWT-WebSocketServer]: Cannot get request of connection {0} because connection was not found in dictionary.", id);
|
||||
return null;
|
||||
}
|
||||
|
||||
return conn.request;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/WebSocketServer.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/Server/WebSocketServer.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c434db044777d2439bae5a57d4e8ee7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/SimpleWebTransport.asmdef
vendored
Normal file
14
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/SimpleWebTransport.asmdef
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "SimpleWebTransport",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/SimpleWebTransport.asmdef.meta
vendored
Normal file
7
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/SimpleWebTransport.asmdef.meta
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b5390adca4e2bb4791cb930316d6f3e
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
49
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/SslConfigLoader.cs
vendored
Normal file
49
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/SslConfigLoader.cs
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.IO;
|
||||
using System.Security.Authentication;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
|
||||
public class SslConfigLoader
|
||||
{
|
||||
internal struct Cert
|
||||
{
|
||||
public string path;
|
||||
public string password;
|
||||
}
|
||||
public static SslConfig Load(bool sslEnabled, string sslCertJson, SslProtocols sslProtocols)
|
||||
{
|
||||
// don't need to load anything if ssl is not enabled
|
||||
if (!sslEnabled)
|
||||
return default;
|
||||
|
||||
string certJsonPath = sslCertJson;
|
||||
|
||||
Cert cert = LoadCertJson(certJsonPath);
|
||||
|
||||
return new SslConfig(
|
||||
enabled: sslEnabled,
|
||||
sslProtocols: sslProtocols,
|
||||
certPath: cert.path,
|
||||
certPassword: cert.password
|
||||
);
|
||||
}
|
||||
|
||||
internal static Cert LoadCertJson(string certJsonPath)
|
||||
{
|
||||
string json = File.ReadAllText(certJsonPath);
|
||||
Cert cert = JsonUtility.FromJson<Cert>(json);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cert.path))
|
||||
throw new InvalidDataException("Cert Json didn't not contain \"path\"");
|
||||
|
||||
// don't use IsNullOrWhiteSpace here because whitespace could be a valid password for a cert
|
||||
// password can also be empty
|
||||
if (string.IsNullOrEmpty(cert.password))
|
||||
cert.password = string.Empty;
|
||||
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/SslConfigLoader.cs.meta
vendored
Normal file
11
Assets/ThirdParty/Mirror/Transports/SimpleWeb/SimpleWeb/SslConfigLoader.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfdb6b97a48a48b498e563e857342da1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user