import WebSockets code from CoreFX (#105)
* import WebSockets code from CoreFX * sync pr feedback from dotnet/corefx#10510
This commit is contained in:
parent
8b25152a67
commit
45f0fe178f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)})";
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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).
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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-*",
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue