/// <summary> /// Receive web socket result /// </summary> /// <param name="buffer">The buffer to copy data into</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>The web socket result details</returns> public override async Task <WebSocketReceiveResult> ReceiveAsync(ArraySegment <byte> buffer, CancellationToken cancellationToken) { try { // we may receive control frames so reading needs to happen in an infinite loop while (true) { // allow this operation to be cancelled from iniside OR outside this instance using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_internalReadCts.Token, cancellationToken)) { WebSocketFrame frame = null; try { frame = await WebSocketFrameReader.ReadAsync(_stream, buffer, linkedCts.Token); } catch (InternalBufferOverflowException ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.MessageTooBig, "Frame too large to fit in buffer. Use message fragmentation", ex); throw; } catch (ArgumentOutOfRangeException ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, "Payload length out of range", ex); throw; } catch (EndOfStreamException ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InvalidPayloadData, "Unexpected end of stream encountered", ex); throw; } catch (OperationCanceledException ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.EndpointUnavailable, "Operation cancelled", ex); throw; } catch (Exception ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InternalServerError, "Error reading WebSocket frame", ex); throw; } switch (frame.OpCode) { case WebSocketOpCode.ConnectionClose: return(await RespondToCloseFrame(frame, buffer, linkedCts.Token)); case WebSocketOpCode.Ping: ArraySegment <byte> pingPayload = new ArraySegment <byte>(buffer.Array, buffer.Offset, frame.Count); await SendPongAsync(pingPayload, linkedCts.Token); break; case WebSocketOpCode.Pong: ArraySegment <byte> pongBuffer = new ArraySegment <byte>(buffer.Array, frame.Count, buffer.Offset); Pong?.Invoke(this, new PongEventArgs(pongBuffer)); break; case WebSocketOpCode.TextFrame: if (!frame.IsFinBitSet) { // continuation frames will follow, record the message type Text _continuationFrameMessageType = WebSocketMessageType.Text; } return(new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Text, frame.IsFinBitSet)); case WebSocketOpCode.BinaryFrame: if (!frame.IsFinBitSet) { // continuation frames will follow, record the message type Binary _continuationFrameMessageType = WebSocketMessageType.Binary; } return(new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Binary, frame.IsFinBitSet)); case WebSocketOpCode.ContinuationFrame: return(new WebSocketReceiveResult(frame.Count, _continuationFrameMessageType, frame.IsFinBitSet)); default: Exception ex = new NotSupportedException($"Unknown WebSocket opcode {frame.OpCode}"); await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, ex.Message, ex); throw ex; } } } } catch (Exception catchAll) { // Most exceptions will be caught closer to their source to send an appropriate close message (and set the WebSocketState) // However, if an unhandled exception is encountered and a close message not sent then send one here if (_state == WebSocketState.Open) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InternalServerError, "Unexpected error reading from WebSocket", catchAll); } throw; } }
/// <summary> /// Read a WebSocket frame from the stream /// </summary> /// <param name="fromStream">The stream to read from</param> /// <param name="intoBuffer">The buffer to read into</param> /// <param name="cancellationToken">the cancellation token</param> /// <returns>A websocket frame</returns> public static async Task <WebSocketReadCursor> ReadAsync(System.IO.Stream fromStream, ArraySegment <byte> intoBuffer, CancellationToken cancellationToken) { // allocate a small buffer to read small chunks of data from the stream var smallBuffer = new ArraySegment <byte>(new byte[8]); await BinaryReaderWriter.ReadExactly(2, fromStream, smallBuffer, cancellationToken); byte byte1 = smallBuffer.Array[0]; byte byte2 = smallBuffer.Array[1]; // process first byte byte finBitFlag = 0x80; byte opCodeFlag = 0x0F; bool isFinBitSet = (byte1 & finBitFlag) == finBitFlag; WebSocketOpCode opCode = (WebSocketOpCode)(byte1 & opCodeFlag); // read and process second byte byte maskFlag = 0x80; bool isMaskBitSet = (byte2 & maskFlag) == maskFlag; uint len = await ReadLength(byte2, smallBuffer, fromStream, cancellationToken); int count = (int)len; var minCount = CalculateNumBytesToRead(count, intoBuffer.Count); ArraySegment <byte> maskKey = new ArraySegment <byte>(); try { // use the masking key to decode the data if needed if (isMaskBitSet) { maskKey = new ArraySegment <byte>(smallBuffer.Array, 0, WebSocketFrameCommon.MaskKeyLength); await BinaryReaderWriter.ReadExactly(maskKey.Count, fromStream, maskKey, cancellationToken); await BinaryReaderWriter.ReadExactly(minCount, fromStream, intoBuffer, cancellationToken); ArraySegment <byte> payloadToMask = new ArraySegment <byte>(intoBuffer.Array, intoBuffer.Offset, minCount); WebSocketFrameCommon.ToggleMask(maskKey, payloadToMask); } else { await BinaryReaderWriter.ReadExactly(minCount, fromStream, intoBuffer, cancellationToken); } } catch (InternalBufferOverflowException e) { throw new InternalBufferOverflowException( $"Supplied buffer too small to read {0} bytes from {Enum.GetName(typeof(WebSocketOpCode), opCode)} frame", e); } WebSocketFrame frame; if (opCode == WebSocketOpCode.ConnectionClose) { frame = DecodeCloseFrame(isFinBitSet, opCode, count, intoBuffer, maskKey); } else { // note that by this point the payload will be populated frame = new WebSocketFrame(isFinBitSet, opCode, count, maskKey); } return(new WebSocketReadCursor(frame, minCount, count - minCount)); }