예제 #1
0
        private static void DrainActionQueue(SafeHandle webSocketHandle, ActionQueue actionQueue)
        {
            Debug.Assert(webSocketHandle != null && !webSocketHandle.IsInvalid,
                         "'webSocketHandle' MUST NOT be NULL or INVALID.");

            IntPtr actionContext;
            Action action;

            while (true)
            {
                Interop.WebSocket.Buffer[] dataBuffers = new Interop.WebSocket.Buffer[1];
                uint dataBufferCount = 1;
                int  errorCode       = Interop.WebSocket.WebSocketGetAction(webSocketHandle,
                                                                            actionQueue,
                                                                            dataBuffers,
                                                                            ref dataBufferCount,
                                                                            out action,
                                                                            out _,
                                                                            out _,
                                                                            out actionContext);

                if (!Succeeded(errorCode))
                {
                    Debug.Assert(errorCode == 0, "'errorCode' MUST be 0.");
                    return;
                }

                if (action == Action.NoAction)
                {
                    return;
                }

                Interop.WebSocket.WebSocketCompleteAction(webSocketHandle, actionContext, 0);
            }
        }
예제 #2
0
        internal static void UnwrapWebSocketBuffer(Interop.WebSocket.Buffer buffer,
                                                   WebSocketProtocolComponent.BufferType bufferType,
                                                   out IntPtr bufferData,
                                                   out uint bufferLength)
        {
            bufferData   = IntPtr.Zero;
            bufferLength = 0;

            switch (bufferType)
            {
            case WebSocketProtocolComponent.BufferType.Close:
                bufferData   = buffer.CloseStatus.ReasonData;
                bufferLength = buffer.CloseStatus.ReasonLength;
                break;

            case WebSocketProtocolComponent.BufferType.None:
            case WebSocketProtocolComponent.BufferType.BinaryFragment:
            case WebSocketProtocolComponent.BufferType.BinaryMessage:
            case WebSocketProtocolComponent.BufferType.UTF8Fragment:
            case WebSocketProtocolComponent.BufferType.UTF8Message:
            case WebSocketProtocolComponent.BufferType.PingPong:
            case WebSocketProtocolComponent.BufferType.UnsolicitedPong:
                bufferData   = buffer.Data.BufferData;
                bufferLength = buffer.Data.BufferLength;
                break;

            default:
                Debug.Assert(false,
                             string.Format(CultureInfo.InvariantCulture,
                                           "BufferType '{0}' is invalid/unknown.",
                                           bufferType));
                break;
            }
        }
예제 #3
0
        internal ArraySegment <byte> ConvertNativeBuffer(WebSocketProtocolComponent.Action action,
                                                         Interop.WebSocket.Buffer buffer,
                                                         WebSocketProtocolComponent.BufferType bufferType)
        {
            ThrowIfDisposed();

            IntPtr bufferData;
            uint   bufferLength;

            UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferLength);

            if (bufferData == IntPtr.Zero)
            {
                return(ArraySegment <byte> .Empty);
            }

            if (this.IsNativeBuffer(bufferData, bufferLength))
            {
                return(new ArraySegment <byte>(_internalBuffer.Array,
                                               this.GetOffset(bufferData),
                                               (int)bufferLength));
            }

            Debug.Assert(false, "'buffer' MUST reference a memory segment within the pinned InternalBuffer.");
            // Indicates a violation in the contract with native Websocket.dll and could indicate
            // memory corruption because the internal buffer is shared between managed and native code
            throw new AccessViolationException();
        }
예제 #4
0
        // This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation
        internal bool IsPinnedSendPayloadBuffer(Interop.WebSocket.Buffer buffer,
                                                WebSocketProtocolComponent.BufferType bufferType)
        {
            if (_sendBufferState != SendBufferState.SendPayloadSpecified)
            {
                return(false);
            }

            IntPtr bufferData;
            uint   bufferSize;

            UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferSize);

            long nativeBufferStartAddress = bufferData.ToInt64();
            long nativeBufferEndAddress   = nativeBufferStartAddress + bufferSize;

            return(nativeBufferStartAddress >= _pinnedSendBufferStartAddress &&
                   nativeBufferEndAddress >= _pinnedSendBufferStartAddress &&
                   nativeBufferStartAddress <= _pinnedSendBufferEndAddress &&
                   nativeBufferEndAddress <= _pinnedSendBufferEndAddress);
        }
예제 #5
0
        internal void ConvertCloseBuffer(WebSocketProtocolComponent.Action action,
                                         Interop.WebSocket.Buffer buffer,
                                         out WebSocketCloseStatus closeStatus,
                                         out string reason)
        {
            ThrowIfDisposed();
            IntPtr bufferData;
            uint   bufferLength;

            closeStatus = (WebSocketCloseStatus)buffer.CloseStatus.CloseStatus;

            UnwrapWebSocketBuffer(buffer, WebSocketProtocolComponent.BufferType.Close, out bufferData, out bufferLength);

            if (bufferData == IntPtr.Zero)
            {
                reason = null;
            }
            else
            {
                ArraySegment <byte> reasonBlob;
                if (this.IsNativeBuffer(bufferData, bufferLength))
                {
                    reasonBlob = new ArraySegment <byte>(_internalBuffer.Array,
                                                         this.GetOffset(bufferData),
                                                         (int)bufferLength);
                }
                else
                {
                    Debug.Assert(false, "'buffer' MUST reference a memory segment within the pinned InternalBuffer.");
                    // Indicates a violation in the contract with native Websocket.dll and could indicate
                    // memory corruption because the internal buffer is shared between managed and native code
                    throw new AccessViolationException();
                }

                // No need to wrap DecoderFallbackException for invalid UTF8 chacters, because
                // Encoding.UTF8 will not throw but replace invalid characters instead.
                reason = Encoding.UTF8.GetString(reasonBlob.Array, reasonBlob.Offset, reasonBlob.Count);
            }
        }
예제 #6
0
        // This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation
        internal ArraySegment <byte> ConvertPinnedSendPayloadFromNative(Interop.WebSocket.Buffer buffer,
                                                                        WebSocketProtocolComponent.BufferType bufferType)
        {
            if (!IsPinnedSendPayloadBuffer(buffer, bufferType))
            {
                // Indicates a violation in the API contract that could indicate
                // memory corruption because the pinned sendbuffer is shared between managed and native code
                throw new AccessViolationException();
            }

            Debug.Assert(Marshal.UnsafeAddrOfPinnedArrayElement(_pinnedSendBuffer.Array,
                                                                _pinnedSendBuffer.Offset).ToInt64() == _pinnedSendBufferStartAddress,
                         "'m_PinnedSendBuffer.Array' MUST be pinned during the entire send operation.");

            IntPtr bufferData;
            uint   bufferSize;

            UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferSize);

            int internalOffset = (int)(bufferData.ToInt64() - _pinnedSendBufferStartAddress);

            return(new ArraySegment <byte>(_pinnedSendBuffer.Array, _pinnedSendBuffer.Offset + internalOffset, (int)bufferSize));
        }
예제 #7
0
        internal static void WebSocketSend(WebSocketBase webSocket,
                                           BufferType bufferType,
                                           Interop.WebSocket.Buffer buffer)
        {
            Debug.Assert(webSocket != null,
                         "'webSocket' MUST NOT be NULL or INVALID.");
            Debug.Assert(webSocket.SessionHandle != null && !webSocket.SessionHandle.IsInvalid,
                         "'webSocket.SessionHandle' MUST NOT be NULL or INVALID.");

            ThrowIfSessionHandleClosed(webSocket);

            int errorCode;

            try
            {
                errorCode = Interop.WebSocket.WebSocketSend_Raw(webSocket.SessionHandle, bufferType, ref buffer, IntPtr.Zero);
            }
            catch (ObjectDisposedException innerException)
            {
                throw ConvertObjectDisposedException(webSocket, innerException);
            }

            ThrowOnError(errorCode);
        }
예제 #8
0
        internal void ValidateNativeBuffers(WebSocketProtocolComponent.Action action,
                                            WebSocketProtocolComponent.BufferType bufferType,
                                            Interop.WebSocket.Buffer[] dataBuffers,
                                            uint dataBufferCount)
        {
            Debug.Assert(dataBufferCount <= (uint)int.MaxValue,
                         "'dataBufferCount' MUST NOT be bigger than Int32.MaxValue.");
            Debug.Assert(dataBuffers != null, "'dataBuffers' MUST NOT be NULL.");

            ThrowIfDisposed();
            if (dataBufferCount > dataBuffers.Length)
            {
                Debug.Assert(false, "'dataBufferCount' MUST NOT be bigger than 'dataBuffers.Length'.");
                // Indicates a violation in the contract with native Websocket.dll and could indicate
                // memory corruption because the internal buffer is shared between managed and native code
                throw new AccessViolationException();
            }

            int  count          = dataBuffers.Length;
            bool isSendActivity = action == WebSocketProtocolComponent.Action.IndicateSendComplete ||
                                  action == WebSocketProtocolComponent.Action.SendToNetwork;

            if (isSendActivity)
            {
                count = (int)dataBufferCount;
            }

            bool nonZeroBufferFound = false;

            for (int i = 0; i < count; i++)
            {
                Interop.WebSocket.Buffer dataBuffer = dataBuffers[i];

                IntPtr bufferData;
                uint   bufferLength;
                UnwrapWebSocketBuffer(dataBuffer, bufferType, out bufferData, out bufferLength);

                if (bufferData == IntPtr.Zero)
                {
                    continue;
                }

                nonZeroBufferFound = true;

                bool isPinnedSendPayloadBuffer = IsPinnedSendPayloadBuffer(dataBuffer, bufferType);

                if (bufferLength > GetMaxBufferSize())
                {
                    if (!isSendActivity || !isPinnedSendPayloadBuffer)
                    {
                        Debug.Assert(false,
                                     "'dataBuffer.BufferLength' MUST NOT be bigger than 'm_ReceiveBufferSize' and 'm_SendBufferSize'.");
                        // Indicates a violation in the contract with native Websocket.dll and could indicate
                        // memory corruption because the internal buffer is shared between managed and native code
                        throw new AccessViolationException();
                    }
                }

                if (!isPinnedSendPayloadBuffer && !IsNativeBuffer(bufferData, bufferLength))
                {
                    Debug.Assert(false,
                                 "WebSocketGetAction MUST return a pointer within the pinned internal buffer.");
                    // Indicates a violation in the contract with native Websocket.dll and could indicate
                    // memory corruption because the internal buffer is shared between managed and native code
                    throw new AccessViolationException();
                }
            }

            if (!nonZeroBufferFound &&
                action != WebSocketProtocolComponent.Action.NoAction &&
                action != WebSocketProtocolComponent.Action.IndicateReceiveComplete &&
                action != WebSocketProtocolComponent.Action.IndicateSendComplete)
            {
                Debug.Assert(false, "At least one 'dataBuffer.Buffer' MUST NOT be NULL.");
            }
        }
예제 #9
0
                protected override Nullable<Interop.WebSocket.Buffer> CreateBuffer(Nullable<ArraySegment<byte>> buffer)
                {
                    Debug.Assert(buffer == null, "'buffer' MUST BE NULL.");
                    _webSocket.ThrowIfDisposed();
                    _webSocket.ThrowIfPendingException();

                    if (CloseStatus == WebSocketCloseStatus.Empty)
                    {
                        return null;
                    }

                    Interop.WebSocket.Buffer payloadBuffer = new Interop.WebSocket.Buffer();
                    if (CloseReason != null)
                    {
                        byte[] blob = Encoding.UTF8.GetBytes(CloseReason);
                        Debug.Assert(blob.Length <= WebSocketValidate.MaxControlFramePayloadLength,
                            "The close reason is too long.");
                        ArraySegment<byte> closeBuffer = new ArraySegment<byte>(blob, 0, Math.Min(WebSocketValidate.MaxControlFramePayloadLength, blob.Length));
                        _webSocket._internalBuffer.PinSendBuffer(closeBuffer, out _BufferHasBeenPinned);
                        payloadBuffer.CloseStatus.ReasonData = _webSocket._internalBuffer.ConvertPinnedSendPayloadToNative(closeBuffer);
                        payloadBuffer.CloseStatus.ReasonLength = (uint)closeBuffer.Count;
                    }

                    payloadBuffer.CloseStatus.CloseStatus = (ushort)CloseStatus;
                    return payloadBuffer;
                }
예제 #10
0
                protected virtual Nullable<Interop.WebSocket.Buffer> CreateBuffer(Nullable<ArraySegment<byte>> buffer)
                {
                    if (buffer == null)
                    {
                        return null;
                    }

                    Interop.WebSocket.Buffer payloadBuffer;
                    payloadBuffer = new Interop.WebSocket.Buffer();
                    _webSocket._internalBuffer.PinSendBuffer(buffer.Value, out _BufferHasBeenPinned);
                    payloadBuffer.Data.BufferData = _webSocket._internalBuffer.ConvertPinnedSendPayloadToNative(buffer.Value);
                    payloadBuffer.Data.BufferLength = (uint)buffer.Value.Count;
                    return payloadBuffer;
                }
예제 #11
0
            internal async Task<WebSocketReceiveResult> Process(Nullable<ArraySegment<byte>> buffer,
                CancellationToken cancellationToken)
            {
                Debug.Assert(BufferCount >= 1 && BufferCount <= 2, "'bufferCount' MUST ONLY BE '1' or '2'.");

                bool sessionHandleLockTaken = false;
                AsyncOperationCompleted = false;
                ReceiveResult = null;
                try
                {
                    Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                    _webSocket.ThrowIfPendingException();
                    Initialize(buffer, cancellationToken);

                    while (ShouldContinue(cancellationToken))
                    {
                        WebSocketProtocolComponent.Action action;
                        WebSocketProtocolComponent.BufferType bufferType;

                        bool completed = false;

                        while (!completed)
                        {
                            Interop.WebSocket.Buffer[] dataBuffers =
                                new Interop.WebSocket.Buffer[BufferCount];
                            uint dataBufferCount = (uint)BufferCount;
                            IntPtr actionContext;

                            _webSocket.ThrowIfDisposed();
                            WebSocketProtocolComponent.WebSocketGetAction(_webSocket,
                                ActionQueue,
                                dataBuffers,
                                ref dataBufferCount,
                                out action,
                                out bufferType,
                                out actionContext);

                            switch (action)
                            {
                                case WebSocketProtocolComponent.Action.NoAction:
                                    if (ProcessAction_NoAction())
                                    {
                                        // A close frame was received

                                        Debug.Assert(ReceiveResult.Count == 0, "'receiveResult.Count' MUST be 0.");
                                        Debug.Assert(ReceiveResult.CloseStatus != null, "'receiveResult.CloseStatus' MUST NOT be NULL for message type 'Close'.");
                                        bool thisLockTaken = false;
                                        try
                                        {
                                            if (_webSocket.StartOnCloseReceived(ref thisLockTaken))
                                            {
                                                // If StartOnCloseReceived returns true the WebSocket close handshake has been completed
                                                // so there is no need to retake the SessionHandle-lock.
                                                // _ThisLock lock is guaranteed to be taken by StartOnCloseReceived when returning true
                                                ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                                                bool callCompleteOnCloseCompleted = false;

                                                try
                                                {
                                                    callCompleteOnCloseCompleted = await _webSocket.StartOnCloseCompleted(
                                                        thisLockTaken, sessionHandleLockTaken, cancellationToken).SuppressContextFlow();
                                                }
                                                catch (Exception)
                                                {
                                                    // If an exception is thrown we know that the locks have been released,
                                                    // because we enforce IWebSocketStream.CloseNetworkConnectionAsync to yield
                                                    _webSocket.ResetFlagAndTakeLock(_webSocket._thisLock, ref thisLockTaken);
                                                    throw;
                                                }

                                                if (callCompleteOnCloseCompleted)
                                                {
                                                    _webSocket.ResetFlagAndTakeLock(_webSocket._thisLock, ref thisLockTaken);
                                                    _webSocket.FinishOnCloseCompleted();
                                                }
                                            }
                                            _webSocket.FinishOnCloseReceived(ReceiveResult.CloseStatus.Value, ReceiveResult.CloseStatusDescription);
                                        }
                                        finally
                                        {
                                            if (thisLockTaken)
                                            {
                                                ReleaseLock(_webSocket._thisLock, ref thisLockTaken);
                                            }
                                        }
                                    }
                                    completed = true;
                                    break;
                                case WebSocketProtocolComponent.Action.IndicateReceiveComplete:
                                    ProcessAction_IndicateReceiveComplete(buffer,
                                        bufferType,
                                        action,
                                        dataBuffers,
                                        dataBufferCount,
                                        actionContext);
                                    break;
                                case WebSocketProtocolComponent.Action.ReceiveFromNetwork:
                                    int count = 0;
                                    try
                                    {
                                        ArraySegment<byte> payload = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType);

                                        ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                                        WebSocketValidate.ThrowIfConnectionAborted(_webSocket._innerStream, true);
                                        try
                                        {
                                            Task<int> readTask = _webSocket._innerStream.ReadAsync(payload.Array,
                                                payload.Offset,
                                                payload.Count,
                                                cancellationToken);
                                            count = await readTask.SuppressContextFlow();
                                            _webSocket._keepAliveTracker.OnDataReceived();
                                        }
                                        catch (ObjectDisposedException objectDisposedException)
                                        {
                                            throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, objectDisposedException);
                                        }
                                        catch (NotSupportedException notSupportedException)
                                        {
                                            throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, notSupportedException);
                                        }
                                        Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                                        _webSocket.ThrowIfPendingException();
                                        // If the client unexpectedly closed the socket we throw an exception as we didn't get any close message
                                        if (count == 0)
                                        {
                                            throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely);
                                        }
                                    }
                                    finally
                                    {
                                        WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket,
                                            actionContext,
                                            count);
                                    }
                                    break;
                                case WebSocketProtocolComponent.Action.IndicateSendComplete:
                                    WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket, actionContext, 0);
                                    AsyncOperationCompleted = true;
                                    ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                                    await _webSocket._innerStream.FlushAsync().SuppressContextFlow();
                                    Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                                    break;
                                case WebSocketProtocolComponent.Action.SendToNetwork:
                                    int bytesSent = 0;
                                    try
                                    {
                                        if (_webSocket.State != WebSocketState.CloseSent ||
                                            (bufferType != WebSocketProtocolComponent.BufferType.PingPong &&
                                            bufferType != WebSocketProtocolComponent.BufferType.UnsolicitedPong))
                                        {
                                            if (dataBufferCount == 0)
                                            {
                                                break;
                                            }

                                            List<ArraySegment<byte>> sendBuffers = new List<ArraySegment<byte>>((int)dataBufferCount);
                                            int sendBufferSize = 0;
                                            ArraySegment<byte> framingBuffer = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType);
                                            sendBuffers.Add(framingBuffer);
                                            sendBufferSize += framingBuffer.Count;

                                            // There can be at most 2 dataBuffers
                                            // - one for the framing header and one for the payload
                                            if (dataBufferCount == 2)
                                            {
                                                ArraySegment<byte> payload;

                                                // The second buffer might be from the pinned send payload buffer (1) or from the
                                                // internal native buffer (2).  In the case of a PONG response being generated, the buffer
                                                // would be from (2).  Even if the payload is from a WebSocketSend operation, the buffer
                                                // might be (1) only if no buffer copies were needed (in the case of no masking, for example).
                                                // Or it might be (2).  So, we need to check.
                                                if (_webSocket._internalBuffer.IsPinnedSendPayloadBuffer(dataBuffers[1], bufferType))
                                                {
                                                    payload = _webSocket._internalBuffer.ConvertPinnedSendPayloadFromNative(dataBuffers[1], bufferType);
                                                }
                                                else
                                                {
                                                    payload = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[1], bufferType);
                                                }

                                                sendBuffers.Add(payload);
                                                sendBufferSize += payload.Count;
                                            }

                                            ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                                            WebSocketValidate.ThrowIfConnectionAborted(_webSocket._innerStream, false);
                                            await _webSocket.SendFrameAsync(sendBuffers, cancellationToken).SuppressContextFlow();
                                            Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                                            _webSocket.ThrowIfPendingException();
                                            bytesSent += sendBufferSize;
                                            _webSocket._keepAliveTracker.OnDataSent();
                                        }
                                    }
                                    finally
                                    {
                                        WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket,
                                            actionContext,
                                            bytesSent);
                                    }

                                    break;
                                default:
                                    string assertMessage = string.Format(CultureInfo.InvariantCulture,
                                        "Invalid action '{0}' returned from WebSocketGetAction.",
                                        action);
                                    Debug.Assert(false, assertMessage);
                                    throw new InvalidOperationException();
                            }
                        }

                        // WebSocketGetAction has returned NO_ACTION. In general, WebSocketGetAction will return
                        // NO_ACTION if there is no work item available to process at the current moment. But
                        // there could be work items on the queue still.  Those work items can't be returned back
                        // until the current work item (being done by another thread) is complete.
                        //
                        // It's possible that another thread might be finishing up an async operation and needs
                        // to call WebSocketCompleteAction. Once that happens, calling WebSocketGetAction on this
                        // thread might return something else to do. This happens, for example, if the RECEIVE thread
                        // ends up having to begin sending out a PONG response (due to it receiving a PING) and the
                        // current SEND thread has posted a WebSocketSend but it can't be processed yet until the
                        // RECEIVE thread has finished sending out the PONG response.
                        // 
                        // So, we need to release the lock briefly to give the other thread a chance to finish
                        // processing.  We won't actually exit this outter loop and return from this async method
                        // until the caller's async operation has been fully completed.
                        ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                        Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                    }
                }
                finally
                {
                    Cleanup();
                    ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
                }

                return ReceiveResult;
            }
예제 #12
0
        private static void DrainActionQueue(SafeHandle webSocketHandle, ActionQueue actionQueue)
        {
            Debug.Assert(webSocketHandle != null && !webSocketHandle.IsInvalid,
                "'webSocketHandle' MUST NOT be NULL or INVALID.");

            IntPtr actionContext;
            IntPtr dummy;
            Action action;
            BufferType bufferType;

            while (true)
            {
                Interop.WebSocket.Buffer[] dataBuffers = new Interop.WebSocket.Buffer[1];
                uint dataBufferCount = 1;
                int errorCode = Interop.WebSocket.WebSocketGetAction(webSocketHandle,
                    actionQueue,
                    dataBuffers,
                    ref dataBufferCount,
                    out action,
                    out bufferType,
                    out dummy,
                    out actionContext);

                if (!Succeeded(errorCode))
                {
                    Debug.Assert(errorCode == 0, "'errorCode' MUST be 0.");
                    return;
                }

                if (action == Action.NoAction)
                {
                    return;
                }

                Interop.WebSocket.WebSocketCompleteAction(webSocketHandle, actionContext, 0);
            }
        }