mirror of
https://github.com/smyalygames/monopoly.git
synced 2025-12-29 07:48:48 +01:00
Added multiplayer plugin
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
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 writen to buffer
|
||||
/// </summary>
|
||||
internal int count;
|
||||
|
||||
/// <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()
|
||||
{
|
||||
return new ArraySegment<byte>(array, 0, count);
|
||||
}
|
||||
|
||||
[Conditional("UNITY_ASSERTIONS")]
|
||||
internal void Validate(int arraySize)
|
||||
{
|
||||
if (array.Length != arraySize)
|
||||
{
|
||||
Log.Error("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.Verbose($"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.Verbose($"BufferBucket({arraySize}) count:{next}");
|
||||
}
|
||||
[Conditional("DEBUG")]
|
||||
void DecrementCreated()
|
||||
{
|
||||
int next = Interlocked.Decrement(ref _current);
|
||||
Log.Verbose($"BufferBucket({arraySize}) count:{next}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection of different sized buffers
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Problem: <br/>
|
||||
/// * Need to cached byte[] so that new ones arn'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 atleast 2");
|
||||
if (smallest < 1) throw new ArgumentException("Smallest must be atleast 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)
|
||||
|
||||
// perceision wont be lose when using doubles
|
||||
}
|
||||
|
||||
[Conditional("UNITY_ASSERTIONS")]
|
||||
void Validate()
|
||||
{
|
||||
if (buckets[0].arraySize != smallest)
|
||||
{
|
||||
Log.Error($"BufferPool Failed to create bucket for smallest. bucket:{buckets[0].arraySize} smallest{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($"BufferPool Failed to create bucket for largest. bucket:{largestBucket} smallest{largest}");
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayBuffer Take(int size)
|
||||
{
|
||||
if (size > largest) { throw new ArgumentException($"Size ({size}) is greatest that largest ({largest})"); }
|
||||
|
||||
for (int i = 0; i < bucketCount; i++)
|
||||
{
|
||||
if (size <= buckets[i].arraySize)
|
||||
{
|
||||
return buckets[i].Take();
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Size ({size}) is greatest that largest ({largest})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94ae50f3ec35667469b861b12cd72f92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
internal sealed class Connection : IDisposable
|
||||
{
|
||||
public const int IdNotSet = -1;
|
||||
|
||||
readonly object disposedLock = new object();
|
||||
|
||||
public TcpClient client;
|
||||
|
||||
public int connId = IdNotSet;
|
||||
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($"Dispose {ToString()}");
|
||||
|
||||
// check hasDisposed first to stop ThreadInterruptedException on lock
|
||||
if (hasDisposed) { return; }
|
||||
|
||||
Log.Info($"Connection Close: {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 dont 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()
|
||||
{
|
||||
System.Net.EndPoint endpoint = client?.Client?.RemoteEndPoint;
|
||||
return $"[Conn:{connId}, endPoint:{endpoint}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a13073c2b49d39943888df45174851bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
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>
|
||||
/// 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' };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85d110a089d6ad348abf2d073ebce7cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public enum EventType
|
||||
{
|
||||
Connected,
|
||||
Data,
|
||||
Disconnected,
|
||||
Error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d9cd7d2b5229ab42a12e82ae17d0347
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
116
Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs
Normal file
116
Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Conditional = System.Diagnostics.ConditionalAttribute;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
public static class Log
|
||||
{
|
||||
// used for Conditional
|
||||
const string SIMPLEWEB_LOG_ENABLED = nameof(SIMPLEWEB_LOG_ENABLED);
|
||||
const string DEBUG = nameof(DEBUG);
|
||||
|
||||
public enum Levels
|
||||
{
|
||||
none = 0,
|
||||
error = 1,
|
||||
warn = 2,
|
||||
info = 3,
|
||||
verbose = 4,
|
||||
}
|
||||
|
||||
public static ILogger logger = Debug.unityLogger;
|
||||
public static Levels level = Levels.none;
|
||||
|
||||
public static string BufferToString(byte[] buffer, int offset = 0, int? length = null)
|
||||
{
|
||||
return BitConverter.ToString(buffer, offset, length ?? buffer.Length);
|
||||
}
|
||||
|
||||
[Conditional(SIMPLEWEB_LOG_ENABLED)]
|
||||
public static void DumpBuffer(string label, byte[] buffer, int offset, int length)
|
||||
{
|
||||
if (level < Levels.verbose)
|
||||
return;
|
||||
|
||||
logger.Log(LogType.Log, $"VERBOSE: <color=blue>{label}: {BufferToString(buffer, offset, length)}</color>");
|
||||
}
|
||||
|
||||
[Conditional(SIMPLEWEB_LOG_ENABLED)]
|
||||
public static void DumpBuffer(string label, ArrayBuffer arrayBuffer)
|
||||
{
|
||||
if (level < Levels.verbose)
|
||||
return;
|
||||
|
||||
logger.Log(LogType.Log, $"VERBOSE: <color=blue>{label}: {BufferToString(arrayBuffer.array, 0, arrayBuffer.count)}</color>");
|
||||
}
|
||||
|
||||
[Conditional(SIMPLEWEB_LOG_ENABLED)]
|
||||
public static void Verbose(string msg, bool showColor = true)
|
||||
{
|
||||
if (level < Levels.verbose)
|
||||
return;
|
||||
|
||||
if (showColor)
|
||||
logger.Log(LogType.Log, $"VERBOSE: <color=blue>{msg}</color>");
|
||||
else
|
||||
logger.Log(LogType.Log, $"VERBOSE: {msg}");
|
||||
}
|
||||
|
||||
[Conditional(SIMPLEWEB_LOG_ENABLED)]
|
||||
public static void Info(string msg, bool showColor = true)
|
||||
{
|
||||
if (level < Levels.info)
|
||||
return;
|
||||
|
||||
if (showColor)
|
||||
logger.Log(LogType.Log, $"INFO: <color=blue>{msg}</color>");
|
||||
else
|
||||
logger.Log(LogType.Log, $"INFO: {msg}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An expected Exception was caught, useful for debugging but not important
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="showColor"></param>
|
||||
[Conditional(SIMPLEWEB_LOG_ENABLED)]
|
||||
public static void InfoException(Exception e)
|
||||
{
|
||||
if (level < Levels.info)
|
||||
return;
|
||||
|
||||
logger.Log(LogType.Log, $"INFO_EXCEPTION: <color=blue>{e.GetType().Name}</color> Message: {e.Message}");
|
||||
}
|
||||
|
||||
[Conditional(SIMPLEWEB_LOG_ENABLED), Conditional(DEBUG)]
|
||||
public static void Warn(string msg, bool showColor = true)
|
||||
{
|
||||
if (level < Levels.warn)
|
||||
return;
|
||||
|
||||
if (showColor)
|
||||
logger.Log(LogType.Warning, $"WARN: <color=orange>{msg}</color>");
|
||||
else
|
||||
logger.Log(LogType.Warning, $"WARN: {msg}");
|
||||
}
|
||||
|
||||
[Conditional(SIMPLEWEB_LOG_ENABLED), Conditional(DEBUG)]
|
||||
public static void Error(string msg, bool showColor = true)
|
||||
{
|
||||
if (level < Levels.error)
|
||||
return;
|
||||
|
||||
if (showColor)
|
||||
logger.Log(LogType.Error, $"ERROR: <color=red>{msg}</color>");
|
||||
else
|
||||
logger.Log(LogType.Error, $"ERROR: {msg}");
|
||||
}
|
||||
|
||||
public static void Exception(Exception e)
|
||||
{
|
||||
// always log Exceptions
|
||||
logger.Log(LogType.Error, $"EXCEPTION: <color=red>{e.GetType().Name}</color> Message: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cf1521098e04f74fbea0fe2aa0439f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5d05d71b09d2714b96ffe80bc3d2a77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,140 @@
|
||||
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);
|
||||
|
||||
public static bool NeedToReadShortLength(byte[] buffer)
|
||||
{
|
||||
byte lenByte = FirstLengthByte(buffer);
|
||||
|
||||
return lenByte >= Constants.UshortPayloadLength;
|
||||
}
|
||||
|
||||
public static int GetOpcode(byte[] buffer)
|
||||
{
|
||||
return buffer[0] & 0b0000_1111;
|
||||
}
|
||||
|
||||
public static int GetPayloadLength(byte[] buffer)
|
||||
{
|
||||
byte lenByte = FirstLengthByte(buffer);
|
||||
return GetMessageLength(buffer, 0, lenByte);
|
||||
}
|
||||
|
||||
public static void ValidateHeader(byte[] buffer, int maxLength, bool expectMask)
|
||||
{
|
||||
bool finished = (buffer[0] & 0b1000_0000) != 0; // has full message been sent
|
||||
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);
|
||||
|
||||
ThrowIfNotFinished(finished);
|
||||
ThrowIfMaskNotExpected(hasMask, expectMask);
|
||||
ThrowIfBadOpCode(opcode);
|
||||
|
||||
int msglen = GetMessageLength(buffer, 0, lenByte);
|
||||
|
||||
ThrowIfLengthZero(msglen);
|
||||
ThrowIfMsgLengthTooLong(msglen, maxLength);
|
||||
}
|
||||
|
||||
public static void ToggleMask(byte[] src, int sourceOffset, int messageLength, byte[] maskBuffer, int maskOffset)
|
||||
{
|
||||
ToggleMask(src, sourceOffset, src, sourceOffset, messageLength, maskBuffer, maskOffset);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 4 bytes long
|
||||
ushort value = 0;
|
||||
value |= (ushort)(buffer[offset + 2] << 8);
|
||||
value |= buffer[offset + 3];
|
||||
|
||||
return value;
|
||||
}
|
||||
else if (lenByte == Constants.UlongPayloadLength)
|
||||
{
|
||||
throw new InvalidDataException("Max length is longer than allowed in a single message");
|
||||
}
|
||||
else // is less than 126
|
||||
{
|
||||
// header is 2 bytes long
|
||||
return lenByte;
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
static void ThrowIfNotFinished(bool finished)
|
||||
{
|
||||
if (!finished)
|
||||
{
|
||||
throw new InvalidDataException("Full message should have been sent, if the full message wasn't sent it wasn't sent from this trasnport");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 2 = binary
|
||||
// 8 = close
|
||||
if (opcode != 2 && opcode != 8)
|
||||
{
|
||||
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 isnt used
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
static void ThrowIfMsgLengthTooLong(int msglen, int maxLength)
|
||||
{
|
||||
if (msglen > maxLength)
|
||||
{
|
||||
throw new InvalidDataException("Message length is greater than max length");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c1f218a2b16ca846aaf23260078e549
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,132 @@
|
||||
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("returned 0");
|
||||
}
|
||||
received += read;
|
||||
}
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
// if interupt is called we dont care about Exceptions
|
||||
Utils.CheckForInterupt();
|
||||
|
||||
// rethrow
|
||||
ae.Handle(e => false);
|
||||
}
|
||||
|
||||
if (received != length)
|
||||
{
|
||||
throw new ReadHelperException("returned 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("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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f4fa5d324e708c46a55810a97de75bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Loop(Config config)
|
||||
{
|
||||
(Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue<Message> queue, BufferPool _) = config;
|
||||
|
||||
byte[] readBuffer = new byte[Constants.HeaderSize + (expectMask ? Constants.MaskSize : 0) + maxMessageSize];
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
TcpClient client = conn.client;
|
||||
|
||||
while (client.Connected)
|
||||
{
|
||||
ReadOneMessage(config, readBuffer);
|
||||
}
|
||||
|
||||
Log.Info($"{conn} Not Connected");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// if interupted we dont care about other execptions
|
||||
Utils.CheckForInterupt();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException e) { Log.InfoException(e); }
|
||||
catch (ObjectDisposedException e) { Log.InfoException(e); }
|
||||
catch (ReadHelperException e)
|
||||
{
|
||||
// this could happen if client sends bad message
|
||||
Log.InfoException(e);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
// this could happen if wss client closes stream
|
||||
Log.Warn($"ReceiveLoop SocketException\n{e.Message}", false);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// this could happen if client disconnects
|
||||
Log.Warn($"ReceiveLoop IOException\n{e.Message}", false);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
Log.Error($"Invalid data from {conn}: {e.Message}");
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
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;
|
||||
|
||||
int offset = 0;
|
||||
// read 2
|
||||
offset = ReadHelper.Read(stream, buffer, offset, Constants.HeaderMinSize);
|
||||
// log after first blocking call
|
||||
Log.Verbose($"Message From {conn}");
|
||||
|
||||
if (MessageProcessor.NeedToReadShortLength(buffer))
|
||||
{
|
||||
offset = ReadHelper.Read(stream, buffer, offset, Constants.ShortLength);
|
||||
}
|
||||
|
||||
MessageProcessor.ValidateHeader(buffer, maxMessageSize, expectMask);
|
||||
|
||||
if (expectMask)
|
||||
{
|
||||
offset = ReadHelper.Read(stream, buffer, offset, Constants.MaskSize);
|
||||
}
|
||||
|
||||
int opcode = MessageProcessor.GetOpcode(buffer);
|
||||
int payloadLength = MessageProcessor.GetPayloadLength(buffer);
|
||||
|
||||
Log.Verbose($"Header ln:{payloadLength} op:{opcode} mask:{expectMask}");
|
||||
Log.DumpBuffer($"Raw Header", buffer, 0, offset);
|
||||
|
||||
int msgOffset = offset;
|
||||
offset = ReadHelper.Read(stream, buffer, offset, payloadLength);
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case 2:
|
||||
HandleArrayMessage(config, buffer, msgOffset, payloadLength);
|
||||
break;
|
||||
case 8:
|
||||
HandleCloseMessage(config, buffer, msgOffset, payloadLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleArrayMessage(Config config, byte[] buffer, int msgOffset, int payloadLength)
|
||||
{
|
||||
(Connection conn, int _, bool expectMask, ConcurrentQueue<Message> queue, BufferPool bufferPool) = config;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// dump after mask off
|
||||
Log.DumpBuffer($"Message", arrayBuffer);
|
||||
|
||||
queue.Enqueue(new Message(conn.connId, 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($"Message", buffer, msgOffset, payloadLength);
|
||||
|
||||
Log.Info($"Close: {GetCloseCode(buffer, msgOffset)} message:{GetCloseMessage(buffer, msgOffset, payloadLength)}");
|
||||
|
||||
conn.Dispose();
|
||||
}
|
||||
|
||||
static string GetCloseMessage(byte[] buffer, int msgOffset, int payloadLength)
|
||||
{
|
||||
return Encoding.UTF8.GetString(buffer, msgOffset + 2, payloadLength - 2);
|
||||
}
|
||||
|
||||
static int GetCloseCode(byte[] buffer, int msgOffset)
|
||||
{
|
||||
return buffer[msgOffset + 0] << 8 | buffer[msgOffset + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a26c2815f58431c4a98c158c8b655ffd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
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;
|
||||
|
||||
// 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 incase disconnect while send thread is starting
|
||||
if (client == null)
|
||||
return;
|
||||
|
||||
while (client.Connected)
|
||||
{
|
||||
// wait for message
|
||||
conn.sendPending.Wait();
|
||||
conn.sendPending.Reset();
|
||||
|
||||
while (conn.sendQueue.TryDequeue(out ArrayBuffer msg))
|
||||
{
|
||||
// check if connected before sending message
|
||||
if (!client.Connected) { Log.Info($"SendLoop {conn} not connected"); return; }
|
||||
|
||||
SendMessage(stream, writeBuffer, msg, setMask, maskHelper);
|
||||
msg.Release();
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info($"{conn} Not Connected");
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException e) { Log.InfoException(e); }
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
conn.Dispose();
|
||||
maskHelper?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
static void SendMessage(Stream stream, byte[] buffer, ArrayBuffer msg, bool setMask, MaskHelper maskHelper)
|
||||
{
|
||||
int msgLength = msg.count;
|
||||
int sendLength = WriteHeader(buffer, msgLength, setMask);
|
||||
|
||||
if (setMask)
|
||||
{
|
||||
sendLength = maskHelper.WriteMask(buffer, sendLength);
|
||||
}
|
||||
|
||||
msg.CopyTo(buffer, sendLength);
|
||||
sendLength += msgLength;
|
||||
|
||||
// dump before mask on
|
||||
Log.DumpBuffer("Send", buffer, 0, sendLength);
|
||||
|
||||
if (setMask)
|
||||
{
|
||||
int messageOffset = sendLength - msgLength;
|
||||
MessageProcessor.ToggleMask(buffer, messageOffset, msgLength, buffer, messageOffset - Constants.MaskSize);
|
||||
}
|
||||
|
||||
stream.Write(buffer, 0, sendLength);
|
||||
}
|
||||
|
||||
static int WriteHeader(byte[] buffer, int msgLength, bool setMask)
|
||||
{
|
||||
int sendLength = 0;
|
||||
const byte finished = 128;
|
||||
const byte byteOpCode = 2;
|
||||
|
||||
buffer[0] = finished | byteOpCode;
|
||||
sendLength++;
|
||||
|
||||
if (msgLength <= Constants.BytePayloadLength)
|
||||
{
|
||||
buffer[1] = (byte)msgLength;
|
||||
sendLength++;
|
||||
}
|
||||
else if (msgLength <= ushort.MaxValue)
|
||||
{
|
||||
buffer[1] = 126;
|
||||
buffer[2] = (byte)(msgLength >> 8);
|
||||
buffer[3] = (byte)msgLength;
|
||||
sendLength += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidDataException($"Trying to send a message larger than {ushort.MaxValue} bytes");
|
||||
}
|
||||
|
||||
if (setMask)
|
||||
{
|
||||
buffer[1] |= 0b1000_0000;
|
||||
}
|
||||
|
||||
return sendLength;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f87dd81736d9c824db67f808ac71841d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81ac8d35f28fab14b9edda5cd9d4fc86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4643ffb4cb0562847b1ae925d07e15b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user