Example #1
0
        private static void PingPayloadWriter(WritableBuffer output, Span <byte> maskingKey, int payloadLength, DateTime timestamp)
        {
            var payload = output.Memory.Slice(0, payloadLength);

            // TODO: Don't put this string on the heap? Is there a way to do that without re-implementing ToString?
            // Ideally we'd like to render the string directly to the output buffer.
            var str = timestamp.ToString("O", CultureInfo.InvariantCulture);

            ArraySegment <byte> buffer;

            if (payload.TryGetArray(out buffer))
            {
                // Fast path - Write the encoded bytes directly out.
                Encoding.UTF8.GetBytes(str, 0, str.Length, buffer.Array, buffer.Offset);
            }
            else
            {
                // TODO: Could use TryGetPointer, GetBytes does take a byte*, but it seems like just waiting until we have a version that uses Span is best.
                // Slow path - Allocate a heap buffer for the encoded bytes before writing them out.
                payload.Span.Set(Encoding.UTF8.GetBytes(str));
            }

            if (maskingKey.Length > 0)
            {
                MaskingUtilities.ApplyMask(payload.Span, maskingKey);
            }

            output.Advance(payloadLength);
        }
Example #2
0
        private static void AppendPayloadWriter(WritableBuffer output, Span <byte> maskingKey, int payloadLength, ReadableBuffer payload)
        {
            if (maskingKey.Length > 0)
            {
                // Mask the payload in it's own buffer
                MaskingUtilities.ApplyMask(ref payload, maskingKey);
            }

            output.Append(payload);
        }
Example #3
0
        private static void CloseResultPayloadWriter(WritableBuffer output, Span <byte> maskingKey, int payloadLength, WebSocketCloseResult result)
        {
            // Write the close payload out
            var payload = output.Memory.Slice(0, payloadLength).Span;

            result.WriteTo(ref output);

            if (maskingKey.Length > 0)
            {
                MaskingUtilities.ApplyMask(payload, maskingKey);
            }
        }
Example #4
0
        private async Task <WebSocketCloseResult> ReceiveLoop(Func <WebSocketFrame, object, Task> messageHandler, object state, CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                // WebSocket Frame layout (https://tools.ietf.org/html/rfc6455#section-5.2):
                //  0                   1                   2                   3
                //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
                // +-+-+-+-+-------+-+-------------+-------------------------------+
                // |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
                // |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
                // |N|V|V|V|       |S|             |   (if payload len==126/127)   |
                // | |1|2|3|       |K|             |                               |
                // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
                // |     Extended payload length continued, if payload len == 127  |
                // + - - - - - - - - - - - - - - - +-------------------------------+
                // |                               |Masking-key, if MASK set to 1  |
                // +-------------------------------+-------------------------------+
                // | Masking-key (continued)       |          Payload Data         |
                // +-------------------------------- - - - - - - - - - - - - - - - +
                // :                     Payload Data continued ...                :
                // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
                // |                     Payload Data continued ...                |
                // +---------------------------------------------------------------+

                // Read at least 2 bytes
                var result = await _inbound.ReadAtLeastAsync(2, cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();
                if (result.IsCompleted && result.Buffer.Length < 2)
                {
                    return(WebSocketCloseResult.AbnormalClosure);
                }
                var buffer = result.Buffer;

                // Read the opcode
                var opcodeByte = buffer.ReadBigEndian <byte>();
                buffer = buffer.Slice(1);

                var fin       = (opcodeByte & 0x80) != 0;
                var opcodeNum = opcodeByte & 0x0F;
                var opcode    = (WebSocketOpcode)opcodeNum;

                if ((opcodeByte & 0x70) != 0)
                {
                    // Reserved bits set, this frame is invalid, close our side and terminate immediately
                    return(await CloseFromProtocolError(cancellationToken, 0, default(ReadableBuffer), "Reserved bits, which are required to be zero, were set."));
                }
                else if ((opcodeNum >= 0x03 && opcodeNum <= 0x07) || (opcodeNum >= 0x0B && opcodeNum <= 0x0F))
                {
                    // Reserved opcode
                    return(await CloseFromProtocolError(cancellationToken, 0, default(ReadableBuffer), $"Received frame using reserved opcode: 0x{opcodeNum:X}"));
                }

                // Read the first byte of the payload length
                var lenByte = buffer.ReadBigEndian <byte>();
                buffer = buffer.Slice(1);

                var masked     = (lenByte & 0x80) != 0;
                var payloadLen = (lenByte & 0x7F);

                // Mark what we've got so far as consumed
                _inbound.Advance(buffer.Start);

                // Calculate the rest of the header length
                var headerLength = masked ? 4 : 0;
                if (payloadLen == 126)
                {
                    headerLength += 2;
                }
                else if (payloadLen == 127)
                {
                    headerLength += 8;
                }

                uint maskingKey = 0;

                if (headerLength > 0)
                {
                    result = await _inbound.ReadAtLeastAsync(headerLength, cancellationToken);

                    cancellationToken.ThrowIfCancellationRequested();
                    if (result.IsCompleted && result.Buffer.Length < headerLength)
                    {
                        return(WebSocketCloseResult.AbnormalClosure);
                    }
                    buffer = result.Buffer;

                    // Read extended payload length (if any)
                    if (payloadLen == 126)
                    {
                        payloadLen = buffer.ReadBigEndian <ushort>();
                        buffer     = buffer.Slice(sizeof(ushort));
                    }
                    else if (payloadLen == 127)
                    {
                        var longLen = buffer.ReadBigEndian <ulong>();
                        buffer = buffer.Slice(sizeof(ulong));
                        if (longLen > int.MaxValue)
                        {
                            throw new WebSocketException($"Frame is too large. Maximum frame size is {int.MaxValue} bytes");
                        }
                        payloadLen = (int)longLen;
                    }

                    // Read masking key
                    if (masked)
                    {
                        var maskingKeyStart = buffer.Start;
                        maskingKey = buffer.Slice(0, 4).ReadBigEndian <uint>();
                        buffer     = buffer.Slice(4);
                    }

                    // Mark the length and masking key consumed
                    _inbound.Advance(buffer.Start);
                }

                var payload = default(ReadableBuffer);
                if (payloadLen > 0)
                {
                    result = await _inbound.ReadAtLeastAsync(payloadLen, cancellationToken);

                    cancellationToken.ThrowIfCancellationRequested();
                    if (result.IsCompleted && result.Buffer.Length < payloadLen)
                    {
                        return(WebSocketCloseResult.AbnormalClosure);
                    }
                    buffer = result.Buffer;

                    payload = buffer.Slice(0, payloadLen);

                    if (masked)
                    {
                        // Unmask
                        MaskingUtilities.ApplyMask(ref payload, maskingKey);
                    }
                }

                // Run the callback, if we're not cancelled.
                cancellationToken.ThrowIfCancellationRequested();

                var frame = new WebSocketFrame(fin, opcode, payload);

                if (frame.Opcode.IsControl() && !frame.EndOfMessage)
                {
                    // Control frames cannot be fragmented.
                    return(await CloseFromProtocolError(cancellationToken, payloadLen, payload, "Control frames may not be fragmented"));
                }
                else if (_currentMessageType != WebSocketOpcode.Continuation && opcode.IsMessage() && opcode != 0)
                {
                    return(await CloseFromProtocolError(cancellationToken, payloadLen, payload, "Received non-continuation frame during a fragmented message"));
                }
                else if (_currentMessageType == WebSocketOpcode.Continuation && frame.Opcode == WebSocketOpcode.Continuation)
                {
                    return(await CloseFromProtocolError(cancellationToken, payloadLen, payload, "Continuation Frame was received when expecting a new message"));
                }

                if (frame.Opcode == WebSocketOpcode.Close)
                {
                    // Allowed frame lengths:
                    //  0 - No body
                    //  2 - Code with no reason phrase
                    //  >2 - Code and reason phrase (must be valid UTF-8)
                    if (frame.Payload.Length > 125)
                    {
                        return(await CloseFromProtocolError(cancellationToken, payloadLen, payload, "Close frame payload too long. Maximum size is 125 bytes"));
                    }
                    else if ((frame.Payload.Length == 1) || (frame.Payload.Length > 2 && !Utf8Validator.ValidateUtf8(payload.Slice(2))))
                    {
                        return(await CloseFromProtocolError(cancellationToken, payloadLen, payload, "Close frame payload invalid"));
                    }

                    ushort?actualStatusCode;
                    var    closeResult = HandleCloseFrame(payload, frame, out actualStatusCode);

                    // Verify the close result
                    if (actualStatusCode != null)
                    {
                        var statusCode = actualStatusCode.Value;
                        if (statusCode < 1000 || statusCode == 1004 || statusCode == 1005 || statusCode == 1006 || (statusCode > 1011 && statusCode < 3000))
                        {
                            return(await CloseFromProtocolError(cancellationToken, payloadLen, payload, $"Invalid close status: {statusCode}."));
                        }
                    }

                    // Make the payload as consumed
                    if (payloadLen > 0)
                    {
                        _inbound.Advance(payload.End);
                    }

                    return(closeResult);
                }
                else
                {
                    if (frame.Opcode == WebSocketOpcode.Ping)
                    {
                        // Check the ping payload length
                        if (frame.Payload.Length > 125)
                        {
                            // Payload too long
                            return(await CloseFromProtocolError(cancellationToken, payloadLen, payload, "Ping frame exceeded maximum size of 125 bytes"));
                        }

                        await SendCoreAsync(
                            frame.EndOfMessage,
                            WebSocketOpcode.Pong,
                            payloadAllocLength : 0,
                            payloadLength : payload.Length,
                            payloadWriter : AppendPayloadWriter,
                            payload : payload,
                            cancellationToken : cancellationToken);
                    }
                    var effectiveOpcode = opcode == WebSocketOpcode.Continuation ? _currentMessageType : opcode;
                    if (effectiveOpcode == WebSocketOpcode.Text && !_validator.ValidateUtf8Frame(frame.Payload, frame.EndOfMessage))
                    {
                        // Drop the frame and immediately close with InvalidPayload
                        return(await CloseFromProtocolError(cancellationToken, payloadLen, payload, "An invalid Text frame payload was received", statusCode : WebSocketCloseStatus.InvalidPayloadData));
                    }
                    else if (_options.PassAllFramesThrough || (frame.Opcode != WebSocketOpcode.Ping && frame.Opcode != WebSocketOpcode.Pong))
                    {
                        await messageHandler(frame, state);
                    }
                }

                if (fin)
                {
                    // Reset the UTF8 validator
                    _validator.Reset();

                    // If it's a non-control frame, reset the message type tracker
                    if (opcode.IsMessage())
                    {
                        _currentMessageType = WebSocketOpcode.Continuation;
                    }
                }
                // If there isn't a current message type, and this was a fragmented message frame, set the current message type
                else if (!fin && _currentMessageType == WebSocketOpcode.Continuation && opcode.IsMessage())
                {
                    _currentMessageType = opcode;
                }

                // Mark the payload as consumed
                if (payloadLen > 0)
                {
                    _inbound.Advance(payload.End);
                }
            }
            return(WebSocketCloseResult.AbnormalClosure);
        }