using System; using System.Collections.Concurrent; using System.Collections.Generic; using Best.HTTP.Shared; using Best.HTTP.Shared.Extensions; using Best.HTTP.Shared.PlatformSupport.Memory; using Best.HTTP.Shared.Streams; #if !UNITY_WEBGL || UNITY_EDITOR using Best.WebSockets.Implementations.Frames; #endif namespace Best.WebSockets.Implementations { /// /// States of the underlying implementation's state. /// public enum WebSocketStates : byte { Connecting = 0, Open = 1, Closing = 2, Closed = 3, Unknown }; public delegate void OnWebSocketOpenDelegate(WebSocket webSocket); public delegate void OnWebSocketMessageDelegate(WebSocket webSocket, string message); public delegate void OnWebSocketBinaryNoAllocDelegate(WebSocket webSocket, BufferSegment data); public delegate void OnWebSocketClosedDelegate(WebSocket webSocket, WebSocketStatusCodes code, string message); #if !UNITY_WEBGL || UNITY_EDITOR public delegate void OnWebSocketIncompleteFrameDelegate(WebSocket webSocket, WebSocketFrameReader frame); #endif /// /// Abstract class for concrete websocket communication implementations. /// public abstract class WebSocketBaseImplementation { /// /// Capacity of the RTT buffer where the latencies are kept. /// public static int RTTBufferCapacity = 5; public const string Timing_Name = "Websocket"; public virtual WebSocketStates State { get; protected set; } #if UNITY_WEBGL && !UNITY_EDITOR public virtual bool IsOpen { get; protected set; } public virtual int BufferedAmount { get; protected set; } #else public bool IsOpen => this.State == WebSocketStates.Open; public int BufferedAmount { get => this._bufferedAmount; } protected volatile int _bufferedAmount; public HTTP.HTTPRequest InternalRequest { get { if (this._internalRequest == null) CreateInternalRequest(); return this._internalRequest; } } protected HTTP.HTTPRequest _internalRequest; public virtual int Latency { get; protected set; } public virtual DateTime LastMessageReceived { get; protected set; } /// /// A circular buffer to store the last N rtt times calculated by the pong messages. /// protected CircularBuffer rtts = new CircularBuffer(WebSocketBaseImplementation.RTTBufferCapacity); /// /// When we sent out the last ping. /// protected DateTime lastPing = DateTime.MinValue; protected bool waitingForPong = false; protected List IncompleteFrames = new List(); protected PeekableIncomingSegmentStream incomingSegmentStream = new PeekableIncomingSegmentStream(); protected ConcurrentQueue CompletedFrames = new ConcurrentQueue(); protected ConcurrentQueue frames = new ConcurrentQueue(); /// /// True if we sent out a Close message to the server /// internal volatile bool _closeSent; internal volatile bool _closeReceived; #endif public WebSocket Parent { get; } public Uri Uri { get; protected set; } public string Origin { get; } public string Protocol { get; } public WebSocketBaseImplementation(WebSocket parent, Uri uri, string origin, string protocol) { this.Parent = parent; this.Uri = uri; this.Origin = origin; this.Protocol = protocol; #if !UNITY_WEBGL || UNITY_EDITOR this.LastMessageReceived = DateTime.MinValue; // Set up some default values. this.Parent.PingFrequency = TimeSpan.FromMilliseconds(10_000); this.Parent.CloseAfterNoMessage = TimeSpan.FromSeconds(2); #endif } public abstract void StartOpen(); public abstract void StartClose(WebSocketStatusCodes code, string message); public abstract void Send(string message); public abstract void Send(byte[] buffer); public abstract void Send(byte[] buffer, ulong offset, ulong count); public abstract void SendAsBinary(BufferSegment data); public abstract void SendAsText(BufferSegment data); #if !UNITY_WEBGL || UNITY_EDITOR protected void ParseExtensionResponse(HTTP.HTTPResponse resp) { if (this.Parent.Extensions != null) { for (int i = 0; i < this.Parent.Extensions.Length; ++i) { var ext = this.Parent.Extensions[i]; try { if (ext != null && !ext.ParseNegotiation(resp)) this.Parent.Extensions[i] = null; // Keep extensions only that successfully negotiated } catch (Exception ex) { HTTPManager.Logger.Exception("WebSocketBaseImplementation", "ParseNegotiation", ex, this.Parent.Context); // Do not try to use a defective extension in the future this.Parent.Extensions[i] = null; } } } } protected abstract void CreateInternalRequest(); /// /// It will send the given frame to the server. /// public abstract void Send(WebSocketFrame frame); protected virtual void Cleanup() { for (int i = 0; i < this.IncompleteFrames.Count; ++i) { var frame = this.IncompleteFrames[i]; BufferPool.Release(frame.Data); } this.IncompleteFrames.Clear(); this.Parent.DisposeExtensions(); } protected int CalculateLatency() { if (this.rtts.Count == 0) return 0; int sumLatency = 0; for (int i = 0; i < this.rtts.Count; ++i) sumLatency += this.rtts[i]; return sumLatency / this.rtts.Count; } public static bool CanReadFullFrame(PeekableStream stream) { if (stream.Length < 2) return false; stream.BeginPeek(); int headerLength = 2; int header = stream.PeekByte(); if (header == -1) return false; int maskAndLength = stream.PeekByte(); if (maskAndLength == -1) return false; // The second byte is the Mask Bit and the length of the payload data var HasMask = (maskAndLength & 0x80) != 0; if (HasMask) throw new NotSupportedException("Server-sent frames must not be masked!"); // if 0-125, that is the payload length. int payloadLength = (int)(maskAndLength & 127); // If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length. if (payloadLength == 126) { byte[] rawLen = BufferPool.Get(2, true); for (int i = 0; i < 2; i++) { int data = stream.PeekByte(); if (data < 0) return false; rawLen[i] = (byte)data; } if (BitConverter.IsLittleEndian) Array.Reverse(rawLen, 0, 2); payloadLength = (int)BitConverter.ToUInt16(rawLen, 0); headerLength += 2; BufferPool.Release(rawLen); } else if (payloadLength == 127) { // If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the // most significant bit MUST be 0) are the payload length. byte[] rawLen = BufferPool.Get(8, true); for (int i = 0; i < 8; i++) { int data = stream.PeekByte(); if (data < 0) return false; rawLen[i] = (byte)data; } if (BitConverter.IsLittleEndian) Array.Reverse(rawLen, 0, 8); payloadLength = (int)BitConverter.ToUInt64(rawLen, 0); headerLength += 8; BufferPool.Release(rawLen); } return stream.Length >= (headerLength + payloadLength); } #endif } }