import WebSockets code from CoreFX (#105)

* import WebSockets code from CoreFX

* sync pr feedback from dotnet/corefx#10510
This commit is contained in:
Andrew Stanton-Nurse 2016-08-03 11:07:01 -07:00 committed by GitHub
parent 8b25152a67
commit 45f0fe178f
41 changed files with 1771 additions and 1389 deletions

View File

@ -10,8 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E55
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.WebSockets.Protocol", "src\Microsoft.AspNetCore.WebSockets.Protocol\Microsoft.AspNetCore.WebSockets.Protocol.xproj", "{E0C10DEC-3339-4A47-85BC-3100C5D34AD4}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.WebSockets.Protocol.Test", "test\Microsoft.AspNetCore.WebSockets.Protocol.Test\Microsoft.AspNetCore.WebSockets.Protocol.Test.xproj", "{62A07A24-4D06-4DDA-B6BF-02D0C9CB7D32}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.WebSockets.Client", "src\Microsoft.AspNetCore.WebSockets.Client\Microsoft.AspNetCore.WebSockets.Client.xproj", "{4A1C4875-AE21-4A78-979A-F0E4DF5EB518}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.WebSockets.Client.Test", "test\Microsoft.AspNetCore.WebSockets.Client.Test\Microsoft.AspNetCore.WebSockets.Client.Test.xproj", "{6604D154-817F-4BC5-BE95-FF7E851179D9}"
@ -27,14 +25,21 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestClient", "samples\TestC
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServer", "samples\TestServer\TestServer.csproj", "{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.WebSockets.Server.Test", "test\Microsoft.AspNetCore.WebSockets.Server.Test\Microsoft.AspNetCore.WebSockets.Server.Test.xproj", "{E82D9F64-8AFA-4DCB-A842-2283FDA73BE8}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AutobahnTestApp", "test\AutobahnTestApp\AutobahnTestApp.xproj", "{9755F612-A155-4BDD-9E20-37ADE0B4B3BA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutobahnTestAppAspNet4", "samples\AutobahnTestAppAspNet4\AutobahnTestAppAspNet4.csproj", "{72E3AB32-682F-42AF-B7C7-0B777244FF11}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutobahnTestAppHttpListener", "samples\AutobahnTestAppHttpListener\AutobahnTestAppHttpListener.csproj", "{B7246F23-6A4B-492F-AB61-292AA1A9E9D5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{19595D64-E42E-46FD-AB2E-BDC870724EE7}"
ProjectSection(SolutionItems) = preProject
scripts\UpdateCoreFxCode.ps1 = scripts\UpdateCoreFxCode.ps1
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.WebSockets.Server.ConformanceTest", "test\Microsoft.AspNetCore.WebSockets.Server.ConformanceTest\Microsoft.AspNetCore.WebSockets.Server.ConformanceTest.xproj", "{A722BB6C-9114-4F25-9BB0-2191D4405F3A}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.WebSockets.Protocol.Test", "test\Microsoft.AspNetCore.WebSockets.Protocol.Test\Microsoft.AspNetCore.WebSockets.Protocol.Test.xproj", "{AAF2DFCF-845E-4410-BBF0-0683AD60DD6A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -45,10 +50,6 @@ Global
{E0C10DEC-3339-4A47-85BC-3100C5D34AD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0C10DEC-3339-4A47-85BC-3100C5D34AD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0C10DEC-3339-4A47-85BC-3100C5D34AD4}.Release|Any CPU.Build.0 = Release|Any CPU
{62A07A24-4D06-4DDA-B6BF-02D0C9CB7D32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62A07A24-4D06-4DDA-B6BF-02D0C9CB7D32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62A07A24-4D06-4DDA-B6BF-02D0C9CB7D32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62A07A24-4D06-4DDA-B6BF-02D0C9CB7D32}.Release|Any CPU.Build.0 = Release|Any CPU
{4A1C4875-AE21-4A78-979A-F0E4DF5EB518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A1C4875-AE21-4A78-979A-F0E4DF5EB518}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A1C4875-AE21-4A78-979A-F0E4DF5EB518}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -69,10 +70,6 @@ Global
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|Any CPU.Build.0 = Release|Any CPU
{E82D9F64-8AFA-4DCB-A842-2283FDA73BE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E82D9F64-8AFA-4DCB-A842-2283FDA73BE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E82D9F64-8AFA-4DCB-A842-2283FDA73BE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E82D9F64-8AFA-4DCB-A842-2283FDA73BE8}.Release|Any CPU.Build.0 = Release|Any CPU
{9755F612-A155-4BDD-9E20-37ADE0B4B3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9755F612-A155-4BDD-9E20-37ADE0B4B3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9755F612-A155-4BDD-9E20-37ADE0B4B3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -85,21 +82,29 @@ Global
{B7246F23-6A4B-492F-AB61-292AA1A9E9D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7246F23-6A4B-492F-AB61-292AA1A9E9D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7246F23-6A4B-492F-AB61-292AA1A9E9D5}.Release|Any CPU.Build.0 = Release|Any CPU
{A722BB6C-9114-4F25-9BB0-2191D4405F3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A722BB6C-9114-4F25-9BB0-2191D4405F3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A722BB6C-9114-4F25-9BB0-2191D4405F3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A722BB6C-9114-4F25-9BB0-2191D4405F3A}.Release|Any CPU.Build.0 = Release|Any CPU
{AAF2DFCF-845E-4410-BBF0-0683AD60DD6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAF2DFCF-845E-4410-BBF0-0683AD60DD6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAF2DFCF-845E-4410-BBF0-0683AD60DD6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAF2DFCF-845E-4410-BBF0-0683AD60DD6A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E0C10DEC-3339-4A47-85BC-3100C5D34AD4} = {2C7947A5-9FBD-4267-97C1-2D726D7B3BAF}
{62A07A24-4D06-4DDA-B6BF-02D0C9CB7D32} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
{4A1C4875-AE21-4A78-979A-F0E4DF5EB518} = {2C7947A5-9FBD-4267-97C1-2D726D7B3BAF}
{6604D154-817F-4BC5-BE95-FF7E851179D9} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
{78A097D0-C0A4-4AED-93E2-84A65392FB52} = {2C7947A5-9FBD-4267-97C1-2D726D7B3BAF}
{8C8EAC01-DC49-4C5E-B348-E4E46FE675F9} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}
{E82D9F64-8AFA-4DCB-A842-2283FDA73BE8} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
{9755F612-A155-4BDD-9E20-37ADE0B4B3BA} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
{72E3AB32-682F-42AF-B7C7-0B777244FF11} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}
{B7246F23-6A4B-492F-AB61-292AA1A9E9D5} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}
{A722BB6C-9114-4F25-9BB0-2191D4405F3A} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
{AAF2DFCF-845E-4410-BBF0-0683AD60DD6A} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,44 @@
param([string]$CoreFxRepoRoot)
$RepoRoot = Split-Path -Parent $PSScriptRoot
$FilesToCopy = @(
"src\System.Net.WebSockets.Client\src\System\Net\WebSockets\ManagedWebSocket.cs",
"src\Common\src\System\Net\WebSockets\WebSocketValidate.cs"
)
if(!$CoreFxRepoRoot) {
$CoreFxRepoRoot = "$RepoRoot\..\..\dotnet\corefx"
}
if(!(Test-Path $CoreFxRepoRoot)) {
throw "Could not find CoreFx repo at $CoreFxRepoRoot"
}
$CoreFxRepoRoot = Convert-Path $CoreFxRepoRoot
$DestinationRoot = "$RepoRoot\src\Microsoft.AspNetCore.WebSockets.Protocol\ext"
$FilesToCopy | foreach {
$Source = Join-Path $CoreFxRepoRoot $_
$Destination = Join-Path $DestinationRoot $_
$DestinationDir = Split-Path -Parent $Destination
if(!(Test-Path $Source)) {
Write-Warning "Can't find source file: $Source"
} else {
if(!(Test-Path $DestinationDir)) {
mkdir $DestinationDir | Out-Null
}
if(Test-Path $Destination) {
del $Destination
}
Write-Host "Copying $_"
$SourceCode = [IO.File]::ReadAllText($Source)
$SourceCode = $SourceCode.Replace("Task.FromException", "CompatHelpers.FromException")
$SourceCode = $SourceCode.Replace("Task.CompletedTask", "CompatHelpers.CompletedTask")
$SourceCode = $SourceCode.Replace("Array.Empty", "CompatHelpers.Empty")
$SourceCode = $SourceCode.Replace("nameof(ClientWebSocket)", "`"ClientWebSocket`"")
[IO.File]::WriteAllText($Destination, $SourceCode)
}
}

View File

@ -53,12 +53,6 @@ namespace Microsoft.AspNetCore.WebSockets.Client
set;
}
public bool UseZeroMask
{
get;
set;
}
public Action<HttpWebRequest> ConfigureRequest
{
get;
@ -114,7 +108,7 @@ namespace Microsoft.AspNetCore.WebSockets.Client
Stream stream = response.GetResponseStream();
return CommonWebSocket.CreateClientWebSocket(stream, subProtocol, KeepAliveInterval, ReceiveBufferSize, useZeroMask: UseZeroMask);
return CommonWebSocket.CreateClientWebSocket(stream, subProtocol, KeepAliveInterval, ReceiveBufferSize);
}
}
}

View File

@ -2,644 +2,31 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.WebSockets.Protocol
{
// https://tools.ietf.org/html/rfc6455
public class CommonWebSocket : WebSocket
public static class CommonWebSocket
{
private readonly static byte[] PingBuffer = Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz");
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
private readonly Stream _stream;
private readonly string _subProtocl;
private readonly bool _maskOutput;
private readonly bool _unmaskInput;
private readonly bool _useZeroMask;
private readonly SemaphoreSlim _writeLock;
private readonly Timer _keepAliveTimer;
private WebSocketState _state;
private WebSocketCloseStatus? _closeStatus;
private string _closeStatusDescription;
private bool _isOutgoingMessageInProgress;
private byte[] _receiveBuffer;
private int _receiveBufferOffset;
private int _receiveBufferBytes;
private FrameHeader _frameInProgress;
private long _frameBytesRemaining;
private int? _firstDataOpCode;
private int _dataUnmaskOffset;
private Utilities.Utf8MessageState _incomingUtf8MessageState = new Utilities.Utf8MessageState();
public CommonWebSocket(Stream stream, string subProtocol, TimeSpan keepAliveInterval, int receiveBufferSize, bool maskOutput, bool useZeroMask, bool unmaskInput)
public static WebSocket CreateClientWebSocket(Stream stream, string subProtocol, TimeSpan keepAliveInterval, int receiveBufferSize)
{
_stream = stream;
_subProtocl = subProtocol;
_state = WebSocketState.Open;
_receiveBuffer = new byte[receiveBufferSize];
_maskOutput = maskOutput;
_useZeroMask = useZeroMask;
_unmaskInput = unmaskInput;
_writeLock = new SemaphoreSlim(1);
if (keepAliveInterval != Timeout.InfiniteTimeSpan)
{
_keepAliveTimer = new Timer(SendKeepAlive, this, keepAliveInterval, keepAliveInterval);
}
return ManagedWebSocket.CreateFromConnectedStream(
stream,
isServer: false,
subprotocol: subProtocol,
keepAliveIntervalSeconds: (int)keepAliveInterval.TotalSeconds,
receiveBufferSize: receiveBufferSize);
}
public static CommonWebSocket CreateClientWebSocket(Stream stream, string subProtocol, TimeSpan keepAliveInterval, int receiveBufferSize, bool useZeroMask)
public static WebSocket CreateServerWebSocket(Stream stream, string subProtocol, TimeSpan keepAliveInterval, int receiveBufferSize)
{
return new CommonWebSocket(stream, subProtocol, keepAliveInterval, receiveBufferSize, maskOutput: true, useZeroMask: useZeroMask, unmaskInput: false);
}
public static CommonWebSocket CreateServerWebSocket(Stream stream, string subProtocol, TimeSpan keepAliveInterval, int receiveBufferSize)
{
return new CommonWebSocket(stream, subProtocol, keepAliveInterval, receiveBufferSize, maskOutput: false, useZeroMask: false, unmaskInput: true);
}
public override WebSocketCloseStatus? CloseStatus
{
get { return _closeStatus; }
}
public override string CloseStatusDescription
{
get { return _closeStatusDescription; }
}
public override WebSocketState State
{
get { return _state; }
}
public override string SubProtocol
{
get { return _subProtocl; }
}
// https://tools.ietf.org/html/rfc6455#section-5.3
// The masking key is a 32-bit value chosen at random by the client.
// When preparing a masked frame, the client MUST pick a fresh masking
// key from the set of allowed 32-bit values. The masking key needs to
// be unpredictable; thus, the masking key MUST be derived from a strong
// source of entropy, and the masking key for a given frame MUST NOT
// make it simple for a server/proxy to predict the masking key for a
// subsequent frame. The unpredictability of the masking key is
// essential to prevent authors of malicious applications from selecting
// the bytes that appear on the wire. RFC 4086 [RFC4086] discusses what
// entails a suitable source of entropy for security-sensitive
// applications.
private int GetNextMask()
{
if (_useZeroMask)
{
return 0;
}
// Get 32-bits of randomness and convert it to an int
var buffer = new byte[sizeof(int)];
_rng.GetBytes(buffer);
return BitConverter.ToInt32(buffer, 0);
}
public override async Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
{
ValidateSegment(buffer);
if (messageType != WebSocketMessageType.Binary && messageType != WebSocketMessageType.Text)
{
// Block control frames
throw new ArgumentOutOfRangeException(nameof(messageType), messageType, string.Empty);
}
// Check concurrent writes, pings & pongs, or closes
await _writeLock.WaitAsync(cancellationToken);
try
{
ThrowIfDisposed();
ThrowIfOutputClosed();
int mask = GetNextMask();
int opcode = _isOutgoingMessageInProgress ? Constants.OpCodes.ContinuationFrame : Utilities.GetOpCode(messageType);
FrameHeader frameHeader = new FrameHeader(endOfMessage, opcode, _maskOutput, mask, buffer.Count);
ArraySegment<byte> headerSegment = frameHeader.Buffer;
if (_maskOutput && mask != 0)
{
// TODO: For larger messages consider using a limited size buffer and masking & sending in segments.
byte[] maskedFrame = Utilities.MergeAndMask(mask, headerSegment, buffer);
await _stream.WriteAsync(maskedFrame, 0, maskedFrame.Length, cancellationToken);
}
else
{
await _stream.WriteAsync(headerSegment.Array, headerSegment.Offset, headerSegment.Count, cancellationToken);
await _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken);
}
_isOutgoingMessageInProgress = !endOfMessage;
}
finally
{
_writeLock.Release();
}
}
private static void SendKeepAlive(object state)
{
CommonWebSocket websocket = (CommonWebSocket)state;
websocket.SendKeepAliveAsync();
}
private async void SendKeepAliveAsync()
{
// Check concurrent writes, pings & pongs, or closes
if (!_writeLock.Wait(0))
{
// Sending real data is better than a ping, discard it.
return;
}
try
{
if (State == WebSocketState.CloseSent || State >= WebSocketState.Closed)
{
_keepAliveTimer.Dispose();
return;
}
int mask = GetNextMask();
FrameHeader frameHeader = new FrameHeader(true, Constants.OpCodes.PingFrame, _maskOutput, mask, PingBuffer.Length);
ArraySegment<byte> headerSegment = frameHeader.Buffer;
// TODO: CancelationToken / timeout?
if (_maskOutput && mask != 0)
{
byte[] maskedFrame = Utilities.MergeAndMask(mask, headerSegment, new ArraySegment<byte>(PingBuffer));
await _stream.WriteAsync(maskedFrame, 0, maskedFrame.Length);
}
else
{
await _stream.WriteAsync(headerSegment.Array, headerSegment.Offset, headerSegment.Count);
await _stream.WriteAsync(PingBuffer, 0, PingBuffer.Length);
}
}
catch (Exception)
{
// TODO: Log exception, this is a background thread.
// Shut down, we must be in a faulted state;
Abort();
}
finally
{
_writeLock.Release();
}
}
public async override Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
{
ThrowIfDisposed();
ThrowIfInputClosed();
ValidateSegment(buffer);
// TODO: InvalidOperationException if any receives are currently in progress.
// No active frame. Loop because we may be discarding ping/pong frames.
while (_frameInProgress == null)
{
await ReadNextFrameAsync(cancellationToken);
}
int opCode = _frameInProgress.OpCode;
if (opCode == Constants.OpCodes.CloseFrame)
{
return await ProcessCloseFrameAsync(cancellationToken);
}
// Handle fragmentation, remember the first frame type
if (opCode == Constants.OpCodes.ContinuationFrame)
{
if (!_firstDataOpCode.HasValue)
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid continuation frame", cancellationToken);
}
opCode = _firstDataOpCode.Value;
}
else
{
_firstDataOpCode = opCode;
}
// Make sure there's at least some data in the buffer
int bytesToBuffer = (int)Math.Min((long)_receiveBuffer.Length, _frameBytesRemaining);
await EnsureDataAvailableOrReadAsync(bytesToBuffer, cancellationToken);
// Copy buffered data to the users buffer
int bytesToRead = (int)Math.Min((long)buffer.Count, _frameBytesRemaining);
int bytesToCopy = Math.Min(bytesToRead, _receiveBufferBytes);
Array.Copy(_receiveBuffer, _receiveBufferOffset, buffer.Array, buffer.Offset, bytesToCopy);
if (_unmaskInput)
{
// _frameInProgress.Masked == _unmaskInput already verified
Utilities.MaskInPlace(_frameInProgress.MaskKey, ref _dataUnmaskOffset, new ArraySegment<byte>(buffer.Array, buffer.Offset, bytesToCopy));
}
WebSocketReceiveResult result;
WebSocketMessageType messageType = Utilities.GetMessageType(opCode);
if (messageType == WebSocketMessageType.Text
&& !Utilities.TryValidateUtf8(new ArraySegment<byte>(buffer.Array, buffer.Offset, bytesToCopy), _frameInProgress.Fin, _incomingUtf8MessageState))
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.InvalidPayloadData, "Invalid UTF-8", cancellationToken);
}
if (bytesToCopy == _frameBytesRemaining)
{
result = new WebSocketReceiveResult(bytesToCopy, messageType, _frameInProgress.Fin);
if (_frameInProgress.Fin)
{
_firstDataOpCode = null;
}
_frameInProgress = null;
_dataUnmaskOffset = 0;
}
else
{
result = new WebSocketReceiveResult(bytesToCopy, messageType, false);
}
_frameBytesRemaining -= bytesToCopy;
_receiveBufferBytes -= bytesToCopy;
_receiveBufferOffset += bytesToCopy;
return result;
}
private async Task ReadNextFrameAsync(CancellationToken cancellationToken)
{
await EnsureDataAvailableOrReadAsync(2, cancellationToken);
int frameHeaderSize = FrameHeader.CalculateFrameHeaderSize(_receiveBuffer[_receiveBufferOffset + 1]);
await EnsureDataAvailableOrReadAsync(frameHeaderSize, cancellationToken);
_frameInProgress = new FrameHeader(new ArraySegment<byte>(_receiveBuffer, _receiveBufferOffset, frameHeaderSize));
_receiveBufferOffset += frameHeaderSize;
_receiveBufferBytes -= frameHeaderSize;
_frameBytesRemaining = _frameInProgress.DataLength;
if (_frameInProgress.AreReservedSet())
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Unexpected reserved bits set", cancellationToken);
}
if (_unmaskInput != _frameInProgress.Masked)
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Incorrect masking", cancellationToken);
}
if (!ValidateOpCode(_frameInProgress.OpCode))
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid opcode: " + _frameInProgress.OpCode, cancellationToken);
}
if (_frameInProgress.IsControlFrame)
{
if (_frameBytesRemaining > 125)
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid control frame size", cancellationToken);
}
if (!_frameInProgress.Fin)
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Fragmented control frame", cancellationToken);
}
if (_frameInProgress.OpCode == Constants.OpCodes.PingFrame || _frameInProgress.OpCode == Constants.OpCodes.PongFrame)
{
// Drain it, should be less than 125 bytes
await EnsureDataAvailableOrReadAsync((int)_frameBytesRemaining, cancellationToken);
if (_frameInProgress.OpCode == Constants.OpCodes.PingFrame)
{
await SendPongReplyAsync(cancellationToken);
}
_receiveBufferOffset += (int)_frameBytesRemaining;
_receiveBufferBytes -= (int)_frameBytesRemaining;
_frameBytesRemaining = 0;
_frameInProgress = null;
}
}
else if (_firstDataOpCode.HasValue && _frameInProgress.OpCode != Constants.OpCodes.ContinuationFrame)
{
// A data frame is already in progress, but this new frame is not a continuation frame.
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Expected a continuation frame: " + _frameInProgress.OpCode, cancellationToken);
}
}
private async Task EnsureDataAvailableOrReadAsync(int bytesNeeded, CancellationToken cancellationToken)
{
// Adequate buffer space?
Contract.Assert(bytesNeeded <= _receiveBuffer.Length);
// Insufficient buffered data
while (_receiveBufferBytes < bytesNeeded)
{
cancellationToken.ThrowIfCancellationRequested();
int spaceRemaining = _receiveBuffer.Length - (_receiveBufferOffset + _receiveBufferBytes);
if (_receiveBufferOffset > 0 && bytesNeeded > spaceRemaining)
{
// Some data in the buffer, shift down to make room
Array.Copy(_receiveBuffer, _receiveBufferOffset, _receiveBuffer, 0, _receiveBufferBytes);
_receiveBufferOffset = 0;
spaceRemaining = _receiveBuffer.Length - _receiveBufferBytes;
}
// Add to the end
int read = await _stream.ReadAsync(_receiveBuffer, _receiveBufferOffset + _receiveBufferBytes, spaceRemaining, cancellationToken);
if (read == 0)
{
throw new IOException("Unexpected end of stream");
}
_receiveBufferBytes += read;
}
}
// We received a ping, send a pong in reply
private async Task SendPongReplyAsync(CancellationToken cancellationToken)
{
await _writeLock.WaitAsync(cancellationToken);
try
{
if (State != WebSocketState.Open)
{
// Output closed, discard the pong.
return;
}
ArraySegment<byte> dataSegment = new ArraySegment<byte>(_receiveBuffer, _receiveBufferOffset, (int)_frameBytesRemaining);
if (_unmaskInput)
{
// _frameInProgress.Masked == _unmaskInput already verified
Utilities.MaskInPlace(_frameInProgress.MaskKey, dataSegment);
}
int mask = GetNextMask();
FrameHeader header = new FrameHeader(true, Constants.OpCodes.PongFrame, _maskOutput, mask, _frameBytesRemaining);
if (_maskOutput)
{
Utilities.MaskInPlace(mask, dataSegment);
}
ArraySegment<byte> headerSegment = header.Buffer;
await _stream.WriteAsync(headerSegment.Array, headerSegment.Offset, headerSegment.Count, cancellationToken);
await _stream.WriteAsync(dataSegment.Array, dataSegment.Offset, dataSegment.Count, cancellationToken);
}
finally
{
_writeLock.Release();
}
}
private async Task<WebSocketReceiveResult> ProcessCloseFrameAsync(CancellationToken cancellationToken)
{
// The close message should be less than 125 bytes and fit in the buffer.
await EnsureDataAvailableOrReadAsync((int)_frameBytesRemaining, CancellationToken.None);
// Status code and message are optional
if (_frameBytesRemaining >= 2)
{
if (_unmaskInput)
{
Utilities.MaskInPlace(_frameInProgress.MaskKey, new ArraySegment<byte>(_receiveBuffer, _receiveBufferOffset, (int)_frameBytesRemaining));
}
_closeStatus = (WebSocketCloseStatus)((_receiveBuffer[_receiveBufferOffset] << 8) | _receiveBuffer[_receiveBufferOffset + 1]);
if (!ValidateCloseStatus(_closeStatus.Value))
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid close status code.", cancellationToken);
}
try
{
var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
_closeStatusDescription = encoding.GetString(_receiveBuffer, _receiveBufferOffset + 2, (int)_frameBytesRemaining - 2) ?? string.Empty;
}
catch (DecoderFallbackException)
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid UTF-8 close message.", cancellationToken);
}
}
else if (_frameBytesRemaining == 1)
{
await SendErrorAbortAndThrow(WebSocketCloseStatus.ProtocolError, "Invalid close body.", cancellationToken);
}
else
{
_closeStatus = _closeStatus ?? WebSocketCloseStatus.NormalClosure;
_closeStatusDescription = _closeStatusDescription ?? string.Empty;
}
Contract.Assert(_frameInProgress.Fin);
WebSocketReceiveResult result = new WebSocketReceiveResult(0, WebSocketMessageType.Close, _frameInProgress.Fin,
_closeStatus.Value, _closeStatusDescription);
if (State == WebSocketState.Open)
{
_state = WebSocketState.CloseReceived;
}
else if (State == WebSocketState.CloseSent)
{
_state = WebSocketState.Closed;
_stream.Dispose();
}
return result;
}
public async override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
{
ThrowIfDisposed();
if (State == WebSocketState.Open || State == WebSocketState.CloseReceived)
{
// Send a close message.
await CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
}
if (State == WebSocketState.CloseSent)
{
// Do a receiving drain
byte[] data = new byte[_receiveBuffer.Length];
WebSocketReceiveResult result;
do
{
result = await ReceiveAsync(new ArraySegment<byte>(data), cancellationToken);
}
while (result.MessageType != WebSocketMessageType.Close);
}
}
public override async Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
{
await _writeLock.WaitAsync(cancellationToken);
try
{
ThrowIfDisposed();
ThrowIfOutputClosed();
if (_keepAliveTimer != null)
{
_keepAliveTimer.Dispose();
}
byte[] descriptionBytes = Encoding.UTF8.GetBytes(statusDescription ?? string.Empty);
byte[] fullData = new byte[descriptionBytes.Length + 2];
fullData[0] = (byte)((int)closeStatus >> 8);
fullData[1] = (byte)closeStatus;
Array.Copy(descriptionBytes, 0, fullData, 2, descriptionBytes.Length);
int mask = GetNextMask();
if (_maskOutput)
{
Utilities.MaskInPlace(mask, new ArraySegment<byte>(fullData));
}
FrameHeader frameHeader = new FrameHeader(true, Constants.OpCodes.CloseFrame, _maskOutput, mask, fullData.Length);
ArraySegment<byte> segment = frameHeader.Buffer;
await _stream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken);
await _stream.WriteAsync(fullData, 0, fullData.Length, cancellationToken);
if (State == WebSocketState.Open)
{
_state = WebSocketState.CloseSent;
}
else if (State == WebSocketState.CloseReceived)
{
_state = WebSocketState.Closed;
_stream.Dispose();
}
}
finally
{
_writeLock.Release();
}
}
public override void Abort()
{
if (_state >= WebSocketState.Closed) // or Aborted
{
return;
}
_state = WebSocketState.Aborted;
if (_keepAliveTimer != null)
{
_keepAliveTimer.Dispose();
}
_stream.Dispose();
}
public override void Dispose()
{
if (_state >= WebSocketState.Closed) // or Aborted
{
return;
}
_state = WebSocketState.Closed;
if (_keepAliveTimer != null)
{
_keepAliveTimer.Dispose();
}
_stream.Dispose();
}
private void ThrowIfDisposed()
{
if (_state >= WebSocketState.Closed) // or Aborted
{
throw new ObjectDisposedException(typeof(CommonWebSocket).FullName);
}
}
private void ThrowIfOutputClosed()
{
if (State == WebSocketState.CloseSent)
{
throw new InvalidOperationException("Close already sent.");
}
}
private void ThrowIfInputClosed()
{
if (State == WebSocketState.CloseReceived)
{
throw new InvalidOperationException("Close already received.");
}
}
private void ValidateSegment(ArraySegment<byte> buffer)
{
if (buffer.Array == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (buffer.Offset < 0 || buffer.Offset > buffer.Array.Length)
{
throw new ArgumentOutOfRangeException(nameof(buffer.Offset), buffer.Offset, string.Empty);
}
if (buffer.Count < 0 || buffer.Count > buffer.Array.Length - buffer.Offset)
{
throw new ArgumentOutOfRangeException(nameof(buffer.Count), buffer.Count, string.Empty);
}
}
private bool ValidateOpCode(int opCode)
{
return Constants.OpCodes.ValidOpCodes.Contains(opCode);
}
private static bool ValidateCloseStatus(WebSocketCloseStatus closeStatus)
{
if (closeStatus < (WebSocketCloseStatus)1000 || closeStatus >= (WebSocketCloseStatus)5000)
{
return false;
}
else if (closeStatus >= (WebSocketCloseStatus)3000)
{
// 3000-3999 - Reserved for frameworks
// 4000-4999 - Reserved for private usage
return true;
}
int[] validCodes = new[] { 1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011 };
foreach (var validCode in validCodes)
{
if (closeStatus == (WebSocketCloseStatus)validCode)
{
return true;
}
}
return false;
}
private async Task SendErrorAbortAndThrow(WebSocketCloseStatus error, string message, CancellationToken cancellationToken)
{
if (State == WebSocketState.Open || State == WebSocketState.CloseReceived)
{
await CloseOutputAsync(error, message, cancellationToken);
}
Abort();
throw new InvalidOperationException(message); // TODO: WebSocketException
return ManagedWebSocket.CreateFromConnectedStream(
stream,
isServer: true,
subprotocol: subProtocol,
keepAliveIntervalSeconds: (int)keepAliveInterval.TotalSeconds,
receiveBufferSize: receiveBufferSize);
}
}
}

View File

@ -0,0 +1,49 @@
using System.Threading.Tasks;
namespace System.Net.WebSockets
{
// Needed to support the WebSockets code from CoreFX.
internal static class CompatHelpers
{
internal static readonly Task CompletedTask;
static CompatHelpers()
{
var tcs = new TaskCompletionSource<object>();
tcs.SetResult(null);
CompletedTask = tcs.Task;
}
public static Task FromException(Exception ex)
{
#if NET451
return FromException<object>(ex);
#else
return Task.FromException(ex);
#endif
}
public static Task<T> FromException<T>(Exception ex)
{
#if NET451
var tcs = new TaskCompletionSource<T>();
tcs.SetException(ex);
return tcs.Task;
#else
return Task.FromException<T>(ex);
#endif
}
internal static T[] Empty<T>()
{
#if NET451
return new T[0];
#else
return Array.Empty<T>();
#endif
}
}
// This is just here to be used by a nameof in the CoreFX code.
//internal static class ClientWebSocket { }
}

View File

@ -1,252 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.WebSockets.Protocol
{
public class FrameHeader
{
private byte[] _header;
public FrameHeader(ArraySegment<byte> header)
{
_header = new byte[header.Count];
Array.Copy(header.Array, header.Offset, _header, 0, _header.Length);
}
public FrameHeader(bool final, int opCode, bool masked, int maskKey, long dataLength)
{
int headerLength = 2;
if (masked)
{
headerLength += 4;
}
if (dataLength <= 125)
{
}
else if (125 < dataLength && dataLength <= 0xFFFF)
{
headerLength += 2;
}
else
{
headerLength += 8;
}
_header = new byte[headerLength];
Fin = final;
OpCode = opCode;
Masked = masked;
DataLength = dataLength;
if (masked)
{
MaskKey = maskKey;
}
}
public bool Fin
{
get
{
return (_header[0] & 0x80) == 0x80;
}
private set
{
if (value)
{
_header[0] |= 0x80;
}
else
{
_header[0] &= 0x7F;
}
}
}
public int OpCode
{
get
{
return (_header[0] & 0xF);
}
private set
{
// TODO: Clear out a prior value?
_header[0] |= (byte)(value & 0xF);
}
}
public bool Masked
{
get
{
return (_header[1] & 0x80) == 0x80;
}
private set
{
if (value)
{
_header[1] |= 0x80;
}
else
{
_header[1] &= 0x7F;
}
}
}
public int MaskKey
{
get
{
if (!Masked)
{
return 0;
}
int offset = ExtendedLengthFieldSize + 2;
return (_header[offset] << 24) + (_header[offset + 1] << 16)
+ (_header[offset + 2] << 8) + _header[offset + 3];
}
private set
{
int offset = ExtendedLengthFieldSize + 2;
_header[offset] = (byte)(value >> 24);
_header[offset + 1] = (byte)(value >> 16);
_header[offset + 2] = (byte)(value >> 8);
_header[offset + 3] = (byte)value;
}
}
public int PayloadField
{
get
{
return (_header[1] & 0x7F);
}
private set
{
// TODO: Clear out a prior value?
_header[1] |= (byte)(value & 0x7F);
}
}
public int ExtendedLengthFieldSize
{
get
{
int payloadField = PayloadField;
if (payloadField <= 125)
{
return 0;
}
if (payloadField == 126)
{
return 2;
}
return 8;
}
}
public long DataLength
{
get
{
int extendedFieldSize = ExtendedLengthFieldSize;
if (extendedFieldSize == 0)
{
return PayloadField;
}
if (extendedFieldSize == 2)
{
return (_header[2] << 8) + _header[3];
}
return (_header[2] << 56) + (_header[3] << 48)
+ (_header[4] << 40) + (_header[5] << 32)
+ (_header[6] << 24) + (_header[7] << 16)
+ (_header[8] << 8) + _header[9];
}
private set
{
if (value <= 125)
{
PayloadField = (int)value;
}
else if (125 < value && value <= 0xFFFF)
{
PayloadField = 0x7E;
_header[2] = (byte)(value >> 8);
_header[3] = (byte)value;
}
else
{
PayloadField = 0x7F;
_header[2] = (byte)(value >> 56);
_header[3] = (byte)(value >> 48);
_header[4] = (byte)(value >> 40);
_header[5] = (byte)(value >> 32);
_header[6] = (byte)(value >> 24);
_header[7] = (byte)(value >> 16);
_header[8] = (byte)(value >> 8);
_header[9] = (byte)value;
}
}
}
public ArraySegment<byte> Buffer
{
get
{
return new ArraySegment<byte>(_header);
}
}
public bool IsControlFrame
{
get
{
return OpCode >= Constants.OpCodes.CloseFrame;
}
}
// bits 1-3.
internal bool AreReservedSet()
{
return (_header[0] & 0x70) != 0;
}
// Given the second bytes of a frame, calculate how long the whole frame header should be.
// Range 2-12 bytes
public static int CalculateFrameHeaderSize(byte b2)
{
int headerLength = 2;
if ((b2 & 0x80) == 0x80) // Masked
{
headerLength += 4;
}
int payloadField = (b2 & 0x7F);
if (payloadField <= 125)
{
// headerLength += 0
}
else if (payloadField == 126)
{
headerLength += 2;
}
else
{
headerLength += 8;
}
return headerLength;
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace System.Net.WebSockets
{
// Needed to support the WebSockets code from CoreFX.
internal static class SR
{
internal static readonly string net_Websockets_AlreadyOneOutstandingOperation = nameof(net_Websockets_AlreadyOneOutstandingOperation);
internal static readonly string net_WebSockets_Argument_InvalidMessageType = nameof(net_WebSockets_Argument_InvalidMessageType);
internal static readonly string net_WebSockets_InvalidCharInProtocolString = nameof(net_WebSockets_InvalidCharInProtocolString);
internal static readonly string net_WebSockets_InvalidCloseStatusCode = nameof(net_WebSockets_InvalidCloseStatusCode);
internal static readonly string net_WebSockets_InvalidCloseStatusDescription = nameof(net_WebSockets_InvalidCloseStatusDescription);
internal static readonly string net_WebSockets_InvalidEmptySubProtocol = nameof(net_WebSockets_InvalidEmptySubProtocol);
internal static readonly string net_WebSockets_InvalidState = nameof(net_WebSockets_InvalidState);
internal static readonly string net_WebSockets_InvalidState_ClosedOrAborted = nameof(net_WebSockets_InvalidState_ClosedOrAborted);
internal static readonly string net_WebSockets_ReasonNotNull = nameof(net_WebSockets_ReasonNotNull);
internal static readonly string net_WebSockets_UnsupportedPlatform = nameof(net_WebSockets_UnsupportedPlatform);
internal static string Format(string name, params object[] args) => $"TODO, RESX: {name}; ({string.Join(",", args)})";
}
}

View File

@ -1,170 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net.WebSockets;
namespace Microsoft.AspNetCore.WebSockets.Protocol
{
public static class Utilities
{
// Copies the header and data into a new buffer and masks the data.
public static byte[] MergeAndMask(int mask, ArraySegment<byte> header, ArraySegment<byte> data)
{
byte[] frame = new byte[header.Count + data.Count];
Array.Copy(header.Array, header.Offset, frame, 0, header.Count);
Array.Copy(data.Array, data.Offset, frame, header.Count, data.Count);
MaskInPlace(mask, new ArraySegment<byte>(frame, header.Count, data.Count));
return frame;
}
public static void MaskInPlace(int mask, ArraySegment<byte> data)
{
int maskOffset = 0;
MaskInPlace(mask, ref maskOffset, data);
}
public static void MaskInPlace(int mask, ref int maskOffset, ArraySegment<byte> data)
{
if (mask == 0)
{
return;
}
byte[] maskBytes = new byte[]
{
(byte)(mask >> 24),
(byte)(mask >> 16),
(byte)(mask >> 8),
(byte)mask,
};
int end = data.Offset + data.Count;
for (int i = data.Offset; i < end; i++)
{
data.Array[i] ^= maskBytes[maskOffset];
maskOffset = (maskOffset + 1) & 0x3; // fast % 4;
}
}
public static int GetOpCode(WebSocketMessageType messageType)
{
switch (messageType)
{
case WebSocketMessageType.Text: return Constants.OpCodes.TextFrame;
case WebSocketMessageType.Binary: return Constants.OpCodes.BinaryFrame;
case WebSocketMessageType.Close: return Constants.OpCodes.CloseFrame;
default: throw new NotImplementedException(messageType.ToString());
}
}
public static WebSocketMessageType GetMessageType(int opCode)
{
switch (opCode)
{
case Constants.OpCodes.TextFrame: return WebSocketMessageType.Text;
case Constants.OpCodes.BinaryFrame: return WebSocketMessageType.Binary;
case Constants.OpCodes.CloseFrame: return WebSocketMessageType.Close;
default: throw new NotImplementedException(opCode.ToString());
}
}
// Performs a stateful validation of UTF-8 bytes.
// It checks for valid formatting, overlong encodings, surrogates, and value ranges.
public static bool TryValidateUtf8(ArraySegment<byte> arraySegment, bool endOfMessage, Utf8MessageState state)
{
for (int i = arraySegment.Offset; i < arraySegment.Offset + arraySegment.Count; )
{
// Have we started a character sequence yet?
if (!state.SequenceInProgress)
{
// The first byte tells us how many bytes are in the sequence.
state.SequenceInProgress = true;
byte b = arraySegment.Array[i];
i++;
if ((b & 0x80) == 0) // 0bbbbbbb, single byte
{
state.AdditionalBytesExpected = 0;
state.CurrentDecodeBits = b & 0x7F;
state.ExpectedValueMin = 0;
}
else if ((b & 0xC0) == 0x80)
{
// Misplaced 10bbbbbb continuation byte. This cannot be the first byte.
return false;
}
else if ((b & 0xE0) == 0xC0) // 110bbbbb 10bbbbbb
{
state.AdditionalBytesExpected = 1;
state.CurrentDecodeBits = b & 0x1F;
state.ExpectedValueMin = 0x80;
}
else if ((b & 0xF0) == 0xE0) // 1110bbbb 10bbbbbb 10bbbbbb
{
state.AdditionalBytesExpected = 2;
state.CurrentDecodeBits = b & 0xF;
state.ExpectedValueMin = 0x800;
}
else if ((b & 0xF8) == 0xF0) // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
{
state.AdditionalBytesExpected = 3;
state.CurrentDecodeBits = b & 0x7;
state.ExpectedValueMin = 0x10000;
}
else // 111110bb & 1111110b & 11111110 && 11111111 are not valid
{
return false;
}
}
while (state.AdditionalBytesExpected > 0 && i < arraySegment.Offset + arraySegment.Count)
{
byte b = arraySegment.Array[i];
if ((b & 0xC0) != 0x80)
{
return false;
}
i++;
state.AdditionalBytesExpected--;
// Each continuation byte carries 6 bits of data 0x10bbbbbb.
state.CurrentDecodeBits = (state.CurrentDecodeBits << 6) | (b & 0x3F);
if (state.AdditionalBytesExpected == 1 && state.CurrentDecodeBits >= 0x360 && state.CurrentDecodeBits <= 0x37F)
{
// This is going to end up in the range of 0xD800-0xDFFF UTF-16 surrogates that are not allowed in UTF-8;
return false;
}
if (state.AdditionalBytesExpected == 2 && state.CurrentDecodeBits >= 0x110)
{
// This is going to be out of the upper Unicode bound 0x10FFFF.
return false;
}
}
if (state.AdditionalBytesExpected == 0)
{
state.SequenceInProgress = false;
if (state.CurrentDecodeBits < state.ExpectedValueMin)
{
// Overlong encoding (e.g. using 2 bytes to encode something that only needed 1).
return false;
}
}
}
if (endOfMessage && state.SequenceInProgress)
{
return false;
}
return true;
}
public class Utf8MessageState
{
public bool SequenceInProgress { get; set; }
public int AdditionalBytesExpected { get; set; }
public int ExpectedValueMin { get; set; }
public int CurrentDecodeBits { get; set; }
}
}
}

View File

@ -0,0 +1,5 @@
# External Code
External code copied from CoreFX. Do not modify files in this directory, use the `scripts\UpdateCoreFxCore.ps1` script in the repo root.
This folder structure is designed to exactly mirror the structure in the CoreFX repo (hence the deep nesting).

View File

@ -0,0 +1,132 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Globalization;
using System.Text;
namespace System.Net.WebSockets
{
internal static class WebSocketValidate
{
internal const int MaxControlFramePayloadLength = 123;
private const int CloseStatusCodeAbort = 1006;
private const int CloseStatusCodeFailedTLSHandshake = 1015;
private const int InvalidCloseStatusCodesFrom = 0;
private const int InvalidCloseStatusCodesTo = 999;
private const string Separators = "()<>@,;:\\\"/[]?={} ";
internal static void ValidateSubprotocol(string subProtocol)
{
if (string.IsNullOrWhiteSpace(subProtocol))
{
throw new ArgumentException(SR.net_WebSockets_InvalidEmptySubProtocol, nameof(subProtocol));
}
string invalidChar = null;
int i = 0;
while (i < subProtocol.Length)
{
char ch = subProtocol[i];
if (ch < 0x21 || ch > 0x7e)
{
invalidChar = string.Format(CultureInfo.InvariantCulture, "[{0}]", (int)ch);
break;
}
if (!char.IsLetterOrDigit(ch) &&
Separators.IndexOf(ch) >= 0)
{
invalidChar = ch.ToString();
break;
}
i++;
}
if (invalidChar != null)
{
throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCharInProtocolString, subProtocol, invalidChar), nameof(subProtocol));
}
}
internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string statusDescription)
{
if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription))
{
throw new ArgumentException(SR.Format(SR.net_WebSockets_ReasonNotNull,
statusDescription,
WebSocketCloseStatus.Empty),
nameof(statusDescription));
}
int closeStatusCode = (int)closeStatus;
if ((closeStatusCode >= InvalidCloseStatusCodesFrom &&
closeStatusCode <= InvalidCloseStatusCodesTo) ||
closeStatusCode == CloseStatusCodeAbort ||
closeStatusCode == CloseStatusCodeFailedTLSHandshake)
{
// CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort
throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusCode,
closeStatusCode),
nameof(closeStatus));
}
int length = 0;
if (!string.IsNullOrEmpty(statusDescription))
{
length = Encoding.UTF8.GetByteCount(statusDescription);
}
if (length > MaxControlFramePayloadLength)
{
throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusDescription,
statusDescription,
MaxControlFramePayloadLength),
nameof(statusDescription));
}
}
internal static void ThrowPlatformNotSupportedException()
{
throw new PlatformNotSupportedException(SR.net_WebSockets_UnsupportedPlatform);
}
internal static void ValidateArraySegment(ArraySegment<byte> arraySegment, string parameterName)
{
if (arraySegment.Array == null)
{
throw new ArgumentNullException(parameterName + ".Array");
}
}
internal static void ThrowIfInvalidState(WebSocketState currentState, bool isDisposed, WebSocketState[] validStates)
{
string validStatesText = string.Empty;
if (validStates != null && validStates.Length > 0)
{
foreach (WebSocketState validState in validStates)
{
if (currentState == validState)
{
// Ordering is important to maintain .NET 4.5 WebSocket implementation exception behavior.
if (isDisposed)
{
throw new ObjectDisposedException("ClientWebSocket");
}
return;
}
}
validStatesText = string.Join(", ", validStates);
}
throw new WebSocketException(
WebSocketError.InvalidState,
SR.Format(SR.net_WebSockets_InvalidState, currentState, validStatesText));
}
}
}

View File

@ -11,14 +11,18 @@
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk",
"nowarn": [
"CS1591"
"CS1591",
"CS1572",
"CS1573"
],
"xmlDoc": true
"xmlDoc": true,
"allowUnsafe": true
},
"frameworks": {
"net451": {},
"netstandard1.3": {
"dependencies": {
"System.Diagnostics.Debug": "4.0.11-*",
"System.Diagnostics.Contracts": "4.0.1-*",
"System.Globalization": "4.0.11-*",
"System.IO": "4.1.0-*",

View File

@ -1,7 +1,7 @@
#
# RunAutobahnTests.ps1
#
param([Parameter(Mandatory=$true)][string]$ServerUrl, [string[]]$Cases = @("*"), [string]$OutputDir)
param([Parameter(Mandatory=$true)][string]$ServerUrl, [string[]]$Cases = @("*"), [string]$OutputDir, [int]$Iterations = 1)
if(!(Get-Command wstest -ErrorAction SilentlyContinue)) {
throw "Missing required command 'wstest'. See README.md in Microsoft.AspNetCore.WebSockets.Server.Test project for information on installing Autobahn Test Suite."
@ -12,19 +12,32 @@ if(!$OutputDir) {
$OutputDir = Join-Path $OutputDir "autobahnreports"
}
$Spec = Convert-Path (Join-Path $PSScriptRoot "autobahn.spec.json")
Write-Host "Launching Autobahn Test Suite ($Iterations iteration(s))..."
$CasesArray = [string]::Join(",", @($Cases | ForEach-Object { "`"$_`"" }))
0..($Iterations-1) | % {
$iteration = $_
$SpecJson = [IO.File]::ReadAllText($Spec).Replace("OUTPUTDIR", $OutputDir.Replace("\", "\\")).Replace("WEBSOCKETURL", $ServerUrl).Replace("`"CASES`"", $CasesArray)
$Spec = Convert-Path (Join-Path $PSScriptRoot "autobahn.spec.json")
$TempFile = [IO.Path]::GetTempFileName()
$CasesArray = [string]::Join(",", @($Cases | ForEach-Object { "`"$_`"" }))
try {
[IO.File]::WriteAllText($TempFile, $SpecJson)
& wstest -m fuzzingclient -s $TempFile
} finally {
if(Test-Path $TempFile) {
rm $TempFile
$SpecJson = [IO.File]::ReadAllText($Spec).Replace("OUTPUTDIR", $OutputDir.Replace("\", "\\")).Replace("WEBSOCKETURL", $ServerUrl).Replace("`"CASES`"", $CasesArray)
$TempFile = [IO.Path]::GetTempFileName()
try {
[IO.File]::WriteAllText($TempFile, $SpecJson)
$wstestOutput = & wstest -m fuzzingclient -s $TempFile
} finally {
if(Test-Path $TempFile) {
rm $TempFile
}
}
$report = ConvertFrom-Json ([IO.File]::ReadAllText((Convert-Path (Join-Path $OutputDir "index.json"))))
$report.Server | gm | ? { $_.MemberType -eq "NoteProperty" } | % {
$case = $report.Server."$($_.Name)"
Write-Host "[#$($iteration.ToString().PadRight(2))] [$($case.behavior.PadRight(6))] Case $($_.Name)"
}
}

View File

@ -290,9 +290,11 @@ namespace Microsoft.AspNetCore.WebSockets.Client.Test
}
}
[ConditionalFact]
[ConditionalTheory]
[InlineData(1024 * 16)]
[InlineData(0xFFFFFF)]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task ReceiveLongDataInSmallBuffer_Success()
public async Task ReceiveLongData(int receiveBufferSize)
{
var orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF));
using (var server = KestrelWebSocketHelpers.CreateServer(async context =>
@ -303,7 +305,7 @@ namespace Microsoft.AspNetCore.WebSockets.Client.Test
await webSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
}))
{
var client = new WebSocketClient();
var client = new WebSocketClient() { ReceiveBufferSize = receiveBufferSize };
using (var clientSocket = await client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None))
{
var clientBuffer = new byte[orriginalData.Length];
@ -324,32 +326,6 @@ namespace Microsoft.AspNetCore.WebSockets.Client.Test
}
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task ReceiveLongDataInLargeBuffer_Success()
{
var orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF));
using (var server = KestrelWebSocketHelpers.CreateServer(async context =>
{
Assert.True(context.WebSockets.IsWebSocketRequest);
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
await webSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
}))
{
var client = new WebSocketClient() { ReceiveBufferSize = 0xFFFFFF };
using (var clientSocket = await client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None))
{
var clientBuffer = new byte[orriginalData.Length];
var result = await clientSocket.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
Assert.True(result.EndOfMessage);
Assert.Equal(orriginalData.Length, result.Count);
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
Assert.Equal(orriginalData, clientBuffer);
}
}
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task ReceiveFragmentedData_Success()

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.WebSockets.Protocol.Test
private SemaphoreSlim _readLock;
private SemaphoreSlim _writeLock;
private TaskCompletionSource<object> _readWaitingForData;
internal BufferStream()
{
_readLock = new SemaphoreSlim(1, 1);

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.IO;
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.WebSockets.Protocol.Test
public DuplexStream()
: this (new BufferStream(), new BufferStream())
{
{
}
public DuplexStream(Stream readStream, Stream writeStream)

View File

@ -1,66 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.WebSockets.Protocol.Test
{
public class DuplexTests
{
[Fact]
public async Task SendAndReceive()
{
DuplexStream serverStream = new DuplexStream();
DuplexStream clientStream = serverStream.CreateReverseDuplexStream();
WebSocket serverWebSocket = CommonWebSocket.CreateServerWebSocket(serverStream, null, TimeSpan.FromMinutes(2), 1024);
WebSocket clientWebSocket = CommonWebSocket.CreateClientWebSocket(clientStream, null, TimeSpan.FromMinutes(2), 1024, false);
byte[] clientBuffer = Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz");
byte[] serverBuffer = new byte[clientBuffer.Length];
await clientWebSocket.SendAsync(new ArraySegment<byte>(clientBuffer), WebSocketMessageType.Text, true, CancellationToken.None);
WebSocketReceiveResult serverResult = await serverWebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
Assert.True(serverResult.EndOfMessage);
Assert.Equal(clientBuffer.Length, serverResult.Count);
Assert.Equal(WebSocketMessageType.Text, serverResult.MessageType);
Assert.Equal(clientBuffer, serverBuffer);
}
[Fact]
// Tests server unmasking with offset masks
public async Task ServerReceiveOffsetData()
{
DuplexStream serverStream = new DuplexStream();
DuplexStream clientStream = serverStream.CreateReverseDuplexStream();
WebSocket serverWebSocket = CommonWebSocket.CreateServerWebSocket(serverStream, null, TimeSpan.FromMinutes(2), 1024);
WebSocket clientWebSocket = CommonWebSocket.CreateClientWebSocket(clientStream, null, TimeSpan.FromMinutes(2), 1024, false);
byte[] clientBuffer = Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz");
byte[] serverBuffer = new byte[clientBuffer.Length];
await clientWebSocket.SendAsync(new ArraySegment<byte>(clientBuffer), WebSocketMessageType.Text, true, CancellationToken.None);
WebSocketReceiveResult serverResult = await serverWebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer, 0, 3), CancellationToken.None);
Assert.False(serverResult.EndOfMessage);
Assert.Equal(3, serverResult.Count);
Assert.Equal(WebSocketMessageType.Text, serverResult.MessageType);
serverResult = await serverWebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer, 3, 10), CancellationToken.None);
Assert.False(serverResult.EndOfMessage);
Assert.Equal(10, serverResult.Count);
Assert.Equal(WebSocketMessageType.Text, serverResult.MessageType);
serverResult = await serverWebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer, 13, 13), CancellationToken.None);
Assert.True(serverResult.EndOfMessage);
Assert.Equal(13, serverResult.Count);
Assert.Equal(WebSocketMessageType.Text, serverResult.MessageType);
Assert.Equal(clientBuffer, serverBuffer);
}
}
}

View File

@ -4,11 +4,13 @@
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>62a07a24-4d06-4dda-b6bf-02d0c9cb7d32</ProjectGuid>
<ProjectGuid>aaf2dfcf-845e-4410-bbf0-0683ad60dd6a</ProjectGuid>
<RootNamespace>Microsoft.AspNetCore.WebSockets.Protocol.Test</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
@ -16,5 +18,5 @@
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EF1FE910-6E0C-4DE8-8CC1-6118B726A59E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.Net.WebSockets.Test</RootNamespace>
<AssemblyName>Microsoft.Net.WebSockets.Test</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="xunit">
<HintPath>..\..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
</Reference>
<Reference Include="xunit.extensions">
<HintPath>..\..\packages\xunit.extensions.1.9.2\lib\net20\xunit.extensions.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DuplexTests.cs" />
<Compile Include="BufferStream.cs" />
<Compile Include="DuplexStream.cs" />
<Compile Include="UtilitiesTests.cs" />
<Compile Include="WebSocketClientTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -2,35 +2,18 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Microsoft.Net.WebSockets.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Microsoft.Net.WebSockets.Test")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyProduct("Microsoft.AspNetCore.WebSockets.Protocol.Test")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("237d6e8f-6e5e-4c3f-96b4-b19cf3bf4d80")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: Guid("aaf2dfcf-845e-4410-bbf0-0683ad60dd6a")]

View File

@ -0,0 +1,77 @@
using System;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.WebSockets.Protocol.Test
{
public class SendReceiveTests
{
[Fact]
public async Task ClientToServerTextMessage()
{
const string message = "Hello, World!";
var pair = WebSocketPair.Create();
var sendBuffer = Encoding.UTF8.GetBytes(message);
await pair.ClientSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None);
var receiveBuffer = new byte[32];
var result = await pair.ServerSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
Assert.Equal(message, Encoding.UTF8.GetString(receiveBuffer, 0, result.Count));
}
[Fact]
public async Task ServerToClientTextMessage()
{
const string message = "Hello, World!";
var pair = WebSocketPair.Create();
var sendBuffer = Encoding.UTF8.GetBytes(message);
await pair.ServerSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None);
var receiveBuffer = new byte[32];
var result = await pair.ClientSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
Assert.Equal(message, Encoding.UTF8.GetString(receiveBuffer, 0, result.Count));
}
[Fact]
public async Task ClientToServerBinaryMessage()
{
var pair = WebSocketPair.Create();
var sendBuffer = new byte[] { 0xde, 0xad, 0xbe, 0xef };
await pair.ClientSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Binary, endOfMessage: true, cancellationToken: CancellationToken.None);
var receiveBuffer = new byte[32];
var result = await pair.ServerSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
Assert.Equal(sendBuffer, receiveBuffer.Take(result.Count).ToArray());
}
[Fact]
public async Task ServerToClientBinaryMessage()
{
var pair = WebSocketPair.Create();
var sendBuffer = new byte[] { 0xde, 0xad, 0xbe, 0xef };
await pair.ServerSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Binary, endOfMessage: true, cancellationToken: CancellationToken.None);
var receiveBuffer = new byte[32];
var result = await pair.ClientSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
Assert.Equal(sendBuffer, receiveBuffer.Take(result.Count).ToArray());
}
}
}

View File

@ -1,75 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
using Xunit;
namespace Microsoft.AspNetCore.WebSockets.Protocol.Test
{
public class Utf8ValidationTests
{
[Theory]
[InlineData(new byte[] { })]
[InlineData(new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64 })] // Hello World
[InlineData(new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2D, 0xC2, 0xB5, 0x40, 0xC3, 0x9F, 0xC3, 0xB6, 0xC3, 0xA4, 0xC3, 0xBC, 0xC3, 0xA0, 0xC3, 0xA1 })] // "Hello-µ@ßöäüàá";
// [InlineData(new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xf0, 0xa4, 0xad, 0xa2, 0x77, 0x6f, 0x72, 0x6c, 0x64 })] // "hello\U00024b62world"
[InlineData(new byte[] { 0xf0, 0xa4, 0xad, 0xa2 })] // "\U00024b62"
public void ValidateSingleValidSegments_Valid(byte[] data)
{
var state = new Utilities.Utf8MessageState();
Assert.True(Utilities.TryValidateUtf8(new ArraySegment<byte>(data), endOfMessage: true, state: state));
}
[Theory]
[InlineData(new byte[] { }, new byte[] { }, new byte[] { })]
[InlineData(new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20 }, new byte[] { }, new byte[] { 0x57, 0x6F, 0x72, 0x6C, 0x64 })] // Hello ,, World
[InlineData(new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2D, 0xC2, }, new byte[] { 0xB5, 0x40, 0xC3, 0x9F, 0xC3, 0xB6, 0xC3, 0xA4, }, new byte[] { 0xC3, 0xBC, 0xC3, 0xA0, 0xC3, 0xA1 })] // "Hello-µ@ßöäüàá";
public void ValidateMultipleValidSegments_Valid(byte[] data1, byte[] data2, byte[] data3)
{
var state = new Utilities.Utf8MessageState();
Assert.True(Utilities.TryValidateUtf8(new ArraySegment<byte>(data1), endOfMessage: false, state: state));
Assert.True(Utilities.TryValidateUtf8(new ArraySegment<byte>(data2), endOfMessage: false, state: state));
Assert.True(Utilities.TryValidateUtf8(new ArraySegment<byte>(data3), endOfMessage: true, state: state));
}
[Theory]
[InlineData(new byte[] { 0xfe })]
[InlineData(new byte[] { 0xff })]
[InlineData(new byte[] { 0xfe, 0xfe, 0xff, 0xff })]
[InlineData(new byte[] { 0xc0, 0xb1 })] // Overlong Ascii
[InlineData(new byte[] { 0xc1, 0xb1 })] // Overlong Ascii
[InlineData(new byte[] { 0xe0, 0x80, 0xaf })] // Overlong
[InlineData(new byte[] { 0xf0, 0x80, 0x80, 0xaf })] // Overlong
[InlineData(new byte[] { 0xf8, 0x80, 0x80, 0x80, 0xaf })] // Overlong
[InlineData(new byte[] { 0xfc, 0x80, 0x80, 0x80, 0x80, 0xaf })] // Overlong
[InlineData(new byte[] { 0xed, 0xa0, 0x80, 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 })] // 0xEDA080 decodes to 0xD800, which is a reserved high surrogate character.
public void ValidateSingleInvalidSegment_Invalid(byte[] data)
{
var state = new Utilities.Utf8MessageState();
Assert.False(Utilities.TryValidateUtf8(new ArraySegment<byte>(data), endOfMessage: true, state: state));
}
[Fact]
public void ValidateIndividualInvalidSegments_Invalid()
{
var data = new byte[] { 0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 };
var state = new Utilities.Utf8MessageState();
for (int i = 0; i < 12; i++)
{
Assert.True(Utilities.TryValidateUtf8(new ArraySegment<byte>(data, i, 1), endOfMessage: false, state: state), i.ToString());
}
Assert.False(Utilities.TryValidateUtf8(new ArraySegment<byte>(data, 12, 1), endOfMessage: false, state: state), 12.ToString());
}
[Fact]
public void ValidateMultipleInvalidSegments_Invalid()
{
var data0 = new byte[] { 0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xb5, 0xf4 };
var data1 = new byte[] { 0x90 };
var state = new Utilities.Utf8MessageState();
Assert.True(Utilities.TryValidateUtf8(new ArraySegment<byte>(data0), endOfMessage: false, state: state));
Assert.False(Utilities.TryValidateUtf8(new ArraySegment<byte>(data1), endOfMessage: false, state: state));
}
}
}

View File

@ -1,22 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
using Xunit;
namespace Microsoft.AspNetCore.WebSockets.Protocol.Test
{
public class UtilitiesTests
{
[Fact]
public void MaskDataRoundTrips()
{
byte[] data = Encoding.UTF8.GetBytes("Hello World");
byte[] orriginal = Encoding.UTF8.GetBytes("Hello World");
Utilities.MaskInPlace(16843009, new ArraySegment<byte>(data));
Utilities.MaskInPlace(16843009, new ArraySegment<byte>(data));
Assert.Equal(orriginal, data);
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Net.WebSockets;
namespace Microsoft.AspNetCore.WebSockets.Protocol.Test
{
internal class WebSocketPair
{
public WebSocket ClientSocket { get; }
public WebSocket ServerSocket { get; }
public WebSocketPair(WebSocket clientSocket, WebSocket serverSocket)
{
ClientSocket = clientSocket;
ServerSocket = serverSocket;
}
public static WebSocketPair Create()
{
// Create streams
var serverStream = new DuplexStream();
var clientStream = serverStream.CreateReverseDuplexStream();
return new WebSocketPair(
clientSocket: CommonWebSocket.CreateClientWebSocket(clientStream, null, TimeSpan.FromMinutes(2), 1024),
serverSocket: CommonWebSocket.CreateServerWebSocket(serverStream, null, TimeSpan.FromMinutes(2), 1024));
}
}
}

View File

@ -1,4 +1,4 @@
{
{
"dependencies": {
"dotnet-test-xunit": "2.2.0-*",
"Microsoft.AspNetCore.WebSockets.Protocol": "0.2.0-*",
@ -13,7 +13,6 @@
"type": "platform"
}
}
},
"net451": {}
}
}
}
}

View File

@ -37,19 +37,22 @@ namespace Microsoft.AspNetCore.WebSockets.Server.Test
.IncludeCase("*")
.ExcludeCase("9.*", "12.*", "13.*");
var loggerFactory = new LoggerFactory(); // No logging! It's very loud...
var loggerFactory = new LoggerFactory(); // No logging by default! It's very loud...
if(string.Equals(Environment.GetEnvironmentVariable("AUTOBAHN_SUITES_LOG"), "1", StringComparison.Ordinal))
{
loggerFactory.AddConsole();
}
AutobahnResult result;
using (var tester = new AutobahnTester(loggerFactory, spec))
{
await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: false, environment: "ManagedSockets", expectationConfig: expect => expect
.NonStrict("6.4.3", "6.4.4")); // https://github.com/aspnet/WebSockets/issues/99
await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: false, environment: "ManagedSockets");
// Windows-only IIS tests, and Kestrel SSL tests (due to: https://github.com/aspnet/WebSockets/issues/102)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: true, environment: "ManagedSockets", expectationConfig: expect => expect
.NonStrict("6.4.3", "6.4.4")); // https://github.com/aspnet/WebSockets/issues/99
await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: true, environment: "ManagedSockets");
if (IsWindows8OrHigher())
{
@ -62,9 +65,7 @@ namespace Microsoft.AspNetCore.WebSockets.Server.Test
.OkOrNonStrict("3.2", "3.3", "3.4", "4.1.3", "4.1.4", "4.1.5", "4.2.3", "4.2.4", "4.2.5", "5.15")); // These occasionally get non-strict results
}
await tester.DeployTestAndAddToSpec(ServerType.WebListener, ssl: false, environment: "ManagedSockets", expectationConfig: expect => expect
.Fail("6.1.2", "6.1.3") // https://github.com/aspnet/WebSockets/issues/97
.NonStrict("6.4.3", "6.4.4")); // https://github.com/aspnet/WebSockets/issues/99
await tester.DeployTestAndAddToSpec(ServerType.WebListener, ssl: false, environment: "ManagedSockets");
}
}

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>e82d9f64-8afa-4dcb-a842-2283fda73be8</ProjectGuid>
<ProjectGuid>a722bb6c-9114-4f25-9bb0-2191d4405f3a</ProjectGuid>
<RootNamespace>Microsoft.AspNetCore.WebSockets.Server.ConformanceTest</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>