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); } }
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; } }
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(); }
// 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); }
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); } }
// 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)); }
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); }
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."); } }
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; }
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; }
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; }
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); } }