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 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}" 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 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}" 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 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}" 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServer", "samples\TestServer\TestServer.csproj", "{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServer", "samples\TestServer\TestServer.csproj", "{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}"
EndProject 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}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AutobahnTestApp", "test\AutobahnTestApp\AutobahnTestApp.xproj", "{9755F612-A155-4BDD-9E20-37ADE0B4B3BA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutobahnTestAppAspNet4", "samples\AutobahnTestAppAspNet4\AutobahnTestAppAspNet4.csproj", "{72E3AB32-682F-42AF-B7C7-0B777244FF11}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutobahnTestAppAspNet4", "samples\AutobahnTestAppAspNet4\AutobahnTestAppAspNet4.csproj", "{72E3AB32-682F-42AF-B7C7-0B777244FF11}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutobahnTestAppHttpListener", "samples\AutobahnTestAppHttpListener\AutobahnTestAppHttpListener.csproj", "{B7246F23-6A4B-492F-AB61-292AA1A9E9D5}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutobahnTestAppHttpListener", "samples\AutobahnTestAppHttpListener\AutobahnTestAppHttpListener.csproj", "{B7246F23-6A4B-492F-AB61-292AA1A9E9D5}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{E0C10DEC-3339-4A47-85BC-3100C5D34AD4}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{4A1C4875-AE21-4A78-979A-F0E4DF5EB518}.Debug|Any CPU.Build.0 = 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 {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}.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.ActiveCfg = Release|Any CPU
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{9755F612-A155-4BDD-9E20-37ADE0B4B3BA}.Debug|Any CPU.Build.0 = 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 {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}.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.ActiveCfg = Release|Any CPU
{B7246F23-6A4B-492F-AB61-292AA1A9E9D5}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{E0C10DEC-3339-4A47-85BC-3100C5D34AD4} = {2C7947A5-9FBD-4267-97C1-2D726D7B3BAF} {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} {4A1C4875-AE21-4A78-979A-F0E4DF5EB518} = {2C7947A5-9FBD-4267-97C1-2D726D7B3BAF}
{6604D154-817F-4BC5-BE95-FF7E851179D9} = {C45106D0-76C8-4776-A140-F7DD83CA2958} {6604D154-817F-4BC5-BE95-FF7E851179D9} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
{78A097D0-C0A4-4AED-93E2-84A65392FB52} = {2C7947A5-9FBD-4267-97C1-2D726D7B3BAF} {78A097D0-C0A4-4AED-93E2-84A65392FB52} = {2C7947A5-9FBD-4267-97C1-2D726D7B3BAF}
{8C8EAC01-DC49-4C5E-B348-E4E46FE675F9} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B} {8C8EAC01-DC49-4C5E-B348-E4E46FE675F9} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B} = {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} {9755F612-A155-4BDD-9E20-37ADE0B4B3BA} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
{72E3AB32-682F-42AF-B7C7-0B777244FF11} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B} {72E3AB32-682F-42AF-B7C7-0B777244FF11} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}
{B7246F23-6A4B-492F-AB61-292AA1A9E9D5} = {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 EndGlobalSection
EndGlobal 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; set;
} }
public bool UseZeroMask
{
get;
set;
}
public Action<HttpWebRequest> ConfigureRequest public Action<HttpWebRequest> ConfigureRequest
{ {
get; get;
@ -114,7 +108,7 @@ namespace Microsoft.AspNetCore.WebSockets.Client
Stream stream = response.GetResponseStream(); 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics.Contracts;
using System.IO; using System.IO;
using System.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.WebSockets.Protocol namespace Microsoft.AspNetCore.WebSockets.Protocol
{ {
// https://tools.ietf.org/html/rfc6455 public static class CommonWebSocket
public class CommonWebSocket : WebSocket
{ {
private readonly static byte[] PingBuffer = Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz"); public static WebSocket CreateClientWebSocket(Stream stream, string subProtocol, TimeSpan keepAliveInterval, int receiveBufferSize)
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)
{ {
_stream = stream; return ManagedWebSocket.CreateFromConnectedStream(
_subProtocl = subProtocol; stream,
_state = WebSocketState.Open; isServer: false,
_receiveBuffer = new byte[receiveBufferSize]; subprotocol: subProtocol,
_maskOutput = maskOutput; keepAliveIntervalSeconds: (int)keepAliveInterval.TotalSeconds,
_useZeroMask = useZeroMask; receiveBufferSize: receiveBufferSize);
_unmaskInput = unmaskInput;
_writeLock = new SemaphoreSlim(1);
if (keepAliveInterval != Timeout.InfiniteTimeSpan)
{
_keepAliveTimer = new Timer(SendKeepAlive, this, keepAliveInterval, keepAliveInterval);
}
} }
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); return ManagedWebSocket.CreateFromConnectedStream(
} stream,
isServer: true,
public static CommonWebSocket CreateServerWebSocket(Stream stream, string subProtocol, TimeSpan keepAliveInterval, int receiveBufferSize) subprotocol: subProtocol,
{ keepAliveIntervalSeconds: (int)keepAliveInterval.TotalSeconds,
return new CommonWebSocket(stream, subProtocol, keepAliveInterval, receiveBufferSize, maskOutput: false, useZeroMask: false, unmaskInput: true); receiveBufferSize: receiveBufferSize);
}
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
} }
} }
} }

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, "warningsAsErrors": true,
"keyFile": "../../tools/Key.snk", "keyFile": "../../tools/Key.snk",
"nowarn": [ "nowarn": [
"CS1591" "CS1591",
"CS1572",
"CS1573"
], ],
"xmlDoc": true "xmlDoc": true,
"allowUnsafe": true
}, },
"frameworks": { "frameworks": {
"net451": {}, "net451": {},
"netstandard1.3": { "netstandard1.3": {
"dependencies": { "dependencies": {
"System.Diagnostics.Debug": "4.0.11-*",
"System.Diagnostics.Contracts": "4.0.1-*", "System.Diagnostics.Contracts": "4.0.1-*",
"System.Globalization": "4.0.11-*", "System.Globalization": "4.0.11-*",
"System.IO": "4.1.0-*", "System.IO": "4.1.0-*",

View File

@ -1,7 +1,7 @@
# #
# RunAutobahnTests.ps1 # 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)) { 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." 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" $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 { $SpecJson = [IO.File]::ReadAllText($Spec).Replace("OUTPUTDIR", $OutputDir.Replace("\", "\\")).Replace("WEBSOCKETURL", $ServerUrl).Replace("`"CASES`"", $CasesArray)
[IO.File]::WriteAllText($TempFile, $SpecJson)
& wstest -m fuzzingclient -s $TempFile $TempFile = [IO.Path]::GetTempFileName()
} finally {
if(Test-Path $TempFile) { try {
rm $TempFile [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)] [FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task ReceiveLongDataInSmallBuffer_Success() public async Task ReceiveLongData(int receiveBufferSize)
{ {
var orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF)); var orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF));
using (var server = KestrelWebSocketHelpers.CreateServer(async context => 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); 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)) using (var clientSocket = await client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None))
{ {
var clientBuffer = new byte[orriginalData.Length]; 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] [ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)] [FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task ReceiveFragmentedData_Success() 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;
using System.Collections.Concurrent; using System.Collections.Concurrent;

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;
using System.IO; using System.IO;

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> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals"> <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> <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
@ -16,5 +18,5 @@
<ItemGroup> <ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup> </ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </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

@ -5,14 +5,10 @@ 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 // set of attributes. Change these attribute values to modify the information
// associated with an assembly. // associated with an assembly.
[assembly: AssemblyTitle("Microsoft.Net.WebSockets.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Microsoft.Net.WebSockets.Test")] [assembly: AssemblyProduct("Microsoft.AspNetCore.WebSockets.Protocol.Test")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible // 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 // to COM components. If you need to access a type in this assembly from
@ -20,17 +16,4 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM // The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("237d6e8f-6e5e-4c3f-96b4-b19cf3bf4d80")] [assembly: Guid("aaf2dfcf-845e-4410-bbf0-0683ad60dd6a")]
// 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")]

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": { "dependencies": {
"dotnet-test-xunit": "2.2.0-*", "dotnet-test-xunit": "2.2.0-*",
"Microsoft.AspNetCore.WebSockets.Protocol": "0.2.0-*", "Microsoft.AspNetCore.WebSockets.Protocol": "0.2.0-*",
@ -13,7 +13,6 @@
"type": "platform" "type": "platform"
} }
} }
}, }
"net451": {}
} }
} }

View File

@ -37,19 +37,22 @@ namespace Microsoft.AspNetCore.WebSockets.Server.Test
.IncludeCase("*") .IncludeCase("*")
.ExcludeCase("9.*", "12.*", "13.*"); .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; AutobahnResult result;
using (var tester = new AutobahnTester(loggerFactory, spec)) using (var tester = new AutobahnTester(loggerFactory, spec))
{ {
await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: false, environment: "ManagedSockets", expectationConfig: expect => expect await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: false, environment: "ManagedSockets");
.NonStrict("6.4.3", "6.4.4")); // https://github.com/aspnet/WebSockets/issues/99
// Windows-only IIS tests, and Kestrel SSL tests (due to: https://github.com/aspnet/WebSockets/issues/102) // Windows-only IIS tests, and Kestrel SSL tests (due to: https://github.com/aspnet/WebSockets/issues/102)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: true, environment: "ManagedSockets", expectationConfig: expect => expect await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: true, environment: "ManagedSockets");
.NonStrict("6.4.3", "6.4.4")); // https://github.com/aspnet/WebSockets/issues/99
if (IsWindows8OrHigher()) 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 .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 await tester.DeployTestAndAddToSpec(ServerType.WebListener, ssl: false, environment: "ManagedSockets");
.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
} }
} }

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?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> <PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals"> <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> <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup> </PropertyGroup>