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); }
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); }
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); } }
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); }