diff --git a/WebListener.sln b/WebListener.sln index 52409addaa..5623786165 100644 --- a/WebListener.sln +++ b/WebListener.sln @@ -16,8 +16,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HelloWorld", "samples\Hello EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SelfHostServer", "samples\SelfHostServer\SelfHostServer.xproj", "{1236F93A-AC5C-4A77-9477-C88F040151CA}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Net.WebSockets.Server", "src\Microsoft.Net.WebSockets.Server\Microsoft.Net.WebSockets.Server.xproj", "{E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.WebListener.FunctionalTests", "test\Microsoft.AspNetCore.Server.WebListener.FunctionalTests\Microsoft.AspNetCore.Server.WebListener.FunctionalTests.xproj", "{4492FF4C-9032-411D-853F-46B01755E504}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.WebListener", "src\Microsoft.AspNetCore.Server.WebListener\Microsoft.AspNetCore.Server.WebListener.xproj", "{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}" @@ -83,16 +81,6 @@ Global {1236F93A-AC5C-4A77-9477-C88F040151CA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {1236F93A-AC5C-4A77-9477-C88F040151CA}.Release|Mixed Platforms.Build.0 = Release|Any CPU {1236F93A-AC5C-4A77-9477-C88F040151CA}.Release|x86.ActiveCfg = Release|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Debug|x86.ActiveCfg = Debug|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Release|Any CPU.Build.0 = Release|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}.Release|x86.ActiveCfg = Release|Any CPU {4492FF4C-9032-411D-853F-46B01755E504}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4492FF4C-9032-411D-853F-46B01755E504}.Debug|Any CPU.Build.0 = Debug|Any CPU {4492FF4C-9032-411D-853F-46B01755E504}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -156,7 +144,6 @@ Global {3F5212AA-E287-49DD-8CEC-44BF0A2AC9A1} = {99D5E5F3-88F5-4CCF-8D8C-717C8925DF09} {6DAF3E6B-8E1B-4E6E-B9FE-7B1E5FDB7DB4} = {3A1E31E3-2794-4CA3-B8E2-253E96BDE514} {1236F93A-AC5C-4A77-9477-C88F040151CA} = {3A1E31E3-2794-4CA3-B8E2-253E96BDE514} - {E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB} = {99D5E5F3-88F5-4CCF-8D8C-717C8925DF09} {4492FF4C-9032-411D-853F-46B01755E504} = {E183C826-1360-4DFF-9994-F33CED5C8525} {B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92} = {99D5E5F3-88F5-4CCF-8D8C-717C8925DF09} {DCB6E0B1-223D-44E6-8696-4767E5B6E6A1} = {E183C826-1360-4DFF-9994-F33CED5C8525} diff --git a/samples/HelloWorld/Program.cs b/samples/HelloWorld/Program.cs index 7e74bf87d0..6ec6112781 100644 --- a/samples/HelloWorld/Program.cs +++ b/samples/HelloWorld/Program.cs @@ -63,7 +63,7 @@ namespace HelloWorld // Response byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now); - if (context.IsWebSocketRequest()) + if (context.IsWebSocketRequest) { Console.WriteLine("WebSocket"); WebSocket webSocket = await context.AcceptWebSocketAsync(); diff --git a/samples/HelloWorld/project.json b/samples/HelloWorld/project.json index 2cd8e62d8f..b63c18564c 100644 --- a/samples/HelloWorld/project.json +++ b/samples/HelloWorld/project.json @@ -3,8 +3,7 @@ "emitEntryPoint": true }, "dependencies": { - "Microsoft.Net.Http.Server": "0.2.0-*", - "Microsoft.Net.WebSockets.Server": "0.2.0-*" + "Microsoft.Net.Http.Server": "0.2.0-*" }, "commands": { "sample": "HelloWorld" diff --git a/scripts/UpdateCoreFxCode.ps1 b/scripts/UpdateCoreFxCode.ps1 new file mode 100644 index 0000000000..03cd9dfdd5 --- /dev/null +++ b/scripts/UpdateCoreFxCode.ps1 @@ -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.Net.Http.Server\fx\" + +$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) + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Server.WebListener/FeatureContext.cs b/src/Microsoft.AspNetCore.Server.WebListener/FeatureContext.cs index 61109f123f..df448e2b72 100644 --- a/src/Microsoft.AspNetCore.Server.WebListener/FeatureContext.cs +++ b/src/Microsoft.AspNetCore.Server.WebListener/FeatureContext.cs @@ -41,9 +41,7 @@ namespace Microsoft.AspNetCore.Server.WebListener ITlsTokenBindingFeature, IHttpBufferingFeature, IHttpRequestLifetimeFeature, -#if WEBSOCKETS IHttpWebSocketFeature, -#endif IHttpAuthenticationFeature, IHttpUpgradeFeature, IHttpRequestIdentifierFeature @@ -436,14 +434,8 @@ namespace Microsoft.AspNetCore.Server.WebListener { return _requestContext.UpgradeAsync(); } -#if WEBSOCKETS - bool IHttpWebSocketFeature.IsWebSocketRequest - { - get - { - return _requestContext.IsWebSocketRequest(); - } - } + + bool IHttpWebSocketFeature.IsWebSocketRequest => _requestContext.IsWebSocketRequest; Task IHttpWebSocketFeature.AcceptAsync(WebSocketAcceptContext context) { @@ -455,7 +447,7 @@ namespace Microsoft.AspNetCore.Server.WebListener } return _requestContext.AcceptWebSocketAsync(subProtocol); } -#endif + ClaimsPrincipal IHttpAuthenticationFeature.User { get diff --git a/src/Microsoft.AspNetCore.Server.WebListener/StandardFeatureCollection.cs b/src/Microsoft.AspNetCore.Server.WebListener/StandardFeatureCollection.cs index eccf7418e5..274503edee 100644 --- a/src/Microsoft.AspNetCore.Server.WebListener/StandardFeatureCollection.cs +++ b/src/Microsoft.AspNetCore.Server.WebListener/StandardFeatureCollection.cs @@ -38,9 +38,7 @@ namespace Microsoft.AspNetCore.Server.WebListener { typeof(IHttpBufferingFeature), _identityFunc }, { typeof(IHttpRequestLifetimeFeature), _identityFunc }, { typeof(IHttpUpgradeFeature), _identityFunc }, -#if WEBSOCKETS { typeof(IHttpWebSocketFeature), _identityFunc }, -#endif { typeof(IHttpAuthenticationFeature), _identityFunc }, { typeof(IHttpRequestIdentifierFeature), _identityFunc }, { typeof(RequestContext), ctx => ctx.RequestContext }, diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs index 659ec35310..f812891410 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs @@ -207,7 +207,7 @@ namespace Microsoft.Net.Http.Server public HeaderCollection Headers { get; } - private UnsafeNclNativeMethods.HttpApi.HTTP_VERB KnownMethod { get; } + internal UnsafeNclNativeMethods.HttpApi.HTTP_VERB KnownMethod { get; } public bool IsHeadMethod => KnownMethod == UnsafeNclNativeMethods.HttpApi.HTTP_VERB.HttpVerbHEAD; diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/RequestContext.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/RequestContext.cs index 99c5221a82..6f588fae19 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/RequestContext.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/RequestContext.cs @@ -24,6 +24,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Net.WebSockets; using System.Security.Authentication.ExtendedProtection; using System.Security.Claims; using System.Threading; @@ -120,6 +121,155 @@ namespace Microsoft.Net.Http.Server return Task.FromResult(opaqueStream); } + // Compare ValidateWebSocketRequest + public bool IsWebSocketRequest + { + get + { + if (!WebSocketHelpers.AreWebSocketsSupported) + { + return false; + } + + if (!IsUpgradableRequest) + { + return false; + } + + if (Request.KnownMethod != UnsafeNclNativeMethods.HttpApi.HTTP_VERB.HttpVerbGET) + { + return false; + } + + // Connection: Upgrade (some odd clients send Upgrade,KeepAlive) + var connection = Request.Headers[HttpKnownHeaderNames.Connection].ToString(); + if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) + { + return false; + } + + // Upgrade: websocket + var upgrade = Request.Headers[HttpKnownHeaderNames.Upgrade]; + if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Sec-WebSocket-Version: 13 + var version = Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; + if (!string.Equals(WebSocketHelpers.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Sec-WebSocket-Key: {base64string} + var key = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; + if (!WebSocketHelpers.IsValidWebSocketKey(key)) + { + return false; + } + + return true; + } + } + + // Compare IsWebSocketRequest() + private void ValidateWebSocketRequest() + { + if (!WebSocketHelpers.AreWebSocketsSupported) + { + throw new NotSupportedException("WebSockets are not supported on this platform."); + } + + if (!IsUpgradableRequest) + { + throw new InvalidOperationException("This request is not a valid upgrade request."); + } + + if (Request.KnownMethod != UnsafeNclNativeMethods.HttpApi.HTTP_VERB.HttpVerbGET) + { + throw new InvalidOperationException("This request is not a valid upgrade request; invalid verb: " + Request.Method); + } + + // Connection: Upgrade (some odd clients send Upgrade,KeepAlive) + var connection = Request.Headers[HttpKnownHeaderNames.Connection].ToString(); + if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) + { + throw new InvalidOperationException("The Connection header is invalid: " + connection); + } + + // Upgrade: websocket + var upgrade = Request.Headers[HttpKnownHeaderNames.Upgrade]; + if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("The Upgrade header is invalid: " + upgrade); + } + + // Sec-WebSocket-Version: 13 + var version = Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; + if (!string.Equals(WebSocketHelpers.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("The Sec-WebSocket-Version header is invalid or not supported: " + version); + } + + // Sec-WebSocket-Key: {base64string} + var key = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; + if (!WebSocketHelpers.IsValidWebSocketKey(key)) + { + throw new InvalidOperationException("The Sec-WebSocket-Key header is invalid: " + upgrade); + } + } + + public Task AcceptWebSocketAsync() + { + return AcceptWebSocketAsync(null, WebSocketHelpers.DefaultReceiveBufferSize, WebSocketHelpers.DefaultKeepAliveInterval); + } + + public Task AcceptWebSocketAsync(string subProtocol) + { + return AcceptWebSocketAsync(subProtocol, WebSocketHelpers.DefaultReceiveBufferSize, WebSocketHelpers.DefaultKeepAliveInterval); + } + + public Task AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval) + { + return AcceptWebSocketAsync(subProtocol, WebSocketHelpers.DefaultReceiveBufferSize, keepAliveInterval); + } + + public Task AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval) + { + if (!IsUpgradableRequest) + { + throw new InvalidOperationException("This request is cannot be upgraded."); + } + WebSocketHelpers.ValidateOptions(subProtocol, keepAliveInterval); + + return AcceptWebSocketAsyncCore(subProtocol, receiveBufferSize, keepAliveInterval); + } + + private async Task AcceptWebSocketAsyncCore(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval) + { + ValidateWebSocketRequest(); + + var subProtocols = Request.Headers.GetValues(HttpKnownHeaderNames.SecWebSocketProtocol); + var shouldSendSecWebSocketProtocolHeader = WebSocketHelpers.ProcessWebSocketProtocolHeader(subProtocols, subProtocol); + if (shouldSendSecWebSocketProtocolHeader) + { + Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = subProtocol; + } + + // negotiate the websocket key return value + var secWebSocketKey = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; + var secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); + + Response.Headers.Append(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); + Response.Headers.Append(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken); + Response.Headers.Append(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); + + var opaqueStream = await UpgradeAsync(); + + return WebSocketHelpers.CreateServerWebSocket(opaqueStream, subProtocol, receiveBufferSize, keepAliveInterval); + } + // TODO: Public when needed internal bool TryGetChannelBinding(ref ChannelBinding value) { diff --git a/src/Microsoft.Net.Http.Server/WebSocketHelpers.cs b/src/Microsoft.Net.Http.Server/WebSocketHelpers.cs new file mode 100644 index 0000000000..6d4822ab3e --- /dev/null +++ b/src/Microsoft.Net.Http.Server/WebSocketHelpers.cs @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.WebSockets; +using System.Security.Cryptography; +using System.Text; +using System.Threading; + +namespace Microsoft.Net.Http.Server +{ + internal static class WebSocketHelpers + { + internal static string SupportedProtocolVersion = "13"; + + internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + internal const string WebSocketUpgradeToken = "websocket"; + internal const int DefaultReceiveBufferSize = 16 * 1024; + internal const int DefaultClientSendBufferSize = 16 * 1024; + internal const int MaxControlFramePayloadLength = 123; + internal static readonly TimeSpan DefaultKeepAliveInterval = TimeSpan.FromMinutes(2); + + // RFC 6455 requests WebSocket clients to let the server initiate the TCP close to avoid that client sockets + // end up in TIME_WAIT-state + // + // After both sending and receiving a Close message, an endpoint considers the WebSocket connection closed and + // MUST close the underlying TCP connection. The server MUST close the underlying TCP connection immediately; + // the client SHOULD wait for the server to close the connection but MAY close the connection at any time after + // sending and receiving a Close message, e.g., if it has not received a TCP Close from the server in a + // reasonable time period. + internal const int ClientTcpCloseTimeout = 1000; // 1s + + 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 readonly ArraySegment EmptyPayload = new ArraySegment(new byte[] { }, 0, 0); + private static readonly Random KeyGenerator = new Random(); + + internal static bool AreWebSocketsSupported + { + get + { + // https://github.com/aspnet/WebListener/issues/215 + return true; // TODO: ComNetOS.IsWin8orLater; + } + } + + internal static bool IsValidWebSocketKey(string key) + { + if (string.IsNullOrWhiteSpace(key)) + { + return false; + } + // TODO: + // throw new NotImplementedException(); + return true; + } + + internal static string GetSecWebSocketAcceptString(string secWebSocketKey) + { + string retVal; + // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat. + using (SHA1 sha1 = SHA1.Create()) + { + string acceptString = string.Concat(secWebSocketKey, WebSocketHelpers.SecWebSocketKeyGuid); + byte[] toHash = Encoding.UTF8.GetBytes(acceptString); + retVal = Convert.ToBase64String(sha1.ComputeHash(toHash)); + } + return retVal; + } + + internal static WebSocket CreateServerWebSocket(Stream opaqueStream, string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval) + { + return ManagedWebSocket.CreateFromConnectedStream(opaqueStream, isServer: true, subprotocol: subProtocol, + keepAliveIntervalSeconds: (int)keepAliveInterval.TotalSeconds, receiveBufferSize: receiveBufferSize); + } + + // return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server. + internal static bool ProcessWebSocketProtocolHeader(IEnumerable clientSecWebSocketProtocols, string subProtocol) + { + if (clientSecWebSocketProtocols == null || !clientSecWebSocketProtocols.Any()) + { + // client hasn't specified any Sec-WebSocket-Protocol header + if (!string.IsNullOrEmpty(subProtocol)) + { + // If the server specified _anything_ this isn't valid. + throw new WebSocketException(WebSocketError.UnsupportedProtocol, + "The client did not specify a Sec-WebSocket-Protocol header. SubProtocol: " + subProtocol); + } + // Treat empty and null from the server as the same thing here, server should not send headers. + return false; + } + + // here, we know the client specified something and it's non-empty. + + if (string.IsNullOrEmpty(subProtocol)) + { + // client specified some protocols, server specified 'null'. So server should send headers. + return false; + } + + // here, we know that the client has specified something, it's not empty + // and the server has specified exactly one protocol + + // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that + // this exists in the list the client specified. + foreach (var currentRequestProtocol in clientSecWebSocketProtocols) + { + if (string.Compare(subProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0) + { + return true; + } + } + + throw new WebSocketException(WebSocketError.UnsupportedProtocol, + $"Unsupported protocol: {subProtocol}; Client supported protocols: {string.Join(", ", clientSecWebSocketProtocols)}"); + } + + internal static void ValidateSubprotocol(string subProtocol) + { + if (string.IsNullOrEmpty(subProtocol)) + { + return; + } + + char[] chars = subProtocol.ToCharArray(); + string invalidChar = null; + int i = 0; + while (i < chars.Length) + { + char ch = chars[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($"Invalid character '{invalidChar}' in the subProtocol '{subProtocol}'", nameof(subProtocol)); + } + } + + internal static void ValidateOptions(string subProtocol, TimeSpan keepAliveInterval) + { + ValidateSubprotocol(subProtocol); + + // -1 + if (keepAliveInterval < Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(keepAliveInterval), keepAliveInterval, + "The value must be greater than or equal too 0 seconds, or -1 second to disable."); + } + } + } +} diff --git a/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/CompatHelpers.cs b/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/CompatHelpers.cs new file mode 100644 index 0000000000..9bb3fa57fe --- /dev/null +++ b/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/CompatHelpers.cs @@ -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(); + tcs.SetResult(null); + CompletedTask = tcs.Task; + } + + public static Task FromException(Exception ex) + { +#if NET451 + return FromException(ex); +#else + return Task.FromException(ex); +#endif + } + + public static Task FromException(Exception ex) + { +#if NET451 + var tcs = new TaskCompletionSource(); + tcs.SetException(ex); + return tcs.Task; +#else + return Task.FromException(ex); +#endif + } + + internal static T[] Empty() + { +#if NET451 + return new T[0]; +#else + return Array.Empty(); +#endif + } + } + + // This is just here to be used by a nameof in the CoreFX code. + //internal static class ClientWebSocket { } +} \ No newline at end of file diff --git a/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/README.md b/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/README.md new file mode 100644 index 0000000000..4b8f84b7ed --- /dev/null +++ b/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/README.md @@ -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). \ No newline at end of file diff --git a/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/SR.cs b/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/SR.cs new file mode 100644 index 0000000000..8753b1c60e --- /dev/null +++ b/src/Microsoft.Net.Http.Server/fx/System/Net/WebSockets/SR.cs @@ -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)})"; + } +} \ No newline at end of file diff --git a/src/Microsoft.Net.Http.Server/fx/src/Common/src/System/Net/WebSockets/WebSocketValidate.cs b/src/Microsoft.Net.Http.Server/fx/src/Common/src/System/Net/WebSockets/WebSocketValidate.cs new file mode 100644 index 0000000000..06e07f29dd --- /dev/null +++ b/src/Microsoft.Net.Http.Server/fx/src/Common/src/System/Net/WebSockets/WebSocketValidate.cs @@ -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 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)); + } + } +} diff --git a/src/Microsoft.Net.Http.Server/fx/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/ManagedWebSocket.cs b/src/Microsoft.Net.Http.Server/fx/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/ManagedWebSocket.cs new file mode 100644 index 0000000000..738b0a7348 --- /dev/null +++ b/src/Microsoft.Net.Http.Server/fx/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/ManagedWebSocket.cs @@ -0,0 +1,1312 @@ +// 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.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +// NOTE: This file is shared between CoreFX and ASP.NET. Be very thoughtful when changing it. + +namespace System.Net.WebSockets +{ + /// A managed implementation of a web socket that sends and receives data via a . + /// + /// Thread-safety: + /// - It's acceptable to call ReceiveAsync and SendAsync in parallel. One of each may run concurrently. + /// - It's acceptable to have a pending ReceiveAsync while CloseOutputAsync or CloseAsync is called. + /// - Attemping to invoke any other operations in parallel may corrupt the instance. Attempting to invoke + /// a send operation while another is in progress or a receive operation while another is in progress will + /// result in an exception. + /// + internal sealed class ManagedWebSocket : WebSocket + { + /// Creates a from a connected to a websocket endpoint. + /// The connected Stream. + /// true if this is the server-side of the connection; false if this is the client-side of the connection. + /// The agreed upon subprotocol for the connection. + /// The current state of the websocket connection. + /// The interval to use for keep-alive pings. + /// The buffer size to use for received data. + /// The created instance. + public static ManagedWebSocket CreateFromConnectedStream( + Stream stream, bool isServer, string subprotocol, + int keepAliveIntervalSeconds = 30, int receiveBufferSize = 0x1000) + { + return new ManagedWebSocket(stream, isServer, subprotocol, TimeSpan.FromSeconds(keepAliveIntervalSeconds), receiveBufferSize); + } + + /// Per-thread cached 4-byte mask byte array. + [ThreadStatic] + private static byte[] t_headerMask; + + /// Thread-safe random number generator used to generate masks for each send. + private static readonly RandomNumberGenerator s_random = RandomNumberGenerator.Create(); + /// Encoding for the payload of text messages: UTF8 encoding that throws if invalid bytes are discovered, per the RFC. + private static readonly UTF8Encoding s_textEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + /// Valid states to be in when calling SendAsync. + private static readonly WebSocketState[] s_validSendStates = { WebSocketState.Open, WebSocketState.CloseReceived }; + /// Valid states to be in when calling ReceiveAsync. + private static readonly WebSocketState[] s_validReceiveStates = { WebSocketState.Open, WebSocketState.CloseSent }; + /// Valid states to be in when calling CloseOutputAsync. + private static readonly WebSocketState[] s_validCloseOutputStates = { WebSocketState.Open, WebSocketState.CloseReceived }; + /// Valid states to be in when calling CloseAsync. + private static readonly WebSocketState[] s_validCloseStates = { WebSocketState.Open, WebSocketState.CloseReceived, WebSocketState.CloseSent }; + + /// The maximum size in bytes of a message frame header that includes mask bytes. + private const int MaxMessageHeaderLength = 14; + /// The maximum size of a control message payload. + private const int MaxControlPayloadLength = 125; + /// Length of the mask XOR'd with the payload data. + private const int MaskLength = 4; + + /// The stream used to communicate with the remote server. + private readonly Stream _stream; + /// + /// true if this is the server-side of the connection; false if it's client. + /// This impacts masking behavior: clients always mask payloads they send and + /// expect to always receive unmasked payloads, whereas servers always send + /// unmasked payloads and expect to always receive masked payloads. + /// + private readonly bool _isServer = false; + /// The agreed upon subprotocol with the server. + private readonly string _subprotocol; + /// Timer used to send periodic pings to the server, at the interval specified + private readonly Timer _keepAliveTimer; + /// CancellationTokenSource used to abort all current and future operations when anything is canceled or any error occurs. + private readonly CancellationTokenSource _abortSource = new CancellationTokenSource(); + /// Buffer used for reading data from the network. + private readonly byte[] _receiveBuffer; + /// + /// Tracks the state of the validity of the UTF8 encoding of text payloads. Text may be split across fragments. + /// + private readonly Utf8MessageState _utf8TextState = new Utf8MessageState(); + /// + /// Semaphore used to ensure that calls to SendFrameAsync don't run concurrently. While + /// is used to fail if a caller tries to issue another SendAsync while a previous one is running, internally + /// we use SendFrameAsync as an implementation detail, and it should not cause user requests to SendAsync to fail, + /// nor should such internal usage be allowed to run concurrently with other internal usage or with SendAsync. + /// + private readonly SemaphoreSlim _sendFrameAsyncLock = new SemaphoreSlim(1, 1); + + // We maintain the current WebSocketState in _state. However, we separately maintain _sentCloseFrame and _receivedCloseFrame + // as there isn't a strict ordering between CloseSent and CloseReceived. If we receive a close frame from the server, we need to + // transition to CloseReceived even if we're currently in CloseSent, and if we send a close frame, we need to transition to + // CloseSent even if we're currently in CloseReceived. + + /// The current state of the web socket in the protocol. + private WebSocketState _state = WebSocketState.Open; + /// true if Dispose has been called; otherwise, false. + private bool _disposed; + /// Whether we've ever sent a close frame. + private bool _sentCloseFrame; + /// Whether we've ever received a close frame. + private bool _receivedCloseFrame; + /// The reason for the close, as sent by the server, or null if not yet closed. + private WebSocketCloseStatus? _closeStatus = null; + /// A description of the close reason as sent by the server, or null if not yet closed. + private string _closeStatusDescription = null; + + /// + /// The last header received in a ReceiveAsync. If ReceiveAsync got a header but then + /// returned fewer bytes than was indicated in the header, subsequent ReceiveAsync calls + /// will use the data from the header to construct the subsequent receive results, and + /// the payload length in this header will be decremented to indicate the number of bytes + /// remaining to be received for that header. As a result, between fragments, the payload + /// length in this header should be 0. + /// + private MessageHeader _lastReceiveHeader = new MessageHeader { Opcode = MessageOpcode.Text, Fin = true }; + /// The offset of the next available byte in the _receiveBuffer. + private int _receiveBufferOffset = 0; + /// The number of bytes available in the _receiveBuffer. + private int _receiveBufferCount = 0; + /// + /// When dealing with partially read fragments of binary/text messages, a mask previously received may still + /// apply, and the first new byte received may not correspond to the 0th position in the mask. This value is + /// the next offset into the mask that should be applied. + /// + private int _receivedMaskOffsetOffset = 0; + /// + /// Buffer used to store the complete message to be sent to the stream. This is needed + /// rather than just sending a header and then the user's buffer, as we need to mutate the + /// buffered data with the mask, and we don't want to change the data in the user's buffer. + /// + private byte[] _sendBuffer; + /// + /// Whether the last SendAsync had endOfMessage==false. We need to track this so that we + /// can send the subsequent message with a continuation opcode if the last message was a fragment. + /// + private bool _lastSendWasFragment; + /// + /// The task returned from the last SendAsync operation to not complete synchronously. + /// If this is not null and not completed when a subsequent SendAsync is issued, an exception occurs. + /// + private Task _lastSendAsync; + /// + /// The task returned from the last ReceiveAsync operation to not complete synchronously. + /// If this is not null and not completed when a subsequent ReceiveAsync is issued, an exception occurs. + /// + private Task _lastReceiveAsync; + + /// Lock used to protect update and check-and-update operations on _state. + private object StateUpdateLock => _abortSource; + /// + /// We need to coordinate between receives and close operations happening concurrently, as a ReceiveAsync may + /// be pending while a Close{Output}Async is issued, which itself needs to loop until a close frame is received. + /// As such, we need thread-safety in the management of . + /// + private object ReceiveAsyncLock => _utf8TextState; // some object, as we're simply lock'ing on it + + /// Initializes the websocket. + /// The connected Stream. + /// true if this is the server-side of the connection; false if this is the client-side of the connection. + /// The agreed upon subprotocol for the connection. + /// The interval to use for keep-alive pings. + /// The buffer size to use for received data. + private ManagedWebSocket(Stream stream, bool isServer, string subprotocol, TimeSpan keepAliveInterval, int receiveBufferSize) + { + Debug.Assert(StateUpdateLock != null, $"Expected {nameof(StateUpdateLock)} to be non-null"); + Debug.Assert(ReceiveAsyncLock != null, $"Expected {nameof(ReceiveAsyncLock)} to be non-null"); + Debug.Assert(StateUpdateLock != ReceiveAsyncLock, "Locks should be different objects"); + + Debug.Assert(stream != null, $"Expected non-null stream"); + Debug.Assert(stream.CanRead, $"Expected readable stream"); + Debug.Assert(stream.CanWrite, $"Expected writeable stream"); + Debug.Assert(keepAliveInterval == Timeout.InfiniteTimeSpan || keepAliveInterval >= TimeSpan.Zero, $"Invalid keepalive interval: {keepAliveInterval}"); + Debug.Assert(receiveBufferSize >= MaxMessageHeaderLength, $"Receive buffer size {receiveBufferSize} is too small"); + + _stream = stream; + _isServer = isServer; + _subprotocol = subprotocol; + _receiveBuffer = new byte[Math.Max(receiveBufferSize, MaxMessageHeaderLength)]; + + // Set up the abort source so that if it's triggered, we transition the instance appropriately. + _abortSource.Token.Register(s => + { + var thisRef = (ManagedWebSocket)s; + + lock (thisRef.StateUpdateLock) + { + WebSocketState state = thisRef._state; + if (state != WebSocketState.Closed && state != WebSocketState.Aborted) + { + thisRef._state = state != WebSocketState.None && state != WebSocketState.Connecting ? + WebSocketState.Aborted : + WebSocketState.Closed; + } + } + }, this); + + // Now that we're opened, initiate the keep alive timer to send periodic pings + if (keepAliveInterval > TimeSpan.Zero) + { + _keepAliveTimer = new Timer(s => ((ManagedWebSocket)s).SendKeepAliveFrameAsync(), this, keepAliveInterval, keepAliveInterval); + } + } + + public override void Dispose() + { + lock (StateUpdateLock) + { + DisposeCore(); + } + } + + private void DisposeCore() + { + Debug.Assert(Monitor.IsEntered(StateUpdateLock), $"Expected {nameof(StateUpdateLock)} to be held"); + if (!_disposed) + { + _disposed = true; + _keepAliveTimer?.Dispose(); + _stream?.Dispose(); + if (_state < WebSocketState.Aborted) + { + _state = WebSocketState.Closed; + } + } + } + + public override WebSocketCloseStatus? CloseStatus => _closeStatus; + + public override string CloseStatusDescription => _closeStatusDescription; + + public override WebSocketState State => _state; + + public override string SubProtocol => _subprotocol; + + public override Task SendAsync(ArraySegment buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) + { + if (messageType != WebSocketMessageType.Text && messageType != WebSocketMessageType.Binary) + { + throw new ArgumentException(SR.Format( + SR.net_WebSockets_Argument_InvalidMessageType, + nameof(WebSocketMessageType.Close), nameof(SendAsync), nameof(WebSocketMessageType.Binary), nameof(WebSocketMessageType.Text), nameof(CloseOutputAsync)), + nameof(messageType)); + } + WebSocketValidate.ValidateArraySegment(buffer, nameof(buffer)); + + try + { + WebSocketValidate.ThrowIfInvalidState(_state, _disposed, s_validSendStates); + ThrowIfOperationInProgress(_lastSendAsync); + } + catch (Exception exc) + { + return CompatHelpers.FromException(exc); + } + + MessageOpcode opcode = + _lastSendWasFragment ? MessageOpcode.Continuation : + messageType == WebSocketMessageType.Binary ? MessageOpcode.Binary : + MessageOpcode.Text; + + Task t = SendFrameAsync(opcode, endOfMessage, buffer, cancellationToken); + _lastSendWasFragment = !endOfMessage; + _lastSendAsync = t; + return t; + } + + public override Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) + { + WebSocketValidate.ValidateArraySegment(buffer, nameof(buffer)); + + try + { + WebSocketValidate.ThrowIfInvalidState(_state, _disposed, s_validReceiveStates); + + Debug.Assert(!Monitor.IsEntered(StateUpdateLock), $"{nameof(StateUpdateLock)} must never be held when acquiring {nameof(ReceiveAsyncLock)}"); + lock (ReceiveAsyncLock) // synchronize with receives in CloseAsync + { + ThrowIfOperationInProgress(_lastReceiveAsync); + Task t = ReceiveAsyncPrivate(buffer, cancellationToken); + _lastReceiveAsync = t; + return t; + } + } + catch (Exception exc) + { + return CompatHelpers.FromException(exc); + } + } + + public override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) + { + WebSocketValidate.ValidateCloseStatus(closeStatus, statusDescription); + + try + { + WebSocketValidate.ThrowIfInvalidState(_state, _disposed, s_validCloseStates); + } + catch (Exception exc) + { + return CompatHelpers.FromException(exc); + } + + return CloseAsyncPrivate(closeStatus, statusDescription, cancellationToken); + } + + public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) + { + WebSocketValidate.ValidateCloseStatus(closeStatus, statusDescription); + + try + { + WebSocketValidate.ThrowIfInvalidState(_state, _disposed, s_validCloseOutputStates); + } + catch (Exception exc) + { + return CompatHelpers.FromException(exc); + } + + return SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken); + } + + public override void Abort() + { + _abortSource.Cancel(); + Dispose(); // forcibly tear down connection + } + + /// Sends a websocket frame to the network. + /// The opcode for the message. + /// The value of the FIN bit for the message. + /// The buffer containing the payload data fro the message. + /// The CancellationToken to use to cancel the websocket. + private Task SendFrameAsync(MessageOpcode opcode, bool endOfMessage, ArraySegment payloadBuffer, CancellationToken cancellationToken) + { + // TODO: #4900 SendFrameAsync should in theory typically complete synchronously, making it fast and allocation free. + // However, due to #4900, it almost always yields, resulting in all of the allocations involved in an async method + // yielding, e.g. the boxed state machine, the Action delegate, the MoveNextRunner, and the resulting Task, plus it's + // common that the awaited operation completes so fast after the await that we may end up allocating an AwaitTaskContinuation + // inside of the TaskAwaiter. Since SendFrameAsync is such a core code path, until that can be fixed, we put some + // optimizations in place to avoid a few of those expenses, at the expense of more complicated code; for the common case, + // this code has fewer than half the number and size of allocations. If/when that issue is fixed, this method should be deleted + // and replaced by SendFrameFallbackAsync, which is the same logic but in a much more easily understand flow. + + // If a cancelable cancellation token was provided, that would require registering with it, which means more state we have to + // pass around (the CancellationTokenRegistration), so if it is cancelable, just immediately go to the fallback path. + // Similarly, it should be rare that there are multiple outstanding calls to SendFrameAsync, but if there are, again + // fall back to the fallback path. + return cancellationToken.CanBeCanceled || !_sendFrameAsyncLock.Wait(0) ? + SendFrameFallbackAsync(opcode, endOfMessage, payloadBuffer, cancellationToken) : + SendFrameLockAcquiredNonCancelableAsync(opcode, endOfMessage, payloadBuffer); + } + + /// Sends a websocket frame to the network. The caller must hold the sending lock. + /// The opcode for the message. + /// The value of the FIN bit for the message. + /// The buffer containing the payload data fro the message. + private Task SendFrameLockAcquiredNonCancelableAsync(MessageOpcode opcode, bool endOfMessage, ArraySegment payloadBuffer) + { + Debug.Assert(_sendFrameAsyncLock.CurrentCount == 0, "Caller should hold the _sendFrameAsyncLock"); + + // If we get here, the cancellation token is not cancelable so we don't have to worry about it, + // and we own the semaphore, so we don't need to asynchronously wait for it. + Task writeTask = null; + bool releaseSemaphore = true; + try + { + // Write the payload synchronously to the buffer, then write that buffer out to the network. + int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, payloadBuffer); + writeTask = _stream.WriteAsync(_sendBuffer, 0, sendBytes, CancellationToken.None); + + // If the operation happens to complete synchronously (or, more specifically, by + // the time we get from the previous line to here, release the semaphore, propagate + // exceptions, and we're done. + if (writeTask.IsCompleted) + { + writeTask.GetAwaiter().GetResult(); // propagate any exceptions + return CompatHelpers.CompletedTask; + } + + // Up until this point, if an exception occurred (such as when accessing _stream or when + // calling GetResult), we want to release the semaphore. After this point, the semaphore needs + // to remain held until writeTask completes. + releaseSemaphore = false; + } + catch (Exception exc) + { + return CompatHelpers.FromException(_state == WebSocketState.Aborted ? + CreateOperationCanceledException(exc) : + new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc)); + } + finally + { + if (releaseSemaphore) + { + _sendFrameAsyncLock.Release(); + } + } + + // The write was not yet completed. Create and return a continuation that will + // release the semaphore and translate any exception that occurred. + return writeTask.ContinueWith((t, s) => + { + var thisRef = (ManagedWebSocket)s; + thisRef._sendFrameAsyncLock.Release(); + + try { t.GetAwaiter().GetResult(); } + catch (Exception exc) + { + throw thisRef._state == WebSocketState.Aborted ? + CreateOperationCanceledException(exc) : + new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc); + } + }, this, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + + private async Task SendFrameFallbackAsync(MessageOpcode opcode, bool endOfMessage, ArraySegment payloadBuffer, CancellationToken cancellationToken) + { + await _sendFrameAsyncLock.WaitAsync().ConfigureAwait(false); + try + { + int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, payloadBuffer); + using (cancellationToken.Register(s => ((ManagedWebSocket)s).Abort(), this)) + { + await _stream.WriteAsync(_sendBuffer, 0, sendBytes, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception exc) + { + throw _state == WebSocketState.Aborted ? + CreateOperationCanceledException(exc, cancellationToken) : + new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc); + } + finally + { + _sendFrameAsyncLock.Release(); + } + } + + /// Writes a frame into the send buffer, which can then be sent over the network. + private int WriteFrameToSendBuffer(MessageOpcode opcode, bool endOfMessage, ArraySegment payloadBuffer) + { + // Grow our send buffer as needed. We reuse the buffer for all messages, with it protected by the send frame lock. + EnsureBufferLength(ref _sendBuffer, payloadBuffer.Count + MaxMessageHeaderLength); + + // Write the message header data to the buffer. + int headerLength; + int? maskOffset = null; + if (_isServer) + { + // The server doesn't send a mask, so the mask offset returned by WriteHeader + // is actually the end of the header. + headerLength = WriteHeader(opcode, _sendBuffer, payloadBuffer, endOfMessage, useMask: false); + } + else + { + // We need to know where the mask starts so that we can use the mask to manipulate the payload data, + // and we need to know the total length for sending it on the wire. + maskOffset = WriteHeader(opcode, _sendBuffer, payloadBuffer, endOfMessage, useMask: true); + headerLength = maskOffset.GetValueOrDefault() + MaskLength; + } + + // Write the payload + if (payloadBuffer.Count > 0) + { + Buffer.BlockCopy(payloadBuffer.Array, payloadBuffer.Offset, _sendBuffer, headerLength, payloadBuffer.Count); + + // If we added a mask to the header, XOR the payload with the mask. We do the manipulation in the send buffer so as to avoid + // changing the data in the caller-supplied payload buffer. + if (maskOffset.HasValue) + { + ApplyMask(_sendBuffer, headerLength, _sendBuffer, maskOffset.Value, 0, payloadBuffer.Count); + } + } + + // Return the number of bytes in the send buffer + return headerLength + payloadBuffer.Count; + } + + private void SendKeepAliveFrameAsync() + { + bool acquiredLock = _sendFrameAsyncLock.Wait(0); + if (acquiredLock) + { + // This exists purely to keep the connection alive; don't wait for the result, and ignore any failures. + // The call will handle releasing the lock. + SendFrameLockAcquiredNonCancelableAsync(MessageOpcode.Ping, true, new ArraySegment(CompatHelpers.Empty())); + } + else + { + // If the lock is already held, something is already getting sent, + // so there's no need to send a keep-alive ping. + } + } + + private static int WriteHeader(MessageOpcode opcode, byte[] sendBuffer, ArraySegment payload, bool endOfMessage, bool useMask) + { + // Client header format: + // 1 bit - FIN - 1 if this is the final fragment in the message (it could be the only fragment), otherwise 0 + // 1 bit - RSV1 - Reserved - 0 + // 1 bit - RSV2 - Reserved - 0 + // 1 bit - RSV3 - Reserved - 0 + // 4 bits - Opcode - How to interpret the payload + // - 0x0 - continuation + // - 0x1 - text + // - 0x2 - binary + // - 0x8 - connection close + // - 0x9 - ping + // - 0xA - pong + // - (0x3 to 0x7, 0xB-0xF - reserved) + // 1 bit - Masked - 1 if the payload is masked, 0 if it's not. Must be 1 for the client + // 7 bits, 7+16 bits, or 7+64 bits - Payload length + // - For length 0 through 125, 7 bits storing the length + // - For lengths 126 through 2^16, 7 bits storing the value 126, followed by 16 bits storing the length + // - For lengths 2^16+1 through 2^64, 7 bits storing the value 127, followed by 64 bytes storing the length + // 0 or 4 bytes - Mask, if Masked is 1 - random value XOR'd with each 4 bytes of the payload, round-robin + // Length bytes - Payload data + + Debug.Assert(sendBuffer.Length >= MaxMessageHeaderLength, $"Expected sendBuffer to be at least {MaxMessageHeaderLength}, got {sendBuffer.Length}"); + + sendBuffer[0] = (byte)opcode; // 4 bits for the opcode + if (endOfMessage) + { + sendBuffer[0] |= 0x80; // 1 bit for FIN + } + + // Store the payload length. + int maskOffset; + if (payload.Count <= 125) + { + sendBuffer[1] = (byte)payload.Count; + maskOffset = 2; // no additional payload length + } + else if (payload.Count <= ushort.MaxValue) + { + sendBuffer[1] = 126; + sendBuffer[2] = (byte)(payload.Count / 256); + sendBuffer[3] = (byte)payload.Count; + maskOffset = 2 + sizeof(ushort); // additional 2 bytes for 16-bit length + } + else + { + sendBuffer[1] = 127; + int length = payload.Count; + for (int i = 9; i >= 2; i--) + { + sendBuffer[i] = (byte)length; + length = length / 256; + } + maskOffset = 2 + sizeof(ulong); // additional 8 bytes for 64-bit length + } + + if (useMask) + { + // Generate the mask. + sendBuffer[1] |= 0x80; + WriteRandomMask(sendBuffer, maskOffset); + } + + // Return the position of the mask. + return maskOffset; + } + + /// Writes a 4-byte random mask to the specified buffer at the specified offset. + /// The buffer to which to write the mask. + /// The offset into the buffer at which to write the mask. + private static void WriteRandomMask(byte[] buffer, int offset) + { + byte[] mask = t_headerMask ?? (t_headerMask = new byte[MaskLength]); + Debug.Assert(mask.Length == MaskLength, $"Expected mask of length {MaskLength}, got {mask.Length}"); + s_random.GetBytes(mask); + Buffer.BlockCopy(mask, 0, buffer, offset, MaskLength); + } + + /// + /// Receive the next text, binary, continuation, or close message, returning information about it and + /// writing its payload into the supplied buffer. Other control messages may be consumed and processed + /// as part of this operation, but data about them will not be returned. + /// + /// The buffer into which payload data should be written. + /// The CancellationToken used to cancel the websocket. + /// Information about the received message. + private async Task ReceiveAsyncPrivate(ArraySegment payloadBuffer, CancellationToken cancellationToken) + { + // This is a long method. While splitting it up into pieces would arguably help with readability, doing so would + // also result in more allocations, as each async method that yields ends up with multiple allocations. The impact + // of those allocations is amortized across all of the awaits in the method, and since we generally expect a receive + // operation to require at most a single yield (while waiting for data to arrive), it's more efficient to have + // everything in the one method. We do separate out pieces for handling close and ping/pong messages, as we expect + // those to be much less frequent (e.g. we should only get one close per websocket), and thus we can afford to pay + // a bit more for readability and maintainability. + + CancellationTokenRegistration registration = cancellationToken.Register(s => ((ManagedWebSocket)s).Abort(), this); + try + { + while (true) // in case we get control frames that should be ignored from the user's perspective + { + // Get the last received header. If its payload length is non-zero, that means we previously + // received the header but were only able to read a part of the fragment, so we should skip + // reading another header and just proceed to use that same header and read more data associated + // with it. If instead its payload length is zero, then we've completed the processing of + // thta message, and we should read the next header. + MessageHeader header = _lastReceiveHeader; + if (header.PayloadLength == 0) + { + if (_receiveBufferCount < (_isServer ? (MaxMessageHeaderLength - MaskLength) : MaxMessageHeaderLength)) + { + // Make sure we have the first two bytes, which includes the start of the payload length. + if (_receiveBufferCount < 2) + { + await EnsureBufferContainsAsync(2, cancellationToken, throwOnPrematureClosure: false).ConfigureAwait(false); + if (_receiveBufferCount < 2) + { + // The connection closed; nothing more to read. + return new WebSocketReceiveResult(0, WebSocketMessageType.Text, true); + } + } + + // Then make sure we have the full header based on the payload length. + // If this is the server, we also need room for the received mask. + long payloadLength = _receiveBuffer[_receiveBufferOffset + 1] & 0x7F; + if (_isServer || payloadLength > 125) + { + int minNeeded = + 2 + + (_isServer ? MaskLength : 0) + + (payloadLength <= 125 ? 0 : payloadLength == 126 ? sizeof(ushort) : sizeof(ulong)); // additional 2 or 8 bytes for 16-bit or 64-bit length + await EnsureBufferContainsAsync(minNeeded, cancellationToken).ConfigureAwait(false); + } + } + + if (!TryParseMessageHeaderFromReceiveBuffer(out header)) + { + await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted, cancellationToken).ConfigureAwait(false); + } + _receivedMaskOffsetOffset = 0; + } + + // If the header represents a ping or a pong, it's a control message meant + // to be transparent to the user, so handle it and then loop around to read again. + // Alternatively, if it's a close message, handle it and exit. + if (header.Opcode == MessageOpcode.Ping || header.Opcode == MessageOpcode.Pong) + { + await HandleReceivedPingPongAsync(header, cancellationToken).ConfigureAwait(false); + continue; + } + else if (header.Opcode == MessageOpcode.Close) + { + return await HandleReceivedCloseAsync(header, cancellationToken).ConfigureAwait(false); + } + + // If this is a continuation, replace the opcode with the one of the message it's continuing + if (header.Opcode == MessageOpcode.Continuation) + { + header.Opcode = _lastReceiveHeader.Opcode; + } + + // The message should now be a binary or text message. Handle it by reading the payload and returning the contents. + Debug.Assert(header.Opcode == MessageOpcode.Binary || header.Opcode == MessageOpcode.Text, $"Unexpected opcode {header.Opcode}"); + + // If there's no data to read, return an appropriate result. + int bytesToRead = (int)Math.Min(payloadBuffer.Count, header.PayloadLength); + if (bytesToRead == 0) + { + _lastReceiveHeader = header; + return new WebSocketReceiveResult( + 0, + header.Opcode == MessageOpcode.Text ? WebSocketMessageType.Text : WebSocketMessageType.Binary, + header.PayloadLength == 0 ? header.Fin : false); + } + + // Otherwise, read as much of the payload as we can efficiently, and upate the header to reflect how much data + // remains for future reads. + + if (_receiveBufferCount == 0) + { + await EnsureBufferContainsAsync(1, cancellationToken, throwOnPrematureClosure: false).ConfigureAwait(false); + } + + int bytesToCopy = Math.Min(bytesToRead, _receiveBufferCount); + if (_isServer) + { + _receivedMaskOffsetOffset = ApplyMask(_receiveBuffer, _receiveBufferOffset, header.Mask, _receivedMaskOffsetOffset, bytesToCopy); + } + Buffer.BlockCopy(_receiveBuffer, _receiveBufferOffset, payloadBuffer.Array, payloadBuffer.Offset, bytesToCopy); + ConsumeFromBuffer(bytesToCopy); + header.PayloadLength -= bytesToCopy; + + // If this a text message, validate that it contains valid UTF8. + if (header.Opcode == MessageOpcode.Text && + !TryValidateUtf8(new ArraySegment(payloadBuffer.Array, payloadBuffer.Offset, bytesToCopy), header.Fin, _utf8TextState)) + { + await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.InvalidPayloadData, WebSocketError.Faulted, cancellationToken).ConfigureAwait(false); + } + + _lastReceiveHeader = header; + return new WebSocketReceiveResult( + bytesToCopy, + header.Opcode == MessageOpcode.Text ? WebSocketMessageType.Text : WebSocketMessageType.Binary, + bytesToCopy == 0 || (header.Fin && header.PayloadLength == 0)); + } + } + catch (Exception exc) + { + throw _state == WebSocketState.Aborted ? + new WebSocketException(WebSocketError.InvalidState, SR.Format(SR.net_WebSockets_InvalidState_ClosedOrAborted, "System.Net.WebSockets.InternalClientWebSocket", "Aborted"), exc) : + new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc); + } + finally + { + registration.Dispose(); + } + } + + /// Processes a received close message. + /// The message header. + /// The cancellation token to use to cancel the websocket. + /// The received result message. + private async Task HandleReceivedCloseAsync( + MessageHeader header, CancellationToken cancellationToken) + { + lock (StateUpdateLock) + { + _receivedCloseFrame = true; + if (_state < WebSocketState.CloseReceived) + { + _state = WebSocketState.CloseReceived; + } + } + + WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure; + string closeStatusDescription = string.Empty; + + // Handle any payload by parsing it into the close status and description. + if (header.PayloadLength == 1) + { + // The close payload length can be 0 or >= 2, but not 1. + await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted, cancellationToken).ConfigureAwait(false); + } + else if (header.PayloadLength >= 2) + { + if (_receiveBufferCount < header.PayloadLength) + { + await EnsureBufferContainsAsync((int)header.PayloadLength, cancellationToken).ConfigureAwait(false); + } + + if (_isServer) + { + ApplyMask(_receiveBuffer, _receiveBufferOffset, header.Mask, 0, header.PayloadLength); + } + + closeStatus = (WebSocketCloseStatus)(_receiveBuffer[_receiveBufferOffset] << 8 | _receiveBuffer[_receiveBufferOffset + 1]); + if (!IsValidCloseStatus(closeStatus)) + { + await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted, cancellationToken).ConfigureAwait(false); + } + + if (header.PayloadLength > 2) + { + try + { + closeStatusDescription = s_textEncoding.GetString(_receiveBuffer, _receiveBufferOffset + 2, (int)header.PayloadLength - 2); + } + catch (DecoderFallbackException exc) + { + await CloseWithReceiveErrorAndThrowAsync(WebSocketCloseStatus.ProtocolError, WebSocketError.Faulted, cancellationToken, exc).ConfigureAwait(false); + } + } + ConsumeFromBuffer((int)header.PayloadLength); + } + + // Store the close status and description onto the instance. + _closeStatus = closeStatus; + _closeStatusDescription = closeStatusDescription; + + // And return them as part of the result message. + return new WebSocketReceiveResult(0, WebSocketMessageType.Close, true, closeStatus, closeStatusDescription); + } + + /// Processes a received ping or pong message. + /// The message header. + /// The cancellation token to use to cancel the websocket. + private async Task HandleReceivedPingPongAsync(MessageHeader header, CancellationToken cancellationToken) + { + // Consume any (optional) payload associated with the ping/pong. + if (header.PayloadLength > 0 && _receiveBufferCount < header.PayloadLength) + { + await EnsureBufferContainsAsync((int)header.PayloadLength, cancellationToken).ConfigureAwait(false); + } + + // If this was a ping, send back a pong response. + if (header.Opcode == MessageOpcode.Ping) + { + if (_isServer) + { + ApplyMask(_receiveBuffer, _receiveBufferOffset, header.Mask, 0, header.PayloadLength); + } + + await SendFrameAsync( + MessageOpcode.Pong, true, + new ArraySegment(_receiveBuffer, _receiveBufferOffset, (int)header.PayloadLength), cancellationToken).ConfigureAwait(false); + } + + // Regardless of whether it was a ping or pong, we no longer need the payload. + if (header.PayloadLength > 0) + { + ConsumeFromBuffer((int)header.PayloadLength); + } + } + + /// Check whether a close status is valid according to the RFC. + /// The status to validate. + /// true if the status if valid; otherwise, false. + private static bool IsValidCloseStatus(WebSocketCloseStatus closeStatus) + { + // 0-999: "not used" + // 1000-2999: reserved for the protocol; we need to check individual codes manually + // 3000-3999: reserved for use by higher-level code + // 4000-4999: reserved for private use + // 5000-: not mentioned in RFC + + if (closeStatus < (WebSocketCloseStatus)1000 || closeStatus >= (WebSocketCloseStatus)5000) + { + return false; + } + + if (closeStatus >= (WebSocketCloseStatus)3000) + { + return true; + } + + switch (closeStatus) // check for the 1000-2999 range known codes + { + case WebSocketCloseStatus.EndpointUnavailable: + case WebSocketCloseStatus.InternalServerError: + case WebSocketCloseStatus.InvalidMessageType: + case WebSocketCloseStatus.InvalidPayloadData: + case WebSocketCloseStatus.MandatoryExtension: + case WebSocketCloseStatus.MessageTooBig: + case WebSocketCloseStatus.NormalClosure: + case WebSocketCloseStatus.PolicyViolation: + case WebSocketCloseStatus.ProtocolError: + return true; + + default: + return false; + } + } + + /// Send a close message to the server and throw an exception, in response to getting bad data from the server. + /// The close status code to use. + /// The error reason. + /// The CancellationToken used to cancel the websocket. + /// An optional inner exception to include in the thrown exception. + private async Task CloseWithReceiveErrorAndThrowAsync( + WebSocketCloseStatus closeStatus, WebSocketError error, CancellationToken cancellationToken, Exception innerException = null) + { + // Close the connection if it hasn't already been closed + if (!_sentCloseFrame) + { + await CloseOutputAsync(closeStatus, string.Empty, cancellationToken).ConfigureAwait(false); + } + + // Dump our receive buffer; we're in a bad state to do any further processing + _receiveBufferCount = 0; + + // Let the caller know we've failed + throw new WebSocketException(error, innerException); + } + + /// Parses a message header from the buffer. This assumes the header is in the buffer. + /// The read header. + /// true if a header was read; false if the header was invalid. + private bool TryParseMessageHeaderFromReceiveBuffer(out MessageHeader resultHeader) + { + Debug.Assert(_receiveBufferCount >= 2, $"Expected to at least have the first two bytes of the header."); + + var header = new MessageHeader(); + + header.Fin = (_receiveBuffer[_receiveBufferOffset] & 0x80) != 0; + bool reservedSet = (_receiveBuffer[_receiveBufferOffset] & 0x70) != 0; + header.Opcode = (MessageOpcode)(_receiveBuffer[_receiveBufferOffset] & 0xF); + + bool masked = (_receiveBuffer[_receiveBufferOffset + 1] & 0x80) != 0; + header.PayloadLength = _receiveBuffer[_receiveBufferOffset + 1] & 0x7F; + + ConsumeFromBuffer(2); + + // Read the remainder of the payload length, if necessary + if (header.PayloadLength == 126) + { + Debug.Assert(_receiveBufferCount >= 2, $"Expected to have two bytes for the payload length."); + header.PayloadLength = (_receiveBuffer[_receiveBufferOffset] << 8) | _receiveBuffer[_receiveBufferOffset + 1]; + ConsumeFromBuffer(2); + } + else if (header.PayloadLength == 127) + { + Debug.Assert(_receiveBufferCount >= 8, $"Expected to have eight bytes for the payload length."); + header.PayloadLength = 0; + for (int i = 0; i < 8; i++) + { + header.PayloadLength = (header.PayloadLength << 8) | _receiveBuffer[_receiveBufferOffset + i]; + } + ConsumeFromBuffer(8); + } + + bool shouldFail = reservedSet; + if (masked) + { + if (!_isServer) + { + shouldFail = true; + } + header.Mask = CombineMaskBytes(_receiveBuffer, _receiveBufferOffset); + + // Consume the mask bytes + ConsumeFromBuffer(4); + } + + // Do basic validation of the header + switch (header.Opcode) + { + case MessageOpcode.Continuation: + if (_lastReceiveHeader.Fin) + { + // Can't continue from a final message + shouldFail = true; + } + break; + + case MessageOpcode.Binary: + case MessageOpcode.Text: + if (!_lastReceiveHeader.Fin) + { + // Must continue from a non-final message + shouldFail = true; + } + break; + + case MessageOpcode.Close: + case MessageOpcode.Ping: + case MessageOpcode.Pong: + if (header.PayloadLength > MaxControlPayloadLength || !header.Fin) + { + // Invalid control messgae + shouldFail = true; + } + break; + + default: + // Unknown opcode + shouldFail = true; + break; + } + + // Return the read header + resultHeader = header; + return !shouldFail; + } + + /// Send a close message, then receive until we get a close response message. + /// The close status to send. + /// The close status description to send. + /// The CancellationToken to use to cancel the websocket. + private async Task CloseAsyncPrivate(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) + { + // Send the close message. Skip sending a close frame if we're currently in a CloseSent state, + // for example having just done a CloseOutputAsync. + if (!_sentCloseFrame) + { + await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false); + } + + // We should now either be in a CloseSent case (because we just sent one), or in a CloseReceived state, in case + // there was a concurrent receive that ended up handling an immediate close frame response from the server. + // Of course it could also be Aborted if something happened concurrently to cause things to blow up. + Debug.Assert( + State == WebSocketState.CloseSent || + State == WebSocketState.CloseReceived || + State == WebSocketState.Aborted, + $"Unexpected state {State}."); + + // Wait until we've received a close response + byte[] closeBuffer = new byte[MaxMessageHeaderLength + MaxControlPayloadLength]; + while (!_receivedCloseFrame) + { + Debug.Assert(!Monitor.IsEntered(StateUpdateLock), $"{nameof(StateUpdateLock)} must never be held when acquiring {nameof(ReceiveAsyncLock)}"); + Task receiveTask; + lock (ReceiveAsyncLock) + { + // Now that we're holding the ReceiveAsyncLock, double-check that we've not yet received the close frame. + // It could have been received between our check above and now due to a concurrent receive completing. + if (_receivedCloseFrame) + { + break; + } + + // We've not yet processed a received close frame, which means we need to wait for a received close to complete. + // There may already be one in flight, in which case we want to just wait for that one rather than kicking off + // another (we don't support concurrent receive operations). We need to kick off a new receive if either we've + // never issued a receive or if the last issued receive completed for reasons other than a close frame. There is + // a race condition here, e.g. if there's a in-flight receive that completes after we check, but that's fine: worst + // case is we then await it, find that it's not what we need, and try again. + receiveTask = _lastReceiveAsync; + if (receiveTask == null || + (receiveTask.Status == TaskStatus.RanToCompletion && receiveTask.Result.MessageType != WebSocketMessageType.Close)) + { + _lastReceiveAsync = receiveTask = ReceiveAsyncPrivate(new ArraySegment(closeBuffer), cancellationToken); + } + } + + // Wait for whatever receive task we have. We'll then loop around again to re-check our state. + Debug.Assert(receiveTask != null); + await receiveTask.ConfigureAwait(false); + } + + // We're closed. Close the connection and update the status. + lock (StateUpdateLock) + { + DisposeCore(); + if (_state < WebSocketState.Closed) + { + _state = WebSocketState.Closed; + } + } + } + + /// Sends a close message to the server. + /// The close status to send. + /// The close status description to send. + /// The CancellationToken to use to cancel the websocket. + private async Task SendCloseFrameAsync(WebSocketCloseStatus closeStatus, string closeStatusDescription, CancellationToken cancellationToken) + { + // Close payload is two bytes containing the close status followed by a UTF8-encoding of the status description, if it exists. + + byte[] buffer; + if (string.IsNullOrEmpty(closeStatusDescription)) + { + buffer = new byte[2]; + } + else + { + buffer = new byte[2 + s_textEncoding.GetByteCount(closeStatusDescription)]; + int encodedLength = s_textEncoding.GetBytes(closeStatusDescription, 0, closeStatusDescription.Length, buffer, 2); + Debug.Assert(buffer.Length - 2 == encodedLength, $"GetByteCount and GetBytes encoded count didn't match"); + } + + ushort closeStatusValue = (ushort)closeStatus; + buffer[0] = (byte)(closeStatusValue >> 8); + buffer[1] = (byte)(closeStatusValue & 0xFF); + + await SendFrameAsync(MessageOpcode.Close, true, new ArraySegment(buffer), cancellationToken).ConfigureAwait(false); + + lock (StateUpdateLock) + { + _sentCloseFrame = true; + if (_state <= WebSocketState.CloseReceived) + { + _state = WebSocketState.CloseSent; + } + } + } + + private void ConsumeFromBuffer(int count) + { + Debug.Assert(count >= 0, $"Expected non-negative count, got {count}"); + Debug.Assert(count <= _receiveBufferCount, $"Trying to consume {count}, which is more than exists {_receiveBufferCount}"); + _receiveBufferCount -= count; + _receiveBufferOffset += count; + } + + private async Task EnsureBufferContainsAsync(int minimumRequiredBytes, CancellationToken cancellationToken, bool throwOnPrematureClosure = true) + { + Debug.Assert(minimumRequiredBytes <= _receiveBuffer.Length, $"Requested number of bytes {minimumRequiredBytes} must not exceed {_receiveBuffer.Length}"); + + // If we don't have enough data in the buffer to satisfy the minimum required, read some more. + if (_receiveBufferCount < minimumRequiredBytes) + { + // If there's any data in the buffer, shift it down. + if (_receiveBufferCount > 0) + { + Buffer.BlockCopy(_receiveBuffer, _receiveBufferOffset, _receiveBuffer, 0, _receiveBufferCount); + } + _receiveBufferOffset = 0; + + // While we don't have enough data, read more. + while (_receiveBufferCount < minimumRequiredBytes) + { + int numRead = await _stream.ReadAsync(_receiveBuffer, _receiveBufferCount, _receiveBuffer.Length - _receiveBufferCount, cancellationToken).ConfigureAwait(false); + Debug.Assert(numRead >= 0, $"Expected non-negative bytes read, got {numRead}"); + _receiveBufferCount += numRead; + if (numRead == 0) + { + // The connection closed before we were able to read everything we needed. + // If it was due to use being disposed, fail. If it was due to the connection + // being closed and it wasn't expected, fail. If it was due to the connection + // being closed and that was expected, exit gracefully. + if (_disposed) + { + throw new ObjectDisposedException("ClientWebSocket"); + } + else if (throwOnPrematureClosure) + { + throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely); + } + break; + } + } + } + } + + /// + /// Grows the specified buffer if it's not at least the specified minimum length. + /// Data is not copied if the buffer is grown. + /// + private static void EnsureBufferLength(ref byte[] buffer, int minLength) + { + if (buffer == null || buffer.Length < minLength) + { + buffer = new byte[minLength]; + } + } + + private static unsafe int CombineMaskBytes(byte[] buffer, int maskOffset) => + BitConverter.ToInt32(buffer, maskOffset); + + /// Applies a mask to a portion of a byte array. + /// The buffer to which the mask should be applied. + /// The offset into at which the mask should start to be applied. + /// The array containing the mask to apply. + /// The offset into of the mask to apply of length . + /// The next position offset from of which by to apply next from the mask. + /// The number of bytes starting from to which the mask should be applied. + /// The updated maskOffsetOffset value. + private static int ApplyMask(byte[] toMask, int toMaskOffset, byte[] mask, int maskOffset, int maskOffsetIndex, long count) + { + Debug.Assert(maskOffsetIndex < MaskLength, $"Unexpected {nameof(maskOffsetIndex)}: {maskOffsetIndex}"); + Debug.Assert(mask.Length >= MaskLength + maskOffset, $"Unexpected inputs: {mask.Length}, {maskOffset}"); + return ApplyMask(toMask, toMaskOffset, CombineMaskBytes(mask, maskOffset), maskOffsetIndex, count); + } + + /// Applies a mask to a portion of a byte array. + /// The buffer to which the mask should be applied. + /// The offset into at which the mask should start to be applied. + /// The four-byte mask, stored as an Int32. + /// The index into the mas + /// The number of bytes to mask. + /// + private static unsafe int ApplyMask(byte[] toMask, int toMaskOffset, int mask, int maskIndex, long count) + { + Debug.Assert(toMaskOffset <= toMask.Length - count, $"Unexpected inputs: {toMaskOffset}, {toMask.Length}, {count}"); + Debug.Assert(maskIndex < sizeof(int), $"Unexpected {nameof(maskIndex)}: {maskIndex}"); + + byte* maskPtr = (byte*)&mask; + fixed (byte* toMaskPtr = toMask) + { + byte* p = toMaskPtr + toMaskOffset; + byte* end = p + count; + while (p < end) + { + *p++ ^= maskPtr[maskIndex]; + maskIndex = (maskIndex + 1) & 3; // & 3 == faster % MaskLength + } + return maskIndex; + } + } + + /// Aborts the websocket and throws an exception if an existing operation is in progress. + private void ThrowIfOperationInProgress(Task operationTask, [CallerMemberName] string methodName = null) + { + if (operationTask != null && !operationTask.IsCompleted) + { + Abort(); + throw new InvalidOperationException(SR.Format(SR.net_Websockets_AlreadyOneOutstandingOperation, methodName)); + } + } + + /// Creates an OperationCanceledException instance, using a default message and the specified inner exception and token. + private static Exception CreateOperationCanceledException(Exception innerException, CancellationToken cancellationToken = default(CancellationToken)) + { + return new OperationCanceledException( + new OperationCanceledException().Message, + innerException, + cancellationToken); + } + + // From https://raw.githubusercontent.com/aspnet/WebSockets/dev/src/Microsoft.AspNetCore.WebSockets.Protocol/Utilities.cs + // Performs a stateful validation of UTF-8 bytes. + // It checks for valid formatting, overlong encodings, surrogates, and value ranges. + private static bool TryValidateUtf8(ArraySegment 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; + } + + private sealed class Utf8MessageState + { + internal bool SequenceInProgress; + internal int AdditionalBytesExpected; + internal int ExpectedValueMin; + internal int CurrentDecodeBits; + } + + private enum MessageOpcode : byte + { + Continuation = 0x0, + Text = 0x1, + Binary = 0x2, + Close = 0x8, + Ping = 0x9, + Pong = 0xA + } + + [StructLayout(LayoutKind.Auto)] + private struct MessageHeader + { + internal MessageOpcode Opcode; + internal bool Fin; + internal long PayloadLength; + internal int Mask; + } + } +} diff --git a/src/Microsoft.Net.Http.Server/project.json b/src/Microsoft.Net.Http.Server/project.json index ac4ff9de65..e82ad375c9 100644 --- a/src/Microsoft.Net.Http.Server/project.json +++ b/src/Microsoft.Net.Http.Server/project.json @@ -10,7 +10,9 @@ "warningsAsErrors": true, "keyFile": "../../tools/Key.snk", "nowarn": [ - "CS1591" + "CS1591", + "CS1572", + "CS1573" ], "xmlDoc": true }, @@ -31,6 +33,7 @@ "System.IO": "4.1.0-*", "System.IO.FileSystem": "4.0.1-*", "System.Net.Primitives": "4.0.11-*", + "System.Net.WebSockets": "4.0.0-*", "System.Runtime.Extensions": "4.1.0-*", "System.Runtime.InteropServices": "4.1.0-*", "System.Security.Claims": "4.0.1-*", @@ -38,7 +41,8 @@ "System.Security.Principal.Windows": "4.0.0-*", "System.Text.Encoding.Extensions": "4.0.11-*", "System.Threading": "4.0.11-*", - "System.Threading.Overlapped": "4.0.1-*" + "System.Threading.Overlapped": "4.0.1-*", + "System.Threading.Timer": "4.0.1" } } } diff --git a/src/Microsoft.Net.WebSockets.Server/HttpKnownHeaderNames.cs b/src/Microsoft.Net.WebSockets.Server/HttpKnownHeaderNames.cs deleted file mode 100644 index fb6fa1d755..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/HttpKnownHeaderNames.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.Net.WebSockets -{ - // this class contains known header names - internal static class HttpKnownHeaderNames - { - public const string CacheControl = "Cache-Control"; - public const string Connection = "Connection"; - public const string Date = "Date"; - public const string KeepAlive = "Keep-Alive"; - public const string Pragma = "Pragma"; - public const string ProxyConnection = "Proxy-Connection"; - public const string Trailer = "Trailer"; - public const string TransferEncoding = "Transfer-Encoding"; - public const string Upgrade = "Upgrade"; - public const string Via = "Via"; - public const string Warning = "Warning"; - public const string ContentLength = "Content-Length"; - public const string ContentType = "Content-Type"; - public const string ContentDisposition = "Content-Disposition"; - public const string ContentEncoding = "Content-Encoding"; - public const string ContentLanguage = "Content-Language"; - public const string ContentLocation = "Content-Location"; - public const string ContentRange = "Content-Range"; - public const string Expires = "Expires"; - public const string LastModified = "Last-Modified"; - public const string Age = "Age"; - public const string Location = "Location"; - public const string ProxyAuthenticate = "Proxy-Authenticate"; - public const string RetryAfter = "Retry-After"; - public const string Server = "Server"; - public const string SetCookie = "Set-Cookie"; - public const string SetCookie2 = "Set-Cookie2"; - public const string Vary = "Vary"; - public const string WWWAuthenticate = "WWW-Authenticate"; - public const string Accept = "Accept"; - public const string AcceptCharset = "Accept-Charset"; - public const string AcceptEncoding = "Accept-Encoding"; - public const string AcceptLanguage = "Accept-Language"; - public const string Authorization = "Authorization"; - public const string Cookie = "Cookie"; - public const string Cookie2 = "Cookie2"; - public const string Expect = "Expect"; - public const string From = "From"; - public const string Host = "Host"; - public const string IfMatch = "If-Match"; - public const string IfModifiedSince = "If-Modified-Since"; - public const string IfNoneMatch = "If-None-Match"; - public const string IfRange = "If-Range"; - public const string IfUnmodifiedSince = "If-Unmodified-Since"; - public const string MaxForwards = "Max-Forwards"; - public const string ProxyAuthorization = "Proxy-Authorization"; - public const string Referer = "Referer"; - public const string Range = "Range"; - public const string UserAgent = "User-Agent"; - public const string ContentMD5 = "Content-MD5"; - public const string ETag = "ETag"; - public const string TE = "TE"; - public const string Allow = "Allow"; - public const string AcceptRanges = "Accept-Ranges"; - public const string P3P = "P3P"; - public const string XPoweredBy = "X-Powered-By"; - public const string XAspNetVersion = "X-AspNet-Version"; - public const string SecWebSocketKey = "Sec-WebSocket-Key"; - public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; - public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; - public const string Origin = "Origin"; - public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; - public const string SecWebSocketVersion = "Sec-WebSocket-Version"; - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/Legacy/SR.cs b/src/Microsoft.Net.WebSockets.Server/Legacy/SR.cs deleted file mode 100644 index 6062f60855..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/Legacy/SR.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All Rights Reserved. -// Information Contained Herein is Proprietary and Confidential. -// -//------------------------------------------------------------------------------ - -namespace System -{ - internal sealed class SR - { - internal const string net_servicePointAddressNotSupportedInHostMode = "net_servicePointAddressNotSupportedInHostMode"; - internal const string net_Websockets_AlreadyOneOutstandingOperation = "net_Websockets_AlreadyOneOutstandingOperation"; - internal const string net_Websockets_WebSocketBaseFaulted = "net_Websockets_WebSocketBaseFaulted"; - internal const string net_WebSockets_NativeSendResponseHeaders = "net_WebSockets_NativeSendResponseHeaders"; - internal const string net_WebSockets_Generic = "net_WebSockets_Generic"; - internal const string net_WebSockets_NotAWebSocket_Generic = "net_WebSockets_NotAWebSocket_Generic"; - internal const string net_WebSockets_UnsupportedWebSocketVersion_Generic = "net_WebSockets_UnsupportedWebSocketVersion_Generic"; - internal const string net_WebSockets_HeaderError_Generic = "net_WebSockets_HeaderError_Generic"; - internal const string net_WebSockets_UnsupportedProtocol_Generic = "net_WebSockets_UnsupportedProtocol_Generic"; - internal const string net_WebSockets_UnsupportedPlatform = "net_WebSockets_UnsupportedPlatform"; - internal const string net_WebSockets_AcceptNotAWebSocket = "net_WebSockets_AcceptNotAWebSocket"; - internal const string net_WebSockets_AcceptUnsupportedWebSocketVersion = "net_WebSockets_AcceptUnsupportedWebSocketVersion"; - internal const string net_WebSockets_AcceptHeaderNotFound = "net_WebSockets_AcceptHeaderNotFound"; - internal const string net_WebSockets_AcceptUnsupportedProtocol = "net_WebSockets_AcceptUnsupportedProtocol"; - internal const string net_WebSockets_ClientAcceptingNoProtocols = "net_WebSockets_ClientAcceptingNoProtocols"; - internal const string net_WebSockets_ClientSecWebSocketProtocolsBlank = "net_WebSockets_ClientSecWebSocketProtocolsBlank"; - internal const string net_WebSockets_ArgumentOutOfRange_TooSmall = "net_WebSockets_ArgumentOutOfRange_TooSmall"; - internal const string net_WebSockets_ArgumentOutOfRange_InternalBuffer = "net_WebSockets_ArgumentOutOfRange_InternalBuffer"; - internal const string net_WebSockets_ArgumentOutOfRange_TooBig = "net_WebSockets_ArgumentOutOfRange_TooBig"; - internal const string net_WebSockets_InvalidState_Generic = "net_WebSockets_InvalidState_Generic"; - internal const string net_WebSockets_InvalidState_ClosedOrAborted = "net_WebSockets_InvalidState_ClosedOrAborted"; - internal const string net_WebSockets_InvalidState = "net_WebSockets_InvalidState"; - internal const string net_WebSockets_ReceiveAsyncDisallowedAfterCloseAsync = "net_WebSockets_ReceiveAsyncDisallowedAfterCloseAsync"; - internal const string net_WebSockets_InvalidMessageType = "net_WebSockets_InvalidMessageType"; - internal const string net_WebSockets_InvalidBufferType = "net_WebSockets_InvalidBufferType"; - internal const string net_WebSockets_InvalidMessageType_Generic = "net_WebSockets_InvalidMessageType_Generic"; - internal const string net_WebSockets_Argument_InvalidMessageType = "net_WebSockets_Argument_InvalidMessageType"; - internal const string net_WebSockets_ConnectionClosedPrematurely_Generic = "net_WebSockets_ConnectionClosedPrematurely_Generic"; - internal const string net_WebSockets_InvalidCharInProtocolString = "net_WebSockets_InvalidCharInProtocolString"; - internal const string net_WebSockets_InvalidEmptySubProtocol = "net_WebSockets_InvalidEmptySubProtocol"; - internal const string net_WebSockets_ReasonNotNull = "net_WebSockets_ReasonNotNull"; - internal const string net_WebSockets_InvalidCloseStatusCode = "net_WebSockets_InvalidCloseStatusCode"; - internal const string net_WebSockets_InvalidCloseStatusDescription = "net_WebSockets_InvalidCloseStatusDescription"; - internal const string net_WebSockets_Scheme = "net_WebSockets_Scheme"; - internal const string net_WebSockets_AlreadyStarted = "net_WebSockets_AlreadyStarted"; - internal const string net_WebSockets_Connect101Expected = "net_WebSockets_Connect101Expected"; - internal const string net_WebSockets_InvalidResponseHeader = "net_WebSockets_InvalidResponseHeader"; - internal const string net_WebSockets_NotConnected = "net_WebSockets_NotConnected"; - internal const string net_WebSockets_InvalidRegistration = "net_WebSockets_InvalidRegistration"; - internal const string net_WebSockets_NoDuplicateProtocol = "net_WebSockets_NoDuplicateProtocol"; - - internal const string NotReadableStream = "NotReadableStream"; - internal const string NotWriteableStream = "NotWriteableStream"; - - public static string GetString(string name, params object[] args) - { - return name; - } - - public static string GetString(string name) - { - return name; - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/Legacy/WebSocketHttpListenerDuplexStream.cs b/src/Microsoft.Net.WebSockets.Server/Legacy/WebSocketHttpListenerDuplexStream.cs deleted file mode 100644 index be72c5f88e..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/Legacy/WebSocketHttpListenerDuplexStream.cs +++ /dev/null @@ -1,1279 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ -/* -namespace Microsoft.AspNetCore.WebSockets -{ - using Microsoft.Net; - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.ComponentModel; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Runtime.InteropServices; - using System.Security; - using System.Threading; - using System.Threading.Tasks; - - internal class WebSocketHttpListenerDuplexStream : Stream, WebSocketBase.IWebSocketStream - { - private static readonly EventHandler s_OnReadCompleted = - new EventHandler(OnReadCompleted); - private static readonly EventHandler s_OnWriteCompleted = - new EventHandler(OnWriteCompleted); - private static readonly Func s_CanHandleException = new Func(CanHandleException); - private static readonly Action s_OnCancel = new Action(OnCancel); - // private readonly HttpRequestStream m_InputStream; - // private readonly HttpResponseStream m_OutputStream; - private HttpListenerContext m_Context; - private bool m_InOpaqueMode; - private WebSocketBase m_WebSocket; - private HttpListenerAsyncEventArgs m_WriteEventArgs; - private HttpListenerAsyncEventArgs m_ReadEventArgs; - private TaskCompletionSource m_WriteTaskCompletionSource; - private TaskCompletionSource m_ReadTaskCompletionSource; - private int m_CleanedUp; - -#if DEBUG - private class OutstandingOperations - { - internal int m_Reads; - internal int m_Writes; - } - - private readonly OutstandingOperations m_OutstandingOperations = new OutstandingOperations(); -#endif //DEBUG - - public WebSocketHttpListenerDuplexStream( - // HttpRequestStream inputStream, - // HttpResponseStream outputStream, - HttpListenerContext context) - { - Contract.Assert(inputStream != null, "'inputStream' MUST NOT be NULL."); - Contract.Assert(outputStream != null, "'outputStream' MUST NOT be NULL."); - Contract.Assert(context != null, "'context' MUST NOT be NULL."); - Contract.Assert(inputStream.CanRead, "'inputStream' MUST support read operations."); - Contract.Assert(outputStream.CanWrite, "'outputStream' MUST support write operations."); - - m_InputStream = inputStream; - m_OutputStream = outputStream; - m_Context = context; - - if (WebSocketBase.LoggingEnabled) - { - Logging.Associate(Logging.WebSockets, inputStream, this); - Logging.Associate(Logging.WebSockets, outputStream, this); - } - } - - public override bool CanRead - { - get - { - return m_InputStream.CanRead; - } - } - - public override bool CanSeek - { - get - { - return false; - } - } - - public override bool CanTimeout - { - get - { - return m_InputStream.CanTimeout && m_OutputStream.CanTimeout; - } - } - - public override bool CanWrite - { - get - { - return m_OutputStream.CanWrite; - } - } - - public override long Length - { - get - { - throw new NotSupportedException(SR.GetString(SR.net_noseek)); - } - } - - public override long Position - { - get - { - throw new NotSupportedException(SR.GetString(SR.net_noseek)); - } - set - { - throw new NotSupportedException(SR.GetString(SR.net_noseek)); - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - return m_InputStream.Read(buffer, offset, count); - } - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - WebSocketHelpers.ValidateBuffer(buffer, offset, count); - - return ReadAsyncCore(buffer, offset, count, cancellationToken); - } - - private async Task ReadAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, this, Methods.ReadAsyncCore, - WebSocketHelpers.GetTraceMsgForParameters(offset, count, cancellationToken)); - } - - CancellationTokenRegistration cancellationTokenRegistration = new CancellationTokenRegistration(); - - int bytesRead = 0; - try - { - if (cancellationToken.CanBeCanceled) - { - cancellationTokenRegistration = cancellationToken.Register(s_OnCancel, this, false); - } - - if (!m_InOpaqueMode) - { - bytesRead = await m_InputStream.ReadAsync(buffer, offset, count, cancellationToken).SuppressContextFlow(); - } - else - { -#if DEBUG - // When using fast path only one outstanding read is permitted. By switching into opaque mode - // via IWebSocketStream.SwitchToOpaqueMode (see more detailed comments in interface definition) - // caller takes responsibility for enforcing this constraint. - Contract.Assert(Interlocked.Increment(ref m_OutstandingOperations.m_Reads) == 1, - "Only one outstanding read allowed at any given time."); -#endif - m_ReadTaskCompletionSource = new TaskCompletionSource(); - m_ReadEventArgs.SetBuffer(buffer, offset, count); - if (!ReadAsyncFast(m_ReadEventArgs)) - { - if (m_ReadEventArgs.Exception != null) - { - throw m_ReadEventArgs.Exception; - } - - bytesRead = m_ReadEventArgs.BytesTransferred; - } - else - { - bytesRead = await m_ReadTaskCompletionSource.Task.SuppressContextFlow(); - } - } - } - catch (Exception error) - { - if (s_CanHandleException(error)) - { - cancellationToken.ThrowIfCancellationRequested(); - } - - throw; - } - finally - { - cancellationTokenRegistration.Dispose(); - - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.ReadAsyncCore, bytesRead); - } - } - - return bytesRead; - } - - // return value indicates sync vs async completion - // false: sync completion - // true: async completion - private unsafe bool ReadAsyncFast(HttpListenerAsyncEventArgs eventArgs) - { - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, this, Methods.ReadAsyncFast, string.Empty); - } - - eventArgs.StartOperationCommon(this); - eventArgs.StartOperationReceive(); - - uint statusCode = 0; - bool completedAsynchronously = false; - try - { - Contract.Assert(eventArgs.Buffer != null, "'BufferList' is not supported for read operations."); - if (eventArgs.Count == 0 || m_InputStream.Closed) - { - eventArgs.FinishOperationSuccess(0, true); - return false; - } - - uint dataRead = 0; - int offset = eventArgs.Offset; - int remainingCount = eventArgs.Count; - - if (m_InputStream.BufferedDataChunksAvailable) - { - dataRead = m_InputStream.GetChunks(eventArgs.Buffer, eventArgs.Offset, eventArgs.Count); - if (m_InputStream.BufferedDataChunksAvailable && dataRead == eventArgs.Count) - { - eventArgs.FinishOperationSuccess(eventArgs.Count, true); - return false; - } - } - - Contract.Assert(!m_InputStream.BufferedDataChunksAvailable, "'m_InputStream.BufferedDataChunksAvailable' MUST BE 'FALSE' at this point."); - Contract.Assert(dataRead <= eventArgs.Count, "'dataRead' MUST NOT be bigger than 'eventArgs.Count'."); - - if (dataRead != 0) - { - offset += (int)dataRead; - remainingCount -= (int)dataRead; - //the http.sys team recommends that we limit the size to 128kb - if (remainingCount > HttpRequestStream.MaxReadSize) - { - remainingCount = HttpRequestStream.MaxReadSize; - } - - eventArgs.SetBuffer(eventArgs.Buffer, offset, remainingCount); - } - else if (remainingCount > HttpRequestStream.MaxReadSize) - { - remainingCount = HttpRequestStream.MaxReadSize; - eventArgs.SetBuffer(eventArgs.Buffer, offset, remainingCount); - } - - // m_InputStream.InternalHttpContext.EnsureBoundHandle(); - uint flags = 0; - uint bytesReturned = 0; - statusCode = - UnsafeNclNativeMethods.HttpApi.HttpReceiveRequestEntityBody2( - m_InputStream.InternalHttpContext.RequestQueueHandle, - m_InputStream.InternalHttpContext.RequestId, - flags, - (byte*)m_WebSocket.InternalBuffer.ToIntPtr(eventArgs.Offset), - (uint)eventArgs.Count, - out bytesReturned, - eventArgs.NativeOverlapped); - - if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && - statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING && - statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) - { - throw new HttpListenerException((int)statusCode); - } - else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && - HttpListener.SkipIOCPCallbackOnSuccess) - { - // IO operation completed synchronously. No IO completion port callback is used because - // it was disabled in SwitchToOpaqueMode() - eventArgs.FinishOperationSuccess((int)bytesReturned, true); - completedAsynchronously = false; - } - else - { - completedAsynchronously = true; - } - } - catch (Exception e) - { - m_ReadEventArgs.FinishOperationFailure(e, true); - m_OutputStream.SetClosedFlag(); - m_OutputStream.InternalHttpContext.Abort(); - - throw; - } - finally - { - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.ReadAsyncFast, completedAsynchronously); - } - } - - return completedAsynchronously; - } - - public override int ReadByte() - { - return m_InputStream.ReadByte(); - } - - public bool SupportsMultipleWrite - { - get - { - return true; - } - } - - public override IAsyncResult BeginRead(byte[] buffer, - int offset, - int count, - AsyncCallback callback, - object state) - { - return m_InputStream.BeginRead(buffer, offset, count, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return m_InputStream.EndRead(asyncResult); - } - - public Task MultipleWriteAsync(IList> sendBuffers, CancellationToken cancellationToken) - { - Contract.Assert(m_InOpaqueMode, "The stream MUST be in opaque mode at this point."); - Contract.Assert(sendBuffers != null, "'sendBuffers' MUST NOT be NULL."); - Contract.Assert(sendBuffers.Count == 1 || sendBuffers.Count == 2, - "'sendBuffers.Count' MUST be either '1' or '2'."); - - if (sendBuffers.Count == 1) - { - ArraySegment buffer = sendBuffers[0]; - return WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken); - } - - return MultipleWriteAsyncCore(sendBuffers, cancellationToken); - } - - private async Task MultipleWriteAsyncCore(IList> sendBuffers, CancellationToken cancellationToken) - { - Contract.Assert(sendBuffers != null, "'sendBuffers' MUST NOT be NULL."); - Contract.Assert(sendBuffers.Count == 2, "'sendBuffers.Count' MUST be '2' at this point."); - - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, this, Methods.MultipleWriteAsyncCore, string.Empty); - } - - CancellationTokenRegistration cancellationTokenRegistration = new CancellationTokenRegistration(); - - try - { - if (cancellationToken.CanBeCanceled) - { - cancellationTokenRegistration = cancellationToken.Register(s_OnCancel, this, false); - } -#if DEBUG - // When using fast path only one outstanding read is permitted. By switching into opaque mode - // via IWebSocketStream.SwitchToOpaqueMode (see more detailed comments in interface definition) - // caller takes responsibility for enforcing this constraint. - Contract.Assert(Interlocked.Increment(ref m_OutstandingOperations.m_Writes) == 1, - "Only one outstanding write allowed at any given time."); -#endif - m_WriteTaskCompletionSource = new TaskCompletionSource(); - m_WriteEventArgs.SetBuffer(null, 0, 0); - m_WriteEventArgs.BufferList = sendBuffers; - if (WriteAsyncFast(m_WriteEventArgs)) - { - await m_WriteTaskCompletionSource.Task.SuppressContextFlow(); - } - } - catch (Exception error) - { - if (s_CanHandleException(error)) - { - cancellationToken.ThrowIfCancellationRequested(); - } - - throw; - } - finally - { - cancellationTokenRegistration.Dispose(); - - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.MultipleWriteAsyncCore, string.Empty); - } - } - } - - public override void Write(byte[] buffer, int offset, int count) - { - m_OutputStream.Write(buffer, offset, count); - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - WebSocketHelpers.ValidateBuffer(buffer, offset, count); - - return WriteAsyncCore(buffer, offset, count, cancellationToken); - } - - private async Task WriteAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, this, Methods.WriteAsyncCore, - WebSocketHelpers.GetTraceMsgForParameters(offset, count, cancellationToken)); - } - - CancellationTokenRegistration cancellationTokenRegistration = new CancellationTokenRegistration(); - - try - { - if (cancellationToken.CanBeCanceled) - { - cancellationTokenRegistration = cancellationToken.Register(s_OnCancel, this, false); - } - - if (!m_InOpaqueMode) - { - await m_OutputStream.WriteAsync(buffer, offset, count, cancellationToken).SuppressContextFlow(); - } - else - { -#if DEBUG - // When using fast path only one outstanding read is permitted. By switching into opaque mode - // via IWebSocketStream.SwitchToOpaqueMode (see more detailed comments in interface definition) - // caller takes responsibility for enforcing this constraint. - Contract.Assert(Interlocked.Increment(ref m_OutstandingOperations.m_Writes) == 1, - "Only one outstanding write allowed at any given time."); -#endif - m_WriteTaskCompletionSource = new TaskCompletionSource(); - m_WriteEventArgs.BufferList = null; - m_WriteEventArgs.SetBuffer(buffer, offset, count); - if (WriteAsyncFast(m_WriteEventArgs)) - { - await m_WriteTaskCompletionSource.Task.SuppressContextFlow(); - } - } - } - catch (Exception error) - { - if (s_CanHandleException(error)) - { - cancellationToken.ThrowIfCancellationRequested(); - } - - throw; - } - finally - { - cancellationTokenRegistration.Dispose(); - - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.WriteAsyncCore, string.Empty); - } - } - } - - // return value indicates sync vs async completion - // false: sync completion - // true: async completion - private bool WriteAsyncFast(HttpListenerAsyncEventArgs eventArgs) - { - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, this, Methods.WriteAsyncFast, string.Empty); - } - - UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE; - - eventArgs.StartOperationCommon(this); - eventArgs.StartOperationSend(); - - uint statusCode; - bool completedAsynchronously = false; - try - { - if (m_OutputStream.Closed || - (eventArgs.Buffer != null && eventArgs.Count == 0)) - { - eventArgs.FinishOperationSuccess(eventArgs.Count, true); - return false; - } - - if (eventArgs.ShouldCloseOutput) - { - flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT; - } - else - { - flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; - // When using HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA HTTP.SYS will copy the payload to - // kernel memory (Non-Paged Pool). Http.Sys will buffer up to - // Math.Min(16 MB, current TCP window size) - flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA; - } - - m_OutputStream.InternalHttpContext.EnsureBoundHandle(); - uint bytesSent; - statusCode = - UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody2( - m_OutputStream.InternalHttpContext.RequestQueueHandle, - m_OutputStream.InternalHttpContext.RequestId, - (uint)flags, - eventArgs.EntityChunkCount, - eventArgs.EntityChunks, - out bytesSent, - SafeLocalFree.Zero, - 0, - eventArgs.NativeOverlapped, - IntPtr.Zero); - - if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && - statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) - { - throw new HttpListenerException((int)statusCode); - } - else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && - HttpListener.SkipIOCPCallbackOnSuccess) - { - // IO operation completed synchronously - callback won't be called to signal completion. - eventArgs.FinishOperationSuccess((int)bytesSent, true); - completedAsynchronously = false; - } - else - { - completedAsynchronously = true; - } - } - catch (Exception e) - { - m_WriteEventArgs.FinishOperationFailure(e, true); - m_OutputStream.SetClosedFlag(); - m_OutputStream.InternalHttpContext.Abort(); - - throw; - } - finally - { - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.WriteAsyncFast, completedAsynchronously); - } - } - - return completedAsynchronously; - } - - public override void WriteByte(byte value) - { - m_OutputStream.WriteByte(value); - } - - public override IAsyncResult BeginWrite(byte[] buffer, - int offset, - int count, - AsyncCallback callback, - object state) - { - return m_OutputStream.BeginWrite(buffer, offset, count, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - m_OutputStream.EndWrite(asyncResult); - } - - public override void Flush() - { - m_OutputStream.Flush(); - } - - public override Task FlushAsync(CancellationToken cancellationToken) - { - return m_OutputStream.FlushAsync(cancellationToken); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(SR.GetString(SR.net_noseek)); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(SR.GetString(SR.net_noseek)); - } - - public async Task CloseNetworkConnectionAsync(CancellationToken cancellationToken) - { - // need to yield here to make sure that we don't get any exception synchronously - await Task.Yield(); - - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, this, Methods.CloseNetworkConnectionAsync, string.Empty); - } - - CancellationTokenRegistration cancellationTokenRegistration = new CancellationTokenRegistration(); - - try - { - if (cancellationToken.CanBeCanceled) - { - cancellationTokenRegistration = cancellationToken.Register(s_OnCancel, this, false); - } -#if DEBUG - // When using fast path only one outstanding read is permitted. By switching into opaque mode - // via IWebSocketStream.SwitchToOpaqueMode (see more detailed comments in interface definition) - // caller takes responsibility for enforcing this constraint. - Contract.Assert(Interlocked.Increment(ref m_OutstandingOperations.m_Writes) == 1, - "Only one outstanding write allowed at any given time."); -#endif - m_WriteTaskCompletionSource = new TaskCompletionSource(); - m_WriteEventArgs.SetShouldCloseOutput(); - if (WriteAsyncFast(m_WriteEventArgs)) - { - await m_WriteTaskCompletionSource.Task.SuppressContextFlow(); - } - } - catch (Exception error) - { - if (!s_CanHandleException(error)) - { - throw; - } - - // throw OperationCanceledException when canceled by the caller - // otherwise swallow the exception - cancellationToken.ThrowIfCancellationRequested(); - } - finally - { - cancellationTokenRegistration.Dispose(); - - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.CloseNetworkConnectionAsync, string.Empty); - } - } - } - - protected override void Dispose(bool disposing) - { - if (disposing && Interlocked.Exchange(ref m_CleanedUp, 1) == 0) - { - if (m_ReadTaskCompletionSource != null) - { - m_ReadTaskCompletionSource.TrySetCanceled(); - } - - if (m_WriteTaskCompletionSource != null) - { - m_WriteTaskCompletionSource.TrySetCanceled(); - } - - if (m_ReadEventArgs != null) - { - m_ReadEventArgs.Dispose(); - } - - if (m_WriteEventArgs != null) - { - m_WriteEventArgs.Dispose(); - } - - try - { - m_InputStream.Close(); - } - finally - { - m_OutputStream.Close(); - } - } - } - - public void Abort() - { - OnCancel(this); - } - - private static bool CanHandleException(Exception error) - { - return error is HttpListenerException || - error is ObjectDisposedException || - error is IOException; - } - - private static void OnCancel(object state) - { - Contract.Assert(state != null, "'state' MUST NOT be NULL."); - WebSocketHttpListenerDuplexStream thisPtr = state as WebSocketHttpListenerDuplexStream; - Contract.Assert(thisPtr != null, "'thisPtr' MUST NOT be NULL."); - - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, state, Methods.OnCancel, string.Empty); - } - - try - { - thisPtr.m_OutputStream.SetClosedFlag(); - // thisPtr.m_Context.Abort(); - } - catch { } - - TaskCompletionSource readTaskCompletionSourceSnapshot = thisPtr.m_ReadTaskCompletionSource; - - if (readTaskCompletionSourceSnapshot != null) - { - readTaskCompletionSourceSnapshot.TrySetCanceled(); - } - - TaskCompletionSource writeTaskCompletionSourceSnapshot = thisPtr.m_WriteTaskCompletionSource; - - if (writeTaskCompletionSourceSnapshot != null) - { - writeTaskCompletionSourceSnapshot.TrySetCanceled(); - } - - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, state, Methods.OnCancel, string.Empty); - } - } - - public void SwitchToOpaqueMode(WebSocketBase webSocket) - { - Contract.Assert(webSocket != null, "'webSocket' MUST NOT be NULL."); - Contract.Assert(m_OutputStream != null, "'m_OutputStream' MUST NOT be NULL."); - Contract.Assert(m_OutputStream.InternalHttpContext != null, - "'m_OutputStream.InternalHttpContext' MUST NOT be NULL."); - Contract.Assert(m_OutputStream.InternalHttpContext.Response != null, - "'m_OutputStream.InternalHttpContext.Response' MUST NOT be NULL."); - Contract.Assert(m_OutputStream.InternalHttpContext.Response.SentHeaders, - "Headers MUST have been sent at this point."); - Contract.Assert(!m_InOpaqueMode, "SwitchToOpaqueMode MUST NOT be called multiple times."); - - if (m_InOpaqueMode) - { - throw new InvalidOperationException(); - } - - m_WebSocket = webSocket; - m_InOpaqueMode = true; - m_ReadEventArgs = new HttpListenerAsyncEventArgs(webSocket, this); - m_ReadEventArgs.Completed += s_OnReadCompleted; - m_WriteEventArgs = new HttpListenerAsyncEventArgs(webSocket, this); - m_WriteEventArgs.Completed += s_OnWriteCompleted; - - if (WebSocketBase.LoggingEnabled) - { - Logging.Associate(Logging.WebSockets, this, webSocket); - } - } - - private static void OnWriteCompleted(object sender, HttpListenerAsyncEventArgs eventArgs) - { - Contract.Assert(eventArgs != null, "'eventArgs' MUST NOT be NULL."); - WebSocketHttpListenerDuplexStream thisPtr = eventArgs.CurrentStream; - Contract.Assert(thisPtr != null, "'thisPtr' MUST NOT be NULL."); -#if DEBUG - Contract.Assert(Interlocked.Decrement(ref thisPtr.m_OutstandingOperations.m_Writes) >= 0, - "'thisPtr.m_OutstandingOperations.m_Writes' MUST NOT be negative."); -#endif - - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, thisPtr, Methods.OnWriteCompleted, string.Empty); - } - - if (eventArgs.Exception != null) - { - thisPtr.m_WriteTaskCompletionSource.TrySetException(eventArgs.Exception); - } - else - { - thisPtr.m_WriteTaskCompletionSource.TrySetResult(null); - } - - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, thisPtr, Methods.OnWriteCompleted, string.Empty); - } - } - - private static void OnReadCompleted(object sender, HttpListenerAsyncEventArgs eventArgs) - { - Contract.Assert(eventArgs != null, "'eventArgs' MUST NOT be NULL."); - WebSocketHttpListenerDuplexStream thisPtr = eventArgs.CurrentStream; - Contract.Assert(thisPtr != null, "'thisPtr' MUST NOT be NULL."); -#if DEBUG - Contract.Assert(Interlocked.Decrement(ref thisPtr.m_OutstandingOperations.m_Reads) >= 0, - "'thisPtr.m_OutstandingOperations.m_Reads' MUST NOT be negative."); -#endif - - if (WebSocketBase.LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, thisPtr, Methods.OnReadCompleted, string.Empty); - } - - if (eventArgs.Exception != null) - { - thisPtr.m_ReadTaskCompletionSource.TrySetException(eventArgs.Exception); - } - else - { - thisPtr.m_ReadTaskCompletionSource.TrySetResult(eventArgs.BytesTransferred); - } - - if (WebSocketBase.LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, thisPtr, Methods.OnReadCompleted, string.Empty); - } - } - - internal class HttpListenerAsyncEventArgs : EventArgs, IDisposable - { - private const int Free = 0; - private const int InProgress = 1; - private const int Disposed = 2; - private int m_Operating; - - private bool m_DisposeCalled; - private SafeNativeOverlapped m_PtrNativeOverlapped; - private Overlapped m_Overlapped; - private event EventHandler m_Completed; - private byte[] m_Buffer; - private IList> m_BufferList; - private int m_Count; - private int m_Offset; - private int m_BytesTransferred; - private HttpListenerAsyncOperation m_CompletedOperation; - private UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[] m_DataChunks; - private GCHandle m_DataChunksGCHandle; - private ushort m_DataChunkCount; - private Exception m_Exception; - private bool m_ShouldCloseOutput; - private readonly WebSocketBase m_WebSocket; - private readonly WebSocketHttpListenerDuplexStream m_CurrentStream; - - public HttpListenerAsyncEventArgs(WebSocketBase webSocket, WebSocketHttpListenerDuplexStream stream) - : base() - { - m_WebSocket = webSocket; - m_CurrentStream = stream; - InitializeOverlapped(); - } - - public int BytesTransferred - { - get { return m_BytesTransferred; } - } - - public byte[] Buffer - { - get { return m_Buffer; } - } - - // BufferList property. - // Mutually exclusive with Buffer. - // Setting this property with an existing non-null Buffer will cause an assert. - public IList> BufferList - { - get { return m_BufferList; } - set - { - Contract.Assert(!m_ShouldCloseOutput, "'m_ShouldCloseOutput' MUST be 'false' at this point."); - Contract.Assert(value == null || m_Buffer == null, - "Either 'm_Buffer' or 'm_BufferList' MUST be NULL."); - Contract.Assert(m_Operating == Free, - "This property can only be modified if no IO operation is outstanding."); - Contract.Assert(value == null || value.Count == 2, - "This list can only be 'NULL' or MUST have exactly '2' items."); - m_BufferList = value; - } - } - - public bool ShouldCloseOutput - { - get { return m_ShouldCloseOutput; } - } - - public int Offset - { - get { return m_Offset; } - } - - public int Count - { - get { return m_Count; } - } - - public Exception Exception - { - get { return m_Exception; } - } - - public ushort EntityChunkCount - { - get - { - if (m_DataChunks == null) - { - return 0; - } - - return m_DataChunkCount; - } - } - - public SafeNativeOverlapped NativeOverlapped - { - get { return m_PtrNativeOverlapped; } - } - - public IntPtr EntityChunks - { - get - { - if (m_DataChunks == null) - { - return IntPtr.Zero; - } - - return Marshal.UnsafeAddrOfPinnedArrayElement(m_DataChunks, 0); - } - } - - public WebSocketHttpListenerDuplexStream CurrentStream - { - get { return m_CurrentStream; } - } - - public event EventHandler Completed - { - add - { - m_Completed += value; - } - remove - { - m_Completed -= value; - } - } - - protected virtual void OnCompleted(HttpListenerAsyncEventArgs e) - { - EventHandler handler = m_Completed; - if (handler != null) - { - handler(e.m_CurrentStream, e); - } - } - - public void SetShouldCloseOutput() - { - m_BufferList = null; - m_Buffer = null; - m_ShouldCloseOutput = true; - } - - public void Dispose() - { - // Remember that Dispose was called. - m_DisposeCalled = true; - - // Check if this object is in-use for an async socket operation. - if (Interlocked.CompareExchange(ref m_Operating, Disposed, Free) != Free) - { - // Either already disposed or will be disposed when current operation completes. - return; - } - - // OK to dispose now. - // Free native overlapped data. - FreeOverlapped(false); - - // Don't bother finalizing later. - GC.SuppressFinalize(this); - } - - // Finalizer - ~HttpListenerAsyncEventArgs() - { - FreeOverlapped(true); - } - - private unsafe void InitializeOverlapped() - { - m_Overlapped = new Overlapped(); - m_PtrNativeOverlapped = new SafeNativeOverlapped(m_Overlapped.UnsafePack(CompletionPortCallback, null)); - } - - // Method to clean up any existing Overlapped object and related state variables. - private void FreeOverlapped(bool checkForShutdown) - { - if (!checkForShutdown || !NclUtilities.HasShutdownStarted) - { - // Free the overlapped object - if (m_PtrNativeOverlapped != null && !m_PtrNativeOverlapped.IsInvalid) - { - m_PtrNativeOverlapped.Dispose(); - } - - if (m_DataChunksGCHandle.IsAllocated) - { - m_DataChunksGCHandle.Free(); - } - } - } - - // Method called to prepare for a native async http.sys call. - // This method performs the tasks common to all http.sys operations. - internal void StartOperationCommon(WebSocketHttpListenerDuplexStream currentStream) - { - // Change status to "in-use". - if(Interlocked.CompareExchange(ref m_Operating, InProgress, Free) != Free) - { - // If it was already "in-use" check if Dispose was called. - if (m_DisposeCalled) - { - // Dispose was called - throw ObjectDisposed. - throw new ObjectDisposedException(GetType().FullName); - } - - Contract.Assert(false, "Only one outstanding async operation is allowed per HttpListenerAsyncEventArgs instance."); - // Only one at a time. - throw new InvalidOperationException(); - } - - // HttpSendResponseEntityBody can return ERROR_INVALID_PARAMETER if the InternalHigh field of the overlapped - // is not IntPtr.Zero, so we have to reset this field because we are reusing the Overlapped. - // When using the IAsyncResult based approach of HttpListenerResponseStream the Overlapped is reinitialized - // for each operation by the CLR when returned from the OverlappedDataCache. - NativeOverlapped.ReinitializeNativeOverlapped(); - m_Exception = null; - m_BytesTransferred = 0; - } - - internal void StartOperationReceive() - { - // Remember the operation type. - m_CompletedOperation = HttpListenerAsyncOperation.Receive; - } - - internal void StartOperationSend() - { - UpdateDataChunk(); - - // Remember the operation type. - m_CompletedOperation = HttpListenerAsyncOperation.Send; - } - - public void SetBuffer(byte[] buffer, int offset, int count) - { - Contract.Assert(!m_ShouldCloseOutput, "'m_ShouldCloseOutput' MUST be 'false' at this point."); - Contract.Assert(buffer == null || m_BufferList == null, "Either 'm_Buffer' or 'm_BufferList' MUST be NULL."); - m_Buffer = buffer; - m_Offset = offset; - m_Count = count; - } - - private unsafe void UpdateDataChunk() - { - if (m_DataChunks == null) - { - m_DataChunks = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[2]; - m_DataChunksGCHandle = GCHandle.Alloc(m_DataChunks); - m_DataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK(); - m_DataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; - m_DataChunks[1] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK(); - m_DataChunks[1].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; - } - - Contract.Assert(m_Buffer == null || m_BufferList == null, "Either 'm_Buffer' or 'm_BufferList' MUST be NULL."); - Contract.Assert(m_ShouldCloseOutput || m_Buffer != null || m_BufferList != null, "Either 'm_Buffer' or 'm_BufferList' MUST NOT be NULL."); - - // The underlying byte[] m_Buffer or each m_BufferList[].Array are pinned already - if (m_Buffer != null) - { - UpdateDataChunk(0, m_Buffer, m_Offset, m_Count); - UpdateDataChunk(1, null, 0, 0); - m_DataChunkCount = 1; - } - else if (m_BufferList != null) - { - Contract.Assert(m_BufferList != null && m_BufferList.Count == 2, - "'m_BufferList' MUST NOT be NULL and have exactly '2' items at this point."); - UpdateDataChunk(0, m_BufferList[0].Array, m_BufferList[0].Offset, m_BufferList[0].Count); - UpdateDataChunk(1, m_BufferList[1].Array, m_BufferList[1].Offset, m_BufferList[1].Count); - m_DataChunkCount = 2; - } - else - { - Contract.Assert(m_ShouldCloseOutput, "'m_ShouldCloseOutput' MUST be 'true' at this point."); - m_DataChunks = null; - } - } - - private unsafe void UpdateDataChunk(int index, byte[] buffer, int offset, int count) - { - if (buffer == null) - { - m_DataChunks[index].pBuffer = null; - m_DataChunks[index].BufferLength = 0; - return; - } - - if (m_WebSocket.InternalBuffer.IsInternalBuffer(buffer, offset, count)) - { - m_DataChunks[index].pBuffer = (byte*)(m_WebSocket.InternalBuffer.ToIntPtr(offset)); - } - else - { - m_DataChunks[index].pBuffer = - (byte*)m_WebSocket.InternalBuffer.ConvertPinnedSendPayloadToNative(buffer, offset, count); - } - - m_DataChunks[index].BufferLength = (uint)count; - } - - // Method to mark this object as no longer "in-use". - // Will also execute a Dispose deferred because I/O was in progress. - internal void Complete() - { - // Mark as not in-use - m_Operating = Free; - - // Check for deferred Dispose(). - // The deferred Dispose is not guaranteed if Dispose is called while an operation is in progress. - // The m_DisposeCalled variable is not managed in a thread-safe manner on purpose for performance. - if (m_DisposeCalled) - { - Dispose(); - } - } - - // Method to update internal state after sync or async completion. - private void SetResults(Exception exception, int bytesTransferred) - { - m_Exception = exception; - m_BytesTransferred = bytesTransferred; - } - - internal void FinishOperationFailure(Exception exception, bool syncCompletion) - { - SetResults(exception, 0); - - if (WebSocketBase.LoggingEnabled) - { - Logging.PrintError(Logging.WebSockets, m_CurrentStream, - m_CompletedOperation == HttpListenerAsyncOperation.Receive ? Methods.ReadAsyncFast : Methods.WriteAsyncFast, - exception.ToString()); - } - - Complete(); - OnCompleted(this); - } - - internal void FinishOperationSuccess(int bytesTransferred, bool syncCompletion) - { - SetResults(null, bytesTransferred); - - if (WebSocketBase.LoggingEnabled) - { - if (m_Buffer != null) - { - Logging.Dump(Logging.WebSockets, m_CurrentStream, - m_CompletedOperation == HttpListenerAsyncOperation.Receive ? Methods.ReadAsyncFast : Methods.WriteAsyncFast, - m_Buffer, m_Offset, bytesTransferred); - } - else if (m_BufferList != null) - { - Contract.Assert(m_CompletedOperation == HttpListenerAsyncOperation.Send, - "'BufferList' is only supported for send operations."); - - foreach (ArraySegment buffer in BufferList) - { - Logging.Dump(Logging.WebSockets, this, Methods.WriteAsyncFast, buffer.Array, buffer.Offset, buffer.Count); - } - } - else - { - Logging.PrintLine(Logging.WebSockets, TraceEventType.Verbose, 0, - string.Format(CultureInfo.InvariantCulture, "Output channel closed for {0}#{1}", - m_CurrentStream.GetType().Name, ValidationHelper.HashString(m_CurrentStream))); - } - } - - if (m_ShouldCloseOutput) - { - m_CurrentStream.m_OutputStream.SetClosedFlag(); - } - - // Complete the operation and raise completion event. - Complete(); - OnCompleted(this); - } - - private unsafe void CompletionPortCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) - { - if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS || - errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) - { - FinishOperationSuccess((int)numBytes, false); - } - else - { - FinishOperationFailure(new HttpListenerException((int)errorCode), false); - } - } - - public enum HttpListenerAsyncOperation - { - None, - Receive, - Send - } - } - - private static class Methods - { - public const string CloseNetworkConnectionAsync = "CloseNetworkConnectionAsync"; - public const string OnCancel = "OnCancel"; - public const string OnReadCompleted = "OnReadCompleted"; - public const string OnWriteCompleted = "OnWriteCompleted"; - public const string ReadAsyncFast = "ReadAsyncFast"; - public const string ReadAsyncCore = "ReadAsyncCore"; - public const string WriteAsyncFast = "WriteAsyncFast"; - public const string WriteAsyncCore = "WriteAsyncCore"; - public const string MultipleWriteAsyncCore = "MultipleWriteAsyncCore"; - } - } -} -*/ \ No newline at end of file diff --git a/src/Microsoft.Net.WebSockets.Server/Microsoft.Net.WebSockets.Server.xproj b/src/Microsoft.Net.WebSockets.Server/Microsoft.Net.WebSockets.Server.xproj deleted file mode 100644 index a5fa974e8a..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/Microsoft.Net.WebSockets.Server.xproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - e788aeae-2cb4-4bfa-8746-d0bb7e93a1bb - .\obj - .\bin\ - - - 2.0 - - - \ No newline at end of file diff --git a/src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeLoadLibrary.cs b/src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeLoadLibrary.cs deleted file mode 100644 index dcb231e78b..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeLoadLibrary.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using Microsoft.Win32.SafeHandles; - -namespace Microsoft.Net.WebSockets -{ - internal sealed class SafeLoadLibrary : SafeHandleZeroOrMinusOneIsInvalid - { - private const string KERNEL32 = "kernel32.dll"; - - public static readonly SafeLoadLibrary Zero = new SafeLoadLibrary(false); - - private SafeLoadLibrary() : base(true) - { - } - - private SafeLoadLibrary(bool ownsHandle) : base(ownsHandle) - { - } - - public static unsafe SafeLoadLibrary LoadLibraryEx(string library) - { - SafeLoadLibrary result = UnsafeNativeMethods.SafeNetHandles.LoadLibraryExW(library, null, 0); - if (result.IsInvalid) - { - result.SetHandleAsInvalid(); - } - return result; - } - - protected override bool ReleaseHandle() - { - return UnsafeNativeMethods.SafeNetHandles.FreeLibrary(handle); - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeWebSocketHandle.cs b/src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeWebSocketHandle.cs deleted file mode 100644 index 1219624f00..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeWebSocketHandle.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using Microsoft.Win32.SafeHandles; - -namespace Microsoft.Net.WebSockets -{ - // This class is a wrapper for a WSPC (WebSocket protocol component) session. WebSocketCreateClientHandle and WebSocketCreateServerHandle return a PVOID and not a real handle - // but we use a SafeHandle because it provides us the guarantee that WebSocketDeleteHandle will always get called. - internal sealed class SafeWebSocketHandle : SafeHandleZeroOrMinusOneIsInvalid - { - internal SafeWebSocketHandle() - : base(true) - { - } - - protected override bool ReleaseHandle() - { - if (this.IsInvalid) - { - return true; - } - - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketDeleteHandle(this.handle); - return true; - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/NativeInterop/UnsafeNativeMethods.cs b/src/Microsoft.Net.WebSockets.Server/NativeInterop/UnsafeNativeMethods.cs deleted file mode 100644 index 43c86e1fa2..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/NativeInterop/UnsafeNativeMethods.cs +++ /dev/null @@ -1,875 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics.Contracts; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Microsoft.Net.WebSockets -{ - internal static class UnsafeNativeMethods - { -#if NETSTANDARD1_3 - private const string api_ms_win_core_libraryloader_LIB = "api-ms-win-core-libraryloader-l1-1-0.dll"; -#else - private const string KERNEL32 = "kernel32.dll"; -#endif - private const string WEBSOCKET = "websocket.dll"; - - internal static class SafeNetHandles - { -#if NETSTANDARD1_3 - [DllImport(api_ms_win_core_libraryloader_LIB, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] -#else - [DllImport(KERNEL32, ExactSpelling = true, CharSet=CharSet.Unicode, SetLastError = true)] -#endif - internal static extern unsafe SafeLoadLibrary LoadLibraryExW([In] string lpwLibFileName, [In] void* hFile, [In] uint dwFlags); - -#if NETSTANDARD1_3 - [DllImport(api_ms_win_core_libraryloader_LIB, ExactSpelling = true, SetLastError = true)] -#else - [DllImport(KERNEL32, ExactSpelling = true, SetLastError = true)] -#endif - internal static extern unsafe bool FreeLibrary([In] IntPtr hModule); - } - - internal static class WebSocketProtocolComponent - { - private static readonly string DllFileName; - private static readonly string DummyWebsocketKeyBase64 = Convert.ToBase64String(new byte[16]); - private static readonly SafeLoadLibrary WebSocketDllHandle; - private static readonly string PrivateSupportedVersion; - - private static readonly HttpHeader[] InitialClientRequestHeaders = new HttpHeader[] - { - new HttpHeader() - { - Name = HttpKnownHeaderNames.Connection, - NameLength = (uint)HttpKnownHeaderNames.Connection.Length, - Value = HttpKnownHeaderNames.Upgrade, - ValueLength = (uint)HttpKnownHeaderNames.Upgrade.Length - }, - new HttpHeader() - { - Name = HttpKnownHeaderNames.Upgrade, - NameLength = (uint)HttpKnownHeaderNames.Upgrade.Length, - Value = WebSocketHelpers.WebSocketUpgradeToken, - ValueLength = (uint)WebSocketHelpers.WebSocketUpgradeToken.Length - } - }; - - private static readonly HttpHeader[] ServerFakeRequestHeaders; - - internal static class Errors - { - internal const int E_INVALID_OPERATION = unchecked((int)0x80000050); - internal const int E_INVALID_PROTOCOL_OPERATION = unchecked((int)0x80000051); - internal const int E_INVALID_PROTOCOL_FORMAT = unchecked((int)0x80000052); - internal const int E_NUMERIC_OVERFLOW = unchecked((int)0x80000053); - internal const int E_FAIL = unchecked((int)0x80004005); - } - - internal enum Action - { - NoAction = 0, - SendToNetwork = 1, - IndicateSendComplete = 2, - ReceiveFromNetwork = 3, - IndicateReceiveComplete = 4, - } - - internal enum BufferType : uint - { - None = 0x00000000, - UTF8Message = 0x80000000, - UTF8Fragment = 0x80000001, - BinaryMessage = 0x80000002, - BinaryFragment = 0x80000003, - Close = 0x80000004, - PingPong = 0x80000005, - UnsolicitedPong = 0x80000006 - } - - internal enum PropertyType - { - ReceiveBufferSize = 0, - SendBufferSize = 1, - DisableMasking = 2, - AllocatedBuffer = 3, - DisableUtf8Verification = 4, - KeepAliveInterval = 5, - } - - internal enum ActionQueue - { - Send = 1, - Receive = 2, - } - - [StructLayout(LayoutKind.Sequential)] - internal struct Property - { - internal PropertyType Type; - internal IntPtr PropertyData; - internal uint PropertySize; - } - - [StructLayout(LayoutKind.Explicit)] - internal struct Buffer - { - [FieldOffset(0)] - internal DataBuffer Data; - [FieldOffset(0)] - internal CloseBuffer CloseStatus; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct DataBuffer - { - internal IntPtr BufferData; - internal uint BufferLength; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct CloseBuffer - { - internal IntPtr ReasonData; - internal uint ReasonLength; - internal ushort CloseStatus; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct HttpHeader - { - [MarshalAs(UnmanagedType.LPStr)] - internal string Name; - internal uint NameLength; - [MarshalAs(UnmanagedType.LPStr)] - internal string Value; - internal uint ValueLength; - } - - static WebSocketProtocolComponent() - { -#if NETSTANDARD1_3 - DllFileName = Path.Combine(Environment.GetEnvironmentVariable("SYSTEMROOT"), "System32", WEBSOCKET); -#else - DllFileName = Path.Combine(Environment.SystemDirectory, WEBSOCKET); -#endif - WebSocketDllHandle = SafeLoadLibrary.LoadLibraryEx(DllFileName); - - if (!WebSocketDllHandle.IsInvalid) - { - PrivateSupportedVersion = GetSupportedVersion(); - - ServerFakeRequestHeaders = new HttpHeader[] - { - new HttpHeader() - { - Name = HttpKnownHeaderNames.Connection, - NameLength = (uint)HttpKnownHeaderNames.Connection.Length, - Value = HttpKnownHeaderNames.Upgrade, - ValueLength = (uint)HttpKnownHeaderNames.Upgrade.Length - }, - new HttpHeader() - { - Name = HttpKnownHeaderNames.Upgrade, - NameLength = (uint)HttpKnownHeaderNames.Upgrade.Length, - Value = WebSocketHelpers.WebSocketUpgradeToken, - ValueLength = (uint)WebSocketHelpers.WebSocketUpgradeToken.Length - }, - new HttpHeader() - { - Name = HttpKnownHeaderNames.Host, - NameLength = (uint)HttpKnownHeaderNames.Host.Length, - Value = string.Empty, - ValueLength = 0 - }, - new HttpHeader() - { - Name = HttpKnownHeaderNames.SecWebSocketVersion, - NameLength = (uint)HttpKnownHeaderNames.SecWebSocketVersion.Length, - Value = SupportedVersion, - ValueLength = (uint)SupportedVersion.Length - }, - new HttpHeader() - { - Name = HttpKnownHeaderNames.SecWebSocketKey, - NameLength = (uint)HttpKnownHeaderNames.SecWebSocketKey.Length, - Value = DummyWebsocketKeyBase64, - ValueLength = (uint)DummyWebsocketKeyBase64.Length - } - }; - } - } - - internal static string SupportedVersion - { - get - { - if (WebSocketDllHandle.IsInvalid) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - return PrivateSupportedVersion; - } - } - - internal static bool IsSupported - { - get - { - return !WebSocketDllHandle.IsInvalid; - } - } - - internal static string GetSupportedVersion() - { - if (WebSocketDllHandle.IsInvalid) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - SafeWebSocketHandle webSocketHandle = null; - try - { - int errorCode = WebSocketCreateClientHandle_Raw(null, 0, out webSocketHandle); - ThrowOnError(errorCode); - - if (webSocketHandle == null || - webSocketHandle.IsInvalid) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - IntPtr additionalHeadersPtr; - uint additionalHeaderCount; - - errorCode = WebSocketBeginClientHandshake_Raw(webSocketHandle, - IntPtr.Zero, - 0, - IntPtr.Zero, - 0, - InitialClientRequestHeaders, - (uint)InitialClientRequestHeaders.Length, - out additionalHeadersPtr, - out additionalHeaderCount); - ThrowOnError(errorCode); - - HttpHeader[] additionalHeaders = MarshalHttpHeaders(additionalHeadersPtr, (int)additionalHeaderCount); - - string version = null; - foreach (HttpHeader header in additionalHeaders) - { - if (string.Compare(header.Name, - HttpKnownHeaderNames.SecWebSocketVersion, - StringComparison.OrdinalIgnoreCase) == 0) - { - version = header.Value; - break; - } - } - Contract.Assert(version != null, "'version' MUST NOT be NULL."); - - return version; - } - finally - { - if (webSocketHandle != null) - { - webSocketHandle.Dispose(); - } - } - } - - internal static void WebSocketCreateClientHandle(Property[] properties, - out SafeWebSocketHandle webSocketHandle) - { - uint propertyCount = properties == null ? 0 : (uint)properties.Length; - - if (WebSocketDllHandle.IsInvalid) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - int errorCode = WebSocketCreateClientHandle_Raw(properties, propertyCount, out webSocketHandle); - ThrowOnError(errorCode); - - if (webSocketHandle == null || - webSocketHandle.IsInvalid) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - IntPtr additionalHeadersPtr; - uint additionalHeaderCount; - - // Currently the WSPC doesn't allow to initiate a data session - // without also being involved in the http handshake - // There is no information whatsoever, which is needed by the - // WSPC for parsing WebSocket frames from the HTTP handshake - // In the managed implementation the HTTP header handling - // will be done using the managed HTTP stack and we will - // just fake an HTTP handshake for the WSPC calling - // WebSocketBeginClientHandshake and WebSocketEndClientHandshake - // with statically defined dummy headers. - errorCode = WebSocketBeginClientHandshake_Raw(webSocketHandle, - IntPtr.Zero, - 0, - IntPtr.Zero, - 0, - InitialClientRequestHeaders, - (uint)InitialClientRequestHeaders.Length, - out additionalHeadersPtr, - out additionalHeaderCount); - - ThrowOnError(errorCode); - - HttpHeader[] additionalHeaders = MarshalHttpHeaders(additionalHeadersPtr, (int)additionalHeaderCount); - - string key = null; - foreach (HttpHeader header in additionalHeaders) - { - if (string.Compare(header.Name, - HttpKnownHeaderNames.SecWebSocketKey, - StringComparison.OrdinalIgnoreCase) == 0) - { - key = header.Value; - break; - } - } - Contract.Assert(key != null, "'key' MUST NOT be NULL."); - - string acceptValue = WebSocketHelpers.GetSecWebSocketAcceptString(key); - HttpHeader[] responseHeaders = new HttpHeader[] - { - new HttpHeader() - { - Name = HttpKnownHeaderNames.Connection, - NameLength = (uint)HttpKnownHeaderNames.Connection.Length, - Value = HttpKnownHeaderNames.Upgrade, - ValueLength = (uint)HttpKnownHeaderNames.Upgrade.Length - }, - new HttpHeader() - { - Name = HttpKnownHeaderNames.Upgrade, - NameLength = (uint)HttpKnownHeaderNames.Upgrade.Length, - Value = WebSocketHelpers.WebSocketUpgradeToken, - ValueLength = (uint)WebSocketHelpers.WebSocketUpgradeToken.Length - }, - new HttpHeader() - { - Name = HttpKnownHeaderNames.SecWebSocketAccept, - NameLength = (uint)HttpKnownHeaderNames.SecWebSocketAccept.Length, - Value = acceptValue, - ValueLength = (uint)acceptValue.Length - } - }; - - errorCode = WebSocketEndClientHandshake_Raw(webSocketHandle, - responseHeaders, - (uint)responseHeaders.Length, - IntPtr.Zero, - IntPtr.Zero, - IntPtr.Zero); - - ThrowOnError(errorCode); - - Contract.Assert(webSocketHandle != null, "'webSocketHandle' MUST NOT be NULL at this point."); - } - - internal static void WebSocketCreateServerHandle(Property[] properties, - int propertyCount, - out SafeWebSocketHandle webSocketHandle) - { - Contract.Assert(propertyCount >= 0, "'propertyCount' MUST NOT be negative."); - Contract.Assert((properties == null && propertyCount == 0) || - (properties != null && propertyCount == properties.Length), - "'propertyCount' MUST MATCH 'properties.Length'."); - - if (WebSocketDllHandle.IsInvalid) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - int errorCode = WebSocketCreateServerHandle_Raw(properties, (uint)propertyCount, out webSocketHandle); - ThrowOnError(errorCode); - - if (webSocketHandle == null || - webSocketHandle.IsInvalid) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - IntPtr responseHeadersPtr; - uint responseHeaderCount; - - // Currently the WSPC doesn't allow to initiate a data session - // without also being involved in the http handshake - // There is no information whatsoever, which is needed by the - // WSPC for parsing WebSocket frames from the HTTP handshake - // In the managed implementation the HTTP header handling - // will be done using the managed HTTP stack and we will - // just fake an HTTP handshake for the WSPC calling - // WebSocketBeginServerHandshake and WebSocketEndServerHandshake - // with statically defined dummy headers. - errorCode = WebSocketBeginServerHandshake_Raw(webSocketHandle, - IntPtr.Zero, - IntPtr.Zero, - 0, - ServerFakeRequestHeaders, - (uint)ServerFakeRequestHeaders.Length, - out responseHeadersPtr, - out responseHeaderCount); - - ThrowOnError(errorCode); - - HttpHeader[] responseHeaders = MarshalHttpHeaders(responseHeadersPtr, (int)responseHeaderCount); - errorCode = WebSocketEndServerHandshake_Raw(webSocketHandle); - - ThrowOnError(errorCode); - - Contract.Assert(webSocketHandle != null, "'webSocketHandle' MUST NOT be NULL at this point."); - } - - internal static void WebSocketAbortHandle(SafeHandle webSocketHandle) - { - Contract.Assert(webSocketHandle != null && !webSocketHandle.IsInvalid, - "'webSocketHandle' MUST NOT be NULL or INVALID."); - - WebSocketAbortHandle_Raw(webSocketHandle); - - DrainActionQueue(webSocketHandle, ActionQueue.Send); - DrainActionQueue(webSocketHandle, ActionQueue.Receive); - } - - internal static void WebSocketDeleteHandle(IntPtr webSocketPtr) - { - Contract.Assert(webSocketPtr != IntPtr.Zero, "'webSocketPtr' MUST NOT be IntPtr.Zero."); - WebSocketDeleteHandle_Raw(webSocketPtr); - } - - internal static void WebSocketSend(WebSocketBase webSocket, - BufferType bufferType, - Buffer buffer) - { - Contract.Assert(webSocket != null, - "'webSocket' MUST NOT be NULL or INVALID."); - Contract.Assert(webSocket.SessionHandle != null && !webSocket.SessionHandle.IsInvalid, - "'webSocket.SessionHandle' MUST NOT be NULL or INVALID."); - - ThrowIfSessionHandleClosed(webSocket); - - int errorCode; - try - { - errorCode = WebSocketSend_Raw(webSocket.SessionHandle, bufferType, ref buffer, IntPtr.Zero); - } - catch (ObjectDisposedException innerException) - { - throw ConvertObjectDisposedException(webSocket, innerException); - } - - ThrowOnError(errorCode); - } - - internal static void WebSocketSendWithoutBody(WebSocketBase webSocket, - BufferType bufferType) - { - Contract.Assert(webSocket != null, - "'webSocket' MUST NOT be NULL or INVALID."); - Contract.Assert(webSocket.SessionHandle != null && !webSocket.SessionHandle.IsInvalid, - "'webSocket.SessionHandle' MUST NOT be NULL or INVALID."); - - ThrowIfSessionHandleClosed(webSocket); - - int errorCode; - try - { - errorCode = WebSocketSendWithoutBody_Raw(webSocket.SessionHandle, bufferType, IntPtr.Zero, IntPtr.Zero); - } - catch (ObjectDisposedException innerException) - { - throw ConvertObjectDisposedException(webSocket, innerException); - } - - ThrowOnError(errorCode); - } - - internal static void WebSocketReceive(WebSocketBase webSocket) - { - Contract.Assert(webSocket != null, - "'webSocket' MUST NOT be NULL or INVALID."); - Contract.Assert(webSocket.SessionHandle != null && !webSocket.SessionHandle.IsInvalid, - "'webSocket.SessionHandle' MUST NOT be NULL or INVALID."); - - ThrowIfSessionHandleClosed(webSocket); - - int errorCode; - try - { - errorCode = WebSocketReceive_Raw(webSocket.SessionHandle, IntPtr.Zero, IntPtr.Zero); - } - catch (ObjectDisposedException innerException) - { - throw ConvertObjectDisposedException(webSocket, innerException); - } - - ThrowOnError(errorCode); - } - - internal static void WebSocketGetAction(WebSocketBase webSocket, - ActionQueue actionQueue, - Buffer[] dataBuffers, - ref uint dataBufferCount, - out Action action, - out BufferType bufferType, - out IntPtr actionContext) - { - Contract.Assert(webSocket != null, - "'webSocket' MUST NOT be NULL or INVALID."); - Contract.Assert(webSocket.SessionHandle != null && !webSocket.SessionHandle.IsInvalid, - "'webSocket.SessionHandle' MUST NOT be NULL or INVALID."); - Contract.Assert(dataBufferCount >= 0, "'dataBufferCount' MUST NOT be negative."); - Contract.Assert((dataBuffers == null && dataBufferCount == 0) || - (dataBuffers != null && dataBufferCount == dataBuffers.Length), - "'dataBufferCount' MUST MATCH 'dataBuffers.Length'."); - - action = Action.NoAction; - bufferType = BufferType.None; - actionContext = IntPtr.Zero; - - IntPtr dummy; - ThrowIfSessionHandleClosed(webSocket); - - int errorCode; - try - { - errorCode = WebSocketGetAction_Raw(webSocket.SessionHandle, - actionQueue, - dataBuffers, - ref dataBufferCount, - out action, - out bufferType, - out dummy, - out actionContext); - } - catch (ObjectDisposedException innerException) - { - throw ConvertObjectDisposedException(webSocket, innerException); - } - ThrowOnError(errorCode); - - webSocket.ValidateNativeBuffers(action, bufferType, dataBuffers, dataBufferCount); - - Contract.Assert(dataBufferCount >= 0); - Contract.Assert((dataBufferCount == 0 && dataBuffers == null) || - (dataBufferCount <= dataBuffers.Length)); - } - - internal static void WebSocketCompleteAction(WebSocketBase webSocket, - IntPtr actionContext, - int bytesTransferred) - { - Contract.Assert(webSocket != null, - "'webSocket' MUST NOT be NULL or INVALID."); - Contract.Assert(webSocket.SessionHandle != null && !webSocket.SessionHandle.IsInvalid, - "'webSocket.SessionHandle' MUST NOT be NULL or INVALID."); - Contract.Assert(actionContext != IntPtr.Zero, "'actionContext' MUST NOT be IntPtr.Zero."); - Contract.Assert(bytesTransferred >= 0, "'bytesTransferred' MUST NOT be negative."); - - if (webSocket.SessionHandle.IsClosed) - { - return; - } - - try - { - WebSocketCompleteAction_Raw(webSocket.SessionHandle, actionContext, (uint)bytesTransferred); - } - catch (ObjectDisposedException) - { - } - } - - internal static TimeSpan WebSocketGetDefaultKeepAliveInterval() - { - uint result = 0; - uint size = sizeof(uint); - int errorCode = WebSocketGetGlobalProperty_Raw(PropertyType.KeepAliveInterval, ref result, ref size); - if (!Succeeded(errorCode)) - { - Contract.Assert(errorCode == 0, "errorCode: " + errorCode); - return Timeout.InfiniteTimeSpan; - } - return TimeSpan.FromMilliseconds(result); - } - - private static void DrainActionQueue(SafeHandle webSocketHandle, ActionQueue actionQueue) - { - Contract.Assert(webSocketHandle != null && !webSocketHandle.IsInvalid, - "'webSocketHandle' MUST NOT be NULL or INVALID."); - - IntPtr actionContext; - IntPtr dummy; - Action action; - BufferType bufferType; - - while (true) - { - Buffer[] dataBuffers = new Buffer[1]; - uint dataBufferCount = 1; - int errorCode = WebSocketGetAction_Raw(webSocketHandle, - actionQueue, - dataBuffers, - ref dataBufferCount, - out action, - out bufferType, - out dummy, - out actionContext); - - if (!Succeeded(errorCode)) - { - Contract.Assert(errorCode == 0, "'errorCode' MUST be 0."); - return; - } - - if (action == Action.NoAction) - { - return; - } - - WebSocketCompleteAction_Raw(webSocketHandle, actionContext, 0); - } - } - - private static void MarshalAndVerifyHttpHeader(IntPtr httpHeaderPtr, - ref HttpHeader httpHeader) - { - Contract.Assert(httpHeaderPtr != IntPtr.Zero, "'currentHttpHeaderPtr' MUST NOT be IntPtr.Zero."); - - IntPtr httpHeaderNamePtr = Marshal.ReadIntPtr(httpHeaderPtr); - IntPtr lengthPtr = IntPtr.Add(httpHeaderPtr, IntPtr.Size); - int length = Marshal.ReadInt32(lengthPtr); - Contract.Assert(length >= 0, "'length' MUST NOT be negative."); - - if (httpHeaderNamePtr != IntPtr.Zero) - { - httpHeader.Name = Marshal.PtrToStringAnsi(httpHeaderNamePtr, length); - } - - if ((httpHeader.Name == null && length != 0) || - (httpHeader.Name != null && length != httpHeader.Name.Length)) - { - Contract.Assert(false, "The length of 'httpHeader.Name' MUST MATCH 'length'."); - throw new AccessViolationException(); - } - - // structure of HttpHeader: - // Name = string* - // NameLength = uint* - // Value = string* - // ValueLength = uint* - // NOTE - All fields in the object are pointers to the actual value, hence the use of - // n * IntPtr.Size to get to the correct place in the object. - int valueOffset = 2 * IntPtr.Size; - int lengthOffset = 3 * IntPtr.Size; - - IntPtr httpHeaderValuePtr = - Marshal.ReadIntPtr(IntPtr.Add(httpHeaderPtr, valueOffset)); - lengthPtr = IntPtr.Add(httpHeaderPtr, lengthOffset); - length = Marshal.ReadInt32(lengthPtr); - httpHeader.Value = Marshal.PtrToStringAnsi(httpHeaderValuePtr, (int)length); - - if ((httpHeader.Value == null && length != 0) || - (httpHeader.Value != null && length != httpHeader.Value.Length)) - { - Contract.Assert(false, "The length of 'httpHeader.Value' MUST MATCH 'length'."); - throw new AccessViolationException(); - } - } - - private static HttpHeader[] MarshalHttpHeaders(IntPtr nativeHeadersPtr, - int nativeHeaderCount) - { - Contract.Assert(nativeHeaderCount >= 0, "'nativeHeaderCount' MUST NOT be negative."); - Contract.Assert(nativeHeadersPtr != IntPtr.Zero || nativeHeaderCount == 0, - "'nativeHeaderCount' MUST be 0."); - - HttpHeader[] httpHeaders = new HttpHeader[nativeHeaderCount]; - - // structure of HttpHeader: - // Name = string* - // NameLength = uint* - // Value = string* - // ValueLength = uint* - // NOTE - All fields in the object are pointers to the actual value, hence the use of - // 4 * IntPtr.Size to get to the next header. - int httpHeaderStructSize = 4 * IntPtr.Size; - - for (int i = 0; i < nativeHeaderCount; i++) - { - int offset = httpHeaderStructSize * i; - IntPtr currentHttpHeaderPtr = IntPtr.Add(nativeHeadersPtr, offset); - MarshalAndVerifyHttpHeader(currentHttpHeaderPtr, ref httpHeaders[i]); - } - - Contract.Assert(httpHeaders != null); - Contract.Assert(httpHeaders.Length == nativeHeaderCount); - - return httpHeaders; - } - - public static bool Succeeded(int hr) - { - return (hr >= 0); - } - - private static void ThrowOnError(int errorCode) - { - if (Succeeded(errorCode)) - { - return; - } - - throw new WebSocketException(errorCode); - } - - private static void ThrowIfSessionHandleClosed(WebSocketBase webSocket) - { - if (webSocket.SessionHandle.IsClosed) - { - throw new WebSocketException(WebSocketError.InvalidState, - SR.GetString(SR.net_WebSockets_InvalidState_ClosedOrAborted, webSocket.GetType().FullName, webSocket.State)); - } - } - - private static WebSocketException ConvertObjectDisposedException(WebSocketBase webSocket, ObjectDisposedException innerException) - { - return new WebSocketException(WebSocketError.InvalidState, - SR.GetString(SR.net_WebSockets_InvalidState_ClosedOrAborted, webSocket.GetType().FullName, webSocket.State), - innerException); - } - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketCreateClientHandle", ExactSpelling = true)] - private static extern int WebSocketCreateClientHandle_Raw( - [In]Property[] properties, - [In] uint propertyCount, - [Out] out SafeWebSocketHandle webSocketHandle); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketBeginClientHandshake", ExactSpelling = true)] - private static extern int WebSocketBeginClientHandshake_Raw( - [In] SafeHandle webSocketHandle, - [In] IntPtr subProtocols, - [In] uint subProtocolCount, - [In] IntPtr extensions, - [In] uint extensionCount, - [In] HttpHeader[] initialHeaders, - [In] uint initialHeaderCount, - [Out] out IntPtr additionalHeadersPtr, - [Out] out uint additionalHeaderCount); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketEndClientHandshake", ExactSpelling = true)] - private static extern int WebSocketEndClientHandshake_Raw([In] SafeHandle webSocketHandle, - [In] HttpHeader[] responseHeaders, - [In] uint responseHeaderCount, - [In, Out] IntPtr selectedExtensions, - [In] IntPtr selectedExtensionCount, - [In] IntPtr selectedSubProtocol); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketBeginServerHandshake", ExactSpelling = true)] - private static extern int WebSocketBeginServerHandshake_Raw( - [In] SafeHandle webSocketHandle, - [In] IntPtr subProtocol, - [In] IntPtr extensions, - [In] uint extensionCount, - [In] HttpHeader[] requestHeaders, - [In] uint requestHeaderCount, - [Out] out IntPtr responseHeadersPtr, - [Out] out uint responseHeaderCount); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketEndServerHandshake", ExactSpelling = true)] - private static extern int WebSocketEndServerHandshake_Raw([In] SafeHandle webSocketHandle); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketCreateServerHandle", ExactSpelling = true)] - private static extern int WebSocketCreateServerHandle_Raw( - [In]Property[] properties, - [In] uint propertyCount, - [Out] out SafeWebSocketHandle webSocketHandle); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketAbortHandle", ExactSpelling = true)] - private static extern void WebSocketAbortHandle_Raw( - [In] SafeHandle webSocketHandle); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketDeleteHandle", ExactSpelling = true)] - private static extern void WebSocketDeleteHandle_Raw( - [In] IntPtr webSocketHandle); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketSend", ExactSpelling = true)] - private static extern int WebSocketSend_Raw( - [In] SafeHandle webSocketHandle, - [In] BufferType bufferType, - [In] ref Buffer buffer, - [In] IntPtr applicationContext); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketSend", ExactSpelling = true)] - private static extern int WebSocketSendWithoutBody_Raw( - [In] SafeHandle webSocketHandle, - [In] BufferType bufferType, - [In] IntPtr buffer, - [In] IntPtr applicationContext); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketReceive", ExactSpelling = true)] - private static extern int WebSocketReceive_Raw( - [In] SafeHandle webSocketHandle, - [In] IntPtr buffers, - [In] IntPtr applicationContext); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketGetAction", ExactSpelling = true)] - private static extern int WebSocketGetAction_Raw( - [In] SafeHandle webSocketHandle, - [In] ActionQueue actionQueue, - [In, Out] Buffer[] dataBuffers, - [In, Out] ref uint dataBufferCount, - [Out] out Action action, - [Out] out BufferType bufferType, - [Out] out IntPtr applicationContext, - [Out] out IntPtr actionContext); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketCompleteAction", ExactSpelling = true)] - private static extern void WebSocketCompleteAction_Raw( - [In] SafeHandle webSocketHandle, - [In] IntPtr actionContext, - [In] uint bytesTransferred); - - [DllImport(WEBSOCKET, EntryPoint = "WebSocketGetGlobalProperty", ExactSpelling = true)] - private static extern int WebSocketGetGlobalProperty_Raw( - [In] PropertyType property, - [In, Out] ref uint value, - [In, Out] ref uint size); - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/Properties/AssemblyInfo.cs b/src/Microsoft.Net.WebSockets.Server/Properties/AssemblyInfo.cs deleted file mode 100644 index 79eeb571ef..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Reflection; -using System.Resources; - -[assembly: AssemblyMetadata("Serviceable", "True")] -[assembly: NeutralResourcesLanguage("en-us")] -[assembly: AssemblyCompany("Microsoft Corporation.")] -[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] -[assembly: AssemblyProduct("Microsoft ASP.NET Core")] diff --git a/src/Microsoft.Net.WebSockets.Server/ServerWebSocket.cs b/src/Microsoft.Net.WebSockets.Server/ServerWebSocket.cs deleted file mode 100644 index ebf8542848..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/ServerWebSocket.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; -using System.IO; -using System.Runtime.InteropServices; - -namespace Microsoft.Net.WebSockets -{ - internal sealed class ServerWebSocket : WebSocketBase - { - private readonly SafeHandle _sessionHandle; - private readonly UnsafeNativeMethods.WebSocketProtocolComponent.Property[] _properties; - - public ServerWebSocket(Stream innerStream, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) - : base(innerStream, subProtocol, keepAliveInterval, - WebSocketBuffer.CreateServerBuffer(internalBuffer, receiveBufferSize)) - { - _properties = this.InternalBuffer.CreateProperties(false); - _sessionHandle = this.CreateWebSocketHandle(); - - if (_sessionHandle == null || _sessionHandle.IsInvalid) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - StartKeepAliveTimer(); - } - - internal override SafeHandle SessionHandle - { - get - { - Contract.Assert(_sessionHandle != null, "'m_SessionHandle MUST NOT be NULL."); - return _sessionHandle; - } - } - - [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", - Justification = "No arbitrary data controlled by PT code is leaking into native code.")] - private SafeHandle CreateWebSocketHandle() - { - Contract.Assert(_properties != null, "'m_Properties' MUST NOT be NULL."); - SafeWebSocketHandle sessionHandle; - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketCreateServerHandle(_properties, - _properties.Length, - out sessionHandle); - Contract.Assert(sessionHandle != null, "'sessionHandle MUST NOT be NULL."); - - return sessionHandle; - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketBase.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketBase.cs deleted file mode 100644 index 1526acd056..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/WebSocketBase.cs +++ /dev/null @@ -1,2499 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; -using System.Globalization; -using System.IO; -using System.Net.WebSockets; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Net.WebSockets -{ - internal abstract class WebSocketBase : WebSocket, IDisposable - { - // private static volatile bool s_LoggingEnabled; - - private readonly OutstandingOperationHelper _closeOutstandingOperationHelper; - private readonly OutstandingOperationHelper _closeOutputOutstandingOperationHelper; - private readonly OutstandingOperationHelper _receiveOutstandingOperationHelper; - private readonly OutstandingOperationHelper _sendOutstandingOperationHelper; - private readonly Stream _innerStream; - private readonly IWebSocketStream _innerStreamAsWebSocketStream; - private readonly string _subProtocol; - - // We are not calling Dispose method on this object in Cleanup method to avoid a race condition while one thread is calling disposing on - // this object and another one is still using WaitAsync. According to Dev11 358715, this should be fine as long as we are not accessing the - // AvailableWaitHandle on this SemaphoreSlim object. - private readonly SemaphoreSlim _sendFrameThrottle; - // locking m_ThisLock protects access to - // - State - // - m_CloseStack - // - m_CloseAsyncStartedReceive - // - m_CloseReceivedTaskCompletionSource - // - m_CloseNetworkConnectionTask - private readonly object _thisLock; - private readonly WebSocketBuffer _internalBuffer; - private readonly KeepAliveTracker _keepAliveTracker; - -#if DEBUG - private volatile string _closeStack; -#endif - - private volatile bool _cleanedUp; - private volatile TaskCompletionSource _closeReceivedTaskCompletionSource; - private volatile Task _closeOutputTask; - private volatile bool _isDisposed; - private volatile Task _closeNetworkConnectionTask; - private volatile bool _closeAsyncStartedReceive; - private volatile WebSocketState _state; - private volatile Task _keepAliveTask; - private volatile WebSocketOperation.ReceiveOperation _receiveOperation; - private volatile WebSocketOperation.SendOperation _sendOperation; - private volatile WebSocketOperation.SendOperation _keepAliveOperation; - private volatile WebSocketOperation.CloseOutputOperation _closeOutputOperation; - private WebSocketCloseStatus? _closeStatus; - private string _closeStatusDescription; - private int _receiveState; - private Exception _pendingException; - - protected WebSocketBase(Stream innerStream, - string subProtocol, - TimeSpan keepAliveInterval, - WebSocketBuffer internalBuffer) - { - Contract.Assert(internalBuffer != null, "'internalBuffer' MUST NOT be NULL."); - WebSocketHelpers.ValidateInnerStream(innerStream); - WebSocketHelpers.ValidateOptions(subProtocol, internalBuffer.ReceiveBufferSize, - internalBuffer.SendBufferSize, keepAliveInterval); - - // s_LoggingEnabled = Logging.On && Logging.WebSockets.Switch.ShouldTrace(TraceEventType.Critical); - string parameters = string.Empty; - /* - if (s_LoggingEnabled) - { - parameters = string.Format(CultureInfo.InvariantCulture, - "ReceiveBufferSize: {0}, SendBufferSize: {1}, Protocols: {2}, KeepAliveInterval: {3}, innerStream: {4}, internalBuffer: {5}", - internalBuffer.ReceiveBufferSize, - internalBuffer.SendBufferSize, - subProtocol, - keepAliveInterval, - Logging.GetObjectLogHash(innerStream), - Logging.GetObjectLogHash(internalBuffer)); - - Logging.Enter(Logging.WebSockets, this, Methods.Initialize, parameters); - } - */ - _thisLock = new object(); - - try - { - _innerStream = innerStream; - _internalBuffer = internalBuffer; - /*if (s_LoggingEnabled) - { - Logging.Associate(Logging.WebSockets, this, m_InnerStream); - Logging.Associate(Logging.WebSockets, this, m_InternalBuffer); - }*/ - - _closeOutstandingOperationHelper = new OutstandingOperationHelper(); - _closeOutputOutstandingOperationHelper = new OutstandingOperationHelper(); - _receiveOutstandingOperationHelper = new OutstandingOperationHelper(); - _sendOutstandingOperationHelper = new OutstandingOperationHelper(); - _state = WebSocketState.Open; - _subProtocol = subProtocol; - _sendFrameThrottle = new SemaphoreSlim(1, 1); - _closeStatus = null; - _closeStatusDescription = null; - _innerStreamAsWebSocketStream = innerStream as IWebSocketStream; - if (_innerStreamAsWebSocketStream != null) - { - _innerStreamAsWebSocketStream.SwitchToOpaqueMode(this); - } - _keepAliveTracker = KeepAliveTracker.Create(keepAliveInterval); - } - finally - { - /*if (s_LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.Initialize, parameters); - }*/ - } - } - /* - internal static bool LoggingEnabled - { - get - { - return s_LoggingEnabled; - } - } - */ - public override WebSocketState State - { - get - { - Contract.Assert(_state != WebSocketState.None, "'m_State' MUST NOT be 'WebSocketState.None'."); - return _state; - } - } - - public override string SubProtocol - { - get - { - return _subProtocol; - } - } - - public override WebSocketCloseStatus? CloseStatus - { - get - { - return _closeStatus; - } - } - - public override string CloseStatusDescription - { - get - { - return _closeStatusDescription; - } - } - - internal WebSocketBuffer InternalBuffer - { - get - { - Contract.Assert(_internalBuffer != null, "'m_InternalBuffer' MUST NOT be NULL."); - return _internalBuffer; - } - } - - protected void StartKeepAliveTimer() - { - _keepAliveTracker.StartTimer(this); - } - - // locking SessionHandle protects access to - // - WSPC (WebSocketProtocolComponent) - // - m_KeepAliveTask - // - m_CloseOutputTask - // - m_LastSendActivity - internal abstract SafeHandle SessionHandle { get; } - - // MultiThreading: ThreadSafe; At most one outstanding call to ReceiveAsync is allowed - public override Task ReceiveAsync(ArraySegment buffer, - CancellationToken cancellationToken) - { - WebSocketHelpers.ValidateArraySegment(buffer, "buffer"); - return ReceiveAsyncCore(buffer, cancellationToken); - } - - private async Task ReceiveAsyncCore(ArraySegment buffer, - CancellationToken cancellationToken) - { - Contract.Assert(buffer.Array != null); - /* - if (s_LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, this, Methods.ReceiveAsync, string.Empty); - } - */ - WebSocketReceiveResult receiveResult; - try - { - ThrowIfPendingException(); - ThrowIfDisposed(); - WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent); - - bool ownsCancellationTokenSource = false; - CancellationToken linkedCancellationToken = CancellationToken.None; - try - { - ownsCancellationTokenSource = _receiveOutstandingOperationHelper.TryStartOperation(cancellationToken, - out linkedCancellationToken); - if (!ownsCancellationTokenSource) - { - lock (_thisLock) - { - if (_closeAsyncStartedReceive) - { - throw new InvalidOperationException( - SR.GetString(SR.net_WebSockets_ReceiveAsyncDisallowedAfterCloseAsync, Methods.CloseAsync, Methods.CloseOutputAsync)); - } - - throw new InvalidOperationException( - SR.GetString(SR.net_Websockets_AlreadyOneOutstandingOperation, Methods.ReceiveAsync)); - } - } - - EnsureReceiveOperation(); - receiveResult = await _receiveOperation.Process(buffer, linkedCancellationToken).SuppressContextFlow(); - /* - if (s_LoggingEnabled && receiveResult.Count > 0) - { - Logging.Dump(Logging.WebSockets, - this, - Methods.ReceiveAsync, - buffer.Array, - buffer.Offset, - receiveResult.Count); - }*/ - } - catch (Exception exception) - { - bool aborted = linkedCancellationToken.IsCancellationRequested; - Abort(); - ThrowIfConvertibleException(Methods.ReceiveAsync, exception, cancellationToken, aborted); - throw; - } - finally - { - _receiveOutstandingOperationHelper.CompleteOperation(ownsCancellationTokenSource); - } - } - finally - {/* - if (s_LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.ReceiveAsync, string.Empty); - }*/ - } - - return receiveResult; - } - - // MultiThreading: ThreadSafe; At most one outstanding call to SendAsync is allowed - public override Task SendAsync(ArraySegment buffer, - WebSocketMessageType messageType, - bool endOfMessage, - CancellationToken cancellationToken) - { - if (messageType != WebSocketMessageType.Binary && - messageType != WebSocketMessageType.Text) - { - throw new ArgumentException(SR.GetString(SR.net_WebSockets_Argument_InvalidMessageType, - messageType, - Methods.SendAsync, - WebSocketMessageType.Binary, - WebSocketMessageType.Text, - Methods.CloseOutputAsync), - "messageType"); - } - - WebSocketHelpers.ValidateArraySegment(buffer, "buffer"); - - return SendAsyncCore(buffer, messageType, endOfMessage, cancellationToken); - } - - private async Task SendAsyncCore(ArraySegment buffer, - WebSocketMessageType messageType, - bool endOfMessage, - CancellationToken cancellationToken) - { - Contract.Assert(messageType == WebSocketMessageType.Binary || messageType == WebSocketMessageType.Text, - "'messageType' MUST be either 'WebSocketMessageType.Binary' or 'WebSocketMessageType.Text'."); - Contract.Assert(buffer.Array != null); - - string inputParameter = string.Empty; - /*if (s_LoggingEnabled) - { - inputParameter = string.Format(CultureInfo.InvariantCulture, - "messageType: {0}, endOfMessage: {1}", - messageType, - endOfMessage); - Logging.Enter(Logging.WebSockets, this, Methods.SendAsync, inputParameter); - }*/ - - try - { - ThrowIfPendingException(); - ThrowIfDisposed(); - WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); - bool ownsCancellationTokenSource = false; - CancellationToken linkedCancellationToken = CancellationToken.None; - - try - { - while (!(ownsCancellationTokenSource = _sendOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken))) - { - Task keepAliveTask; - - lock (SessionHandle) - { - keepAliveTask = _keepAliveTask; - - if (keepAliveTask == null) - { - // Check whether there is still another outstanding send operation - // Potentially the keepAlive operation has completed before this thread - // was able to enter the SessionHandle-lock. - _sendOutstandingOperationHelper.CompleteOperation(ownsCancellationTokenSource); - if (ownsCancellationTokenSource = _sendOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken)) - { - break; - } - else - { - throw new InvalidOperationException( - SR.GetString(SR.net_Websockets_AlreadyOneOutstandingOperation, Methods.SendAsync)); - } - } - } - - await keepAliveTask.SuppressContextFlow(); - ThrowIfPendingException(); - - _sendOutstandingOperationHelper.CompleteOperation(ownsCancellationTokenSource); - } - /* - if (s_LoggingEnabled && buffer.Count > 0) - { - Logging.Dump(Logging.WebSockets, - this, - Methods.SendAsync, - buffer.Array, - buffer.Offset, - buffer.Count); - }*/ - - int position = buffer.Offset; - - EnsureSendOperation(); - _sendOperation.BufferType = GetBufferType(messageType, endOfMessage); - await _sendOperation.Process(buffer, linkedCancellationToken).SuppressContextFlow(); - } - catch (Exception exception) - { - bool aborted = linkedCancellationToken.IsCancellationRequested; - Abort(); - ThrowIfConvertibleException(Methods.SendAsync, exception, cancellationToken, aborted); - throw; - } - finally - { - _sendOutstandingOperationHelper.CompleteOperation(ownsCancellationTokenSource); - } - } - finally - { - /*if (s_LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.SendAsync, inputParameter); - }*/ - } - } - - private async Task SendFrameAsync(IList> sendBuffers, CancellationToken cancellationToken) - { - bool sendFrameLockTaken = false; - try - { - await _sendFrameThrottle.WaitAsync(cancellationToken).SuppressContextFlow(); - sendFrameLockTaken = true; - - if (sendBuffers.Count > 1 && - _innerStreamAsWebSocketStream != null && - _innerStreamAsWebSocketStream.SupportsMultipleWrite) - { - await _innerStreamAsWebSocketStream.MultipleWriteAsync(sendBuffers, - cancellationToken).SuppressContextFlow(); - } - else - { - foreach (ArraySegment buffer in sendBuffers) - { - await _innerStream.WriteAsync(buffer.Array, - buffer.Offset, - buffer.Count, - cancellationToken).SuppressContextFlow(); - } - } - } - catch (ObjectDisposedException objectDisposedException) - { - throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, objectDisposedException); - } - catch (NotSupportedException notSupportedException) - { - throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, notSupportedException); - } - finally - { - if (sendFrameLockTaken) - { - _sendFrameThrottle.Release(); - } - } - } - - // MultiThreading: ThreadSafe; No-op if already in a terminal state - public override void Abort() - { - /*if (s_LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, this, Methods.Abort, string.Empty); - }*/ - - bool thisLockTaken = false; - bool sessionHandleLockTaken = false; - try - { - if (WebSocketHelpers.IsStateTerminal(State)) - { - return; - } - - TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - if (WebSocketHelpers.IsStateTerminal(State)) - { - return; - } - - _state = WebSocketState.Aborted; - -#if DEBUG && NET451 - string stackTrace = new StackTrace().ToString(); - if (_closeStack == null) - { - _closeStack = stackTrace; - } - /* - if (s_LoggingEnabled) - { - string message = string.Format(CultureInfo.InvariantCulture, "Stack: {0}", stackTrace); - Logging.PrintWarning(Logging.WebSockets, this, Methods.Abort, message); - }*/ -#endif - - // Abort any outstanding IO operations. - if (SessionHandle != null && !SessionHandle.IsClosed && !SessionHandle.IsInvalid) - { - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketAbortHandle(SessionHandle); - } - - _receiveOutstandingOperationHelper.CancelIO(); - _sendOutstandingOperationHelper.CancelIO(); - _closeOutputOutstandingOperationHelper.CancelIO(); - _closeOutstandingOperationHelper.CancelIO(); - if (_innerStreamAsWebSocketStream != null) - { - _innerStreamAsWebSocketStream.Abort(); - } - CleanUp(); - } - finally - { - ReleaseLocks(ref thisLockTaken, ref sessionHandleLockTaken); - /*if (s_LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.Abort, string.Empty); - }*/ - } - } - - // MultiThreading: ThreadSafe; No-op if already in a terminal state - public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, - string statusDescription, - CancellationToken cancellationToken) - { - WebSocketHelpers.ValidateCloseStatus(closeStatus, statusDescription); - - return CloseOutputAsyncCore(closeStatus, statusDescription, cancellationToken); - } - - private async Task CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, - string statusDescription, - CancellationToken cancellationToken) - { - string inputParameter = string.Empty; - /*if (s_LoggingEnabled) - { - inputParameter = string.Format(CultureInfo.InvariantCulture, - "closeStatus: {0}, statusDescription: {1}", - closeStatus, - statusDescription); - Logging.Enter(Logging.WebSockets, this, Methods.CloseOutputAsync, inputParameter); - }*/ - - try - { - ThrowIfPendingException(); - if (WebSocketHelpers.IsStateTerminal(State)) - { - return; - } - ThrowIfDisposed(); - - bool thisLockTaken = false; - bool sessionHandleLockTaken = false; - bool needToCompleteSendOperation = false; - bool ownsCloseOutputCancellationTokenSource = false; - bool ownsSendCancellationTokenSource = false; - CancellationToken linkedCancellationToken = CancellationToken.None; - try - { - TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - ThrowIfPendingException(); - ThrowIfDisposed(); - - if (WebSocketHelpers.IsStateTerminal(State)) - { - return; - } - - WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); - ownsCloseOutputCancellationTokenSource = _closeOutputOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken); - if (!ownsCloseOutputCancellationTokenSource) - { - Task closeOutputTask = _closeOutputTask; - - if (closeOutputTask != null) - { - ReleaseLocks(ref thisLockTaken, ref sessionHandleLockTaken); - await closeOutputTask.SuppressContextFlow(); - TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - } - } - else - { - needToCompleteSendOperation = true; - while (!(ownsSendCancellationTokenSource = - _sendOutstandingOperationHelper.TryStartOperation(cancellationToken, - out linkedCancellationToken))) - { - if (_keepAliveTask != null) - { - Task keepAliveTask = _keepAliveTask; - - ReleaseLocks(ref thisLockTaken, ref sessionHandleLockTaken); - await keepAliveTask.SuppressContextFlow(); - TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - - ThrowIfPendingException(); - } - else - { - throw new InvalidOperationException( - SR.GetString(SR.net_Websockets_AlreadyOneOutstandingOperation, Methods.SendAsync)); - } - - _sendOutstandingOperationHelper.CompleteOperation(ownsSendCancellationTokenSource); - } - - EnsureCloseOutputOperation(); - _closeOutputOperation.CloseStatus = closeStatus; - _closeOutputOperation.CloseReason = statusDescription; - _closeOutputTask = _closeOutputOperation.Process(null, linkedCancellationToken); - - ReleaseLocks(ref thisLockTaken, ref sessionHandleLockTaken); - await _closeOutputTask.SuppressContextFlow(); - TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - - if (OnCloseOutputCompleted()) - { - bool callCompleteOnCloseCompleted = false; - - try - { - callCompleteOnCloseCompleted = await StartOnCloseCompleted( - thisLockTaken, sessionHandleLockTaken, linkedCancellationToken).SuppressContextFlow(); - } - catch (Exception) - { - // If an exception is thrown we know that the locks have been released, - // because we enforce IWebSocketStream.CloseNetworkConnectionAsync to yield - ResetFlagsAndTakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - throw; - } - - if (callCompleteOnCloseCompleted) - { - ResetFlagsAndTakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - FinishOnCloseCompleted(); - } - } - } - } - catch (Exception exception) - { - bool aborted = linkedCancellationToken.IsCancellationRequested; - Abort(); - ThrowIfConvertibleException(Methods.CloseOutputAsync, exception, cancellationToken, aborted); - throw; - } - finally - { - _closeOutputOutstandingOperationHelper.CompleteOperation(ownsCloseOutputCancellationTokenSource); - - if (needToCompleteSendOperation) - { - _sendOutstandingOperationHelper.CompleteOperation(ownsSendCancellationTokenSource); - } - - _closeOutputTask = null; - ReleaseLocks(ref thisLockTaken, ref sessionHandleLockTaken); - } - } - finally - { - /*if (s_LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.CloseOutputAsync, inputParameter); - }*/ - } - } - - // returns TRUE if the caller should also call StartOnCloseCompleted - private bool OnCloseOutputCompleted() - { - if (WebSocketHelpers.IsStateTerminal(State)) - { - return false; - } - - switch (State) - { - case WebSocketState.Open: - _state = WebSocketState.CloseSent; - return false; - case WebSocketState.CloseReceived: - return true; - default: - return false; - } - } - - // MultiThreading: This method has to be called under a m_ThisLock-lock - // ReturnValue: This method returns true only if CompleteOnCloseCompleted needs to be called - // If this method returns true all locks were released before starting the IO operation - // and they have to be retaken by the caller before calling CompleteOnCloseCompleted - // Exception handling: If an exception is thrown from await StartOnCloseCompleted - // it always means the locks have been released already - so the caller has to retake the - // locks in the catch-block. - // This is ensured by enforcing a Task.Yield for IWebSocketStream.CloseNetowrkConnectionAsync - private async Task StartOnCloseCompleted(bool thisLockTakenSnapshot, - bool sessionHandleLockTakenSnapshot, - CancellationToken cancellationToken) - { - Contract.Assert(thisLockTakenSnapshot, "'thisLockTakenSnapshot' MUST be 'true' at this point."); - - if (WebSocketHelpers.IsStateTerminal(_state)) - { - return false; - } - - _state = WebSocketState.Closed; - -#if DEBUG && NET451 - if (_closeStack == null) - { - _closeStack = new StackTrace().ToString(); - } -#endif - - if (_innerStreamAsWebSocketStream != null) - { - bool thisLockTaken = thisLockTakenSnapshot; - bool sessionHandleLockTaken = sessionHandleLockTakenSnapshot; - - try - { - if (_closeNetworkConnectionTask == null) - { - _closeNetworkConnectionTask = - _innerStreamAsWebSocketStream.CloseNetworkConnectionAsync(cancellationToken); - } - - if (thisLockTaken && sessionHandleLockTaken) - { - ReleaseLocks(ref thisLockTaken, ref sessionHandleLockTaken); - } - else if (thisLockTaken) - { - ReleaseLock(_thisLock, ref thisLockTaken); - } - - await _closeNetworkConnectionTask.SuppressContextFlow(); - } - catch (Exception closeNetworkConnectionTaskException) - { - if (!CanHandleExceptionDuringClose(closeNetworkConnectionTaskException)) - { - ThrowIfConvertibleException(Methods.StartOnCloseCompleted, - closeNetworkConnectionTaskException, - cancellationToken, - cancellationToken.IsCancellationRequested); - throw; - } - } - } - - return true; - } - - // MultiThreading: This method has to be called under a thisLock-lock - private void FinishOnCloseCompleted() - { - CleanUp(); - } - - // MultiThreading: ThreadSafe; No-op if already in a terminal state - public override Task CloseAsync(WebSocketCloseStatus closeStatus, - string statusDescription, - CancellationToken cancellationToken) - { - WebSocketHelpers.ValidateCloseStatus(closeStatus, statusDescription); - return CloseAsyncCore(closeStatus, statusDescription, cancellationToken); - } - - private async Task CloseAsyncCore(WebSocketCloseStatus closeStatus, - string statusDescription, - CancellationToken cancellationToken) - { - string inputParameter = string.Empty; - /*if (s_LoggingEnabled) - { - inputParameter = string.Format(CultureInfo.InvariantCulture, - "closeStatus: {0}, statusDescription: {1}", - closeStatus, - statusDescription); - Logging.Enter(Logging.WebSockets, this, Methods.CloseAsync, inputParameter); - }*/ - - try - { - ThrowIfPendingException(); - if (WebSocketHelpers.IsStateTerminal(State)) - { - return; - } - ThrowIfDisposed(); - - bool lockTaken = false; - Monitor.Enter(_thisLock, ref lockTaken); - bool ownsCloseCancellationTokenSource = false; - CancellationToken linkedCancellationToken = CancellationToken.None; - try - { - ThrowIfPendingException(); - if (WebSocketHelpers.IsStateTerminal(State)) - { - return; - } - ThrowIfDisposed(); - WebSocketHelpers.ThrowOnInvalidState(State, - WebSocketState.Open, WebSocketState.CloseReceived, WebSocketState.CloseSent); - - Task closeOutputTask; - ownsCloseCancellationTokenSource = _closeOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken); - if (ownsCloseCancellationTokenSource) - { - closeOutputTask = _closeOutputTask; - if (closeOutputTask == null && State != WebSocketState.CloseSent) - { - if (_closeReceivedTaskCompletionSource == null) - { - _closeReceivedTaskCompletionSource = new TaskCompletionSource(); - } - - closeOutputTask = CloseOutputAsync(closeStatus, - statusDescription, - linkedCancellationToken); - } - } - else - { - Contract.Assert(_closeReceivedTaskCompletionSource != null, - "'m_CloseReceivedTaskCompletionSource' MUST NOT be NULL."); - closeOutputTask = _closeReceivedTaskCompletionSource.Task; - } - - if (closeOutputTask != null) - { - ReleaseLock(_thisLock, ref lockTaken); - try - { - await closeOutputTask.SuppressContextFlow(); - } - catch (Exception closeOutputError) - { - Monitor.Enter(_thisLock, ref lockTaken); - - if (!CanHandleExceptionDuringClose(closeOutputError)) - { - ThrowIfConvertibleException(Methods.CloseOutputAsync, - closeOutputError, - cancellationToken, - linkedCancellationToken.IsCancellationRequested); - throw; - } - } - - // When closeOutputTask != null and an exception thrown from await closeOutputTask is handled, - // the lock will be taken in the catch-block. So the logic here avoids taking the lock twice. - if (!lockTaken) - { - Monitor.Enter(_thisLock, ref lockTaken); - } - } - - if (OnCloseOutputCompleted()) - { - bool callCompleteOnCloseCompleted = false; - - try - { - // linkedCancellationToken can be CancellationToken.None if ownsCloseCancellationTokenSource==false - // This is still ok because OnCloseOutputCompleted won't start any IO operation in this case - callCompleteOnCloseCompleted = await StartOnCloseCompleted( - lockTaken, false, linkedCancellationToken).SuppressContextFlow(); - } - catch (Exception) - { - // If an exception is thrown we know that the locks have been released, - // because we enforce IWebSocketStream.CloseNetworkConnectionAsync to yield - ResetFlagAndTakeLock(_thisLock, ref lockTaken); - throw; - } - - if (callCompleteOnCloseCompleted) - { - ResetFlagAndTakeLock(_thisLock, ref lockTaken); - FinishOnCloseCompleted(); - } - } - - if (WebSocketHelpers.IsStateTerminal(State)) - { - return; - } - - linkedCancellationToken = CancellationToken.None; - - bool ownsReceiveCancellationTokenSource = _receiveOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken); - if (ownsReceiveCancellationTokenSource) - { - _closeAsyncStartedReceive = true; - ArraySegment closeMessageBuffer = - new ArraySegment(new byte[WebSocketBuffer.MinReceiveBufferSize]); - EnsureReceiveOperation(); - Task receiveAsyncTask = _receiveOperation.Process(closeMessageBuffer, - linkedCancellationToken); - ReleaseLock(_thisLock, ref lockTaken); - - WebSocketReceiveResult receiveResult = null; - try - { - receiveResult = await receiveAsyncTask.SuppressContextFlow(); - } - catch (Exception receiveException) - { - Monitor.Enter(_thisLock, ref lockTaken); - - if (!CanHandleExceptionDuringClose(receiveException)) - { - ThrowIfConvertibleException(Methods.CloseAsync, - receiveException, - cancellationToken, - linkedCancellationToken.IsCancellationRequested); - throw; - } - } - - // receiveResult is NEVER NULL if WebSocketBase.ReceiveOperation.Process completes successfully - // - but in the close code path we handle some exception if another thread was able to tranistion - // the state into Closed successfully. In this case receiveResult can be NULL and it is safe to - // skip the statements in the if-block. - if (receiveResult != null) - { - /*if (s_LoggingEnabled && receiveResult.Count > 0) - { - Logging.Dump(Logging.WebSockets, - this, - Methods.ReceiveAsync, - closeMessageBuffer.Array, - closeMessageBuffer.Offset, - receiveResult.Count); - }*/ - - if (receiveResult.MessageType != WebSocketMessageType.Close) - { - throw new WebSocketException(WebSocketError.InvalidMessageType, - SR.GetString(SR.net_WebSockets_InvalidMessageType, - typeof(WebSocket).Name + "." + Methods.CloseAsync, - typeof(WebSocket).Name + "." + Methods.CloseOutputAsync, - receiveResult.MessageType)); - } - } - } - else - { - _receiveOutstandingOperationHelper.CompleteOperation(ownsReceiveCancellationTokenSource); - ReleaseLock(_thisLock, ref lockTaken); - await _closeReceivedTaskCompletionSource.Task.SuppressContextFlow(); - } - - // When ownsReceiveCancellationTokenSource is true and an exception is thrown, the lock will be taken. - // So this logic here is to avoid taking the lock twice. - if (!lockTaken) - { - Monitor.Enter(_thisLock, ref lockTaken); - } - - if (!WebSocketHelpers.IsStateTerminal(State)) - { - bool ownsSendCancellationSource = false; - try - { - // We know that the CloseFrame has been sent at this point. So no Send-operation is allowed anymore and we - // can hijack the m_SendOutstandingOperationHelper to create a linkedCancellationToken - ownsSendCancellationSource = _sendOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken); - Contract.Assert(ownsSendCancellationSource, "'ownsSendCancellationSource' MUST be 'true' at this point."); - - bool callCompleteOnCloseCompleted = false; - - try - { - // linkedCancellationToken can be CancellationToken.None if ownsCloseCancellationTokenSource==false - // This is still ok because OnCloseOutputCompleted won't start any IO operation in this case - callCompleteOnCloseCompleted = await StartOnCloseCompleted( - lockTaken, false, linkedCancellationToken).SuppressContextFlow(); - } - catch (Exception) - { - // If an exception is thrown we know that the locks have been released, - // because we enforce IWebSocketStream.CloseNetworkConnectionAsync to yield - ResetFlagAndTakeLock(_thisLock, ref lockTaken); - throw; - } - - if (callCompleteOnCloseCompleted) - { - ResetFlagAndTakeLock(_thisLock, ref lockTaken); - FinishOnCloseCompleted(); - } - } - finally - { - _sendOutstandingOperationHelper.CompleteOperation(ownsSendCancellationSource); - } - } - } - catch (Exception exception) - { - bool aborted = linkedCancellationToken.IsCancellationRequested; - Abort(); - ThrowIfConvertibleException(Methods.CloseAsync, exception, cancellationToken, aborted); - throw; - } - finally - { - _closeOutstandingOperationHelper.CompleteOperation(ownsCloseCancellationTokenSource); - ReleaseLock(_thisLock, ref lockTaken); - } - } - finally - { - /*if (s_LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, this, Methods.CloseAsync, inputParameter); - }*/ - } - } - - // MultiThreading: ThreadSafe; No-op if already in a terminal state - [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_sendFrameThrottle", - Justification = "SemaphoreSlim.Dispose is not threadsafe and can cause NullRef exceptions on other threads." + - "Also according to the CLR Dev11#358715) there is no need to dispose SemaphoreSlim if the ManualResetEvent " + - "is not used.")] - public override void Dispose() - { - if (_isDisposed) - { - return; - } - - bool thisLockTaken = false; - bool sessionHandleLockTaken = false; - - try - { - TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - - if (_isDisposed) - { - return; - } - - if (!WebSocketHelpers.IsStateTerminal(State)) - { - Abort(); - } - else - { - CleanUp(); - } - - _isDisposed = true; - } - finally - { - ReleaseLocks(ref thisLockTaken, ref sessionHandleLockTaken); - } - } - - private void ResetFlagAndTakeLock(object lockObject, ref bool thisLockTaken) - { - Contract.Assert(lockObject != null, "'lockObject' MUST NOT be NULL."); - thisLockTaken = false; - Monitor.Enter(lockObject, ref thisLockTaken); - } - - private void ResetFlagsAndTakeLocks(ref bool thisLockTaken, ref bool sessionHandleLockTaken) - { - thisLockTaken = false; - sessionHandleLockTaken = false; - TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - } - - private void TakeLocks(ref bool thisLockTaken, ref bool sessionHandleLockTaken) - { - Contract.Assert(_thisLock != null, "'m_ThisLock' MUST NOT be NULL."); - Contract.Assert(SessionHandle != null, "'SessionHandle' MUST NOT be NULL."); - - Monitor.Enter(SessionHandle, ref sessionHandleLockTaken); - Monitor.Enter(_thisLock, ref thisLockTaken); - } - - private void ReleaseLocks(ref bool thisLockTaken, ref bool sessionHandleLockTaken) - { - Contract.Assert(_thisLock != null, "'m_ThisLock' MUST NOT be NULL."); - Contract.Assert(SessionHandle != null, "'SessionHandle' MUST NOT be NULL."); - - if (thisLockTaken || sessionHandleLockTaken) - { -#if !NETSTANDARD1_3 - RuntimeHelpers.PrepareConstrainedRegions(); -#endif - try - { - } - finally - { - if (thisLockTaken) - { - Monitor.Exit(_thisLock); - thisLockTaken = false; - } - - if (sessionHandleLockTaken) - { - Monitor.Exit(SessionHandle); - sessionHandleLockTaken = false; - } - } - } - } - - private void EnsureReceiveOperation() - { - if (_receiveOperation == null) - { - lock (_thisLock) - { - if (_receiveOperation == null) - { - _receiveOperation = new WebSocketOperation.ReceiveOperation(this); - } - } - } - } - - private void EnsureSendOperation() - { - if (_sendOperation == null) - { - lock (_thisLock) - { - if (_sendOperation == null) - { - _sendOperation = new WebSocketOperation.SendOperation(this); - } - } - } - } - - private void EnsureKeepAliveOperation() - { - if (_keepAliveOperation == null) - { - lock (_thisLock) - { - if (_keepAliveOperation == null) - { - WebSocketOperation.SendOperation keepAliveOperation = new WebSocketOperation.SendOperation(this); - keepAliveOperation.BufferType = UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UnsolicitedPong; - _keepAliveOperation = keepAliveOperation; - } - } - } - } - - private void EnsureCloseOutputOperation() - { - if (_closeOutputOperation == null) - { - lock (_thisLock) - { - if (_closeOutputOperation == null) - { - _closeOutputOperation = new WebSocketOperation.CloseOutputOperation(this); - } - } - } - } - - private static void ReleaseLock(object lockObject, ref bool lockTaken) - { - Contract.Assert(lockObject != null, "'lockObject' MUST NOT be NULL."); - if (lockTaken) - { -#if !NETSTANDARD1_3 - RuntimeHelpers.PrepareConstrainedRegions(); -#endif - try - { - } - finally - { - Monitor.Exit(lockObject); - lockTaken = false; - } - } - } - - private static UnsafeNativeMethods.WebSocketProtocolComponent.BufferType GetBufferType(WebSocketMessageType messageType, - bool endOfMessage) - { - Contract.Assert(messageType == WebSocketMessageType.Binary || messageType == WebSocketMessageType.Text, - string.Format(CultureInfo.InvariantCulture, - "The value of 'messageType' ({0}) is invalid. Valid message types: '{1}, {2}'", - messageType, - WebSocketMessageType.Binary, - WebSocketMessageType.Text)); - - if (messageType == WebSocketMessageType.Text) - { - if (endOfMessage) - { - return UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Message; - } - - return UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Fragment; - } - else - { - if (endOfMessage) - { - return UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryMessage; - } - - return UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryFragment; - } - } - - private static WebSocketMessageType GetMessageType(UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType) - { - switch (bufferType) - { - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.Close: - return WebSocketMessageType.Close; - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryFragment: - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryMessage: - return WebSocketMessageType.Binary; - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Fragment: - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Message: - return WebSocketMessageType.Text; - default: - // This indicates a contract violation of the websocket protocol component, - // because we currently don't support any WebSocket extensions and would - // not accept a Websocket handshake requesting extensions - Contract.Assert(false, - string.Format(CultureInfo.InvariantCulture, - "The value of 'bufferType' ({0}) is invalid. Valid buffer types: {1}, {2}, {3}, {4}, {5}.", - bufferType, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.Close, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryFragment, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryMessage, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Fragment, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Message)); - - throw new WebSocketException(WebSocketError.NativeError, - SR.GetString(SR.net_WebSockets_InvalidBufferType, - bufferType, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.Close, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryFragment, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryMessage, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Fragment, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Message)); - } - } - - internal void ValidateNativeBuffers(UnsafeNativeMethods.WebSocketProtocolComponent.Action action, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType, - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer[] dataBuffers, - uint dataBufferCount) - { - _internalBuffer.ValidateNativeBuffers(action, bufferType, dataBuffers, dataBufferCount); - } - - internal void ThrowIfClosedOrAborted() - { - if (State == WebSocketState.Closed || State == WebSocketState.Aborted) - { - throw new WebSocketException(WebSocketError.InvalidState, - SR.GetString(SR.net_WebSockets_InvalidState_ClosedOrAborted, GetType().FullName, State)); - } - } - - private void ThrowIfAborted(bool aborted, Exception innerException) - { - if (aborted) - { - throw new WebSocketException(WebSocketError.InvalidState, - SR.GetString(SR.net_WebSockets_InvalidState_ClosedOrAborted, GetType().FullName, WebSocketState.Aborted), - innerException); - } - } - - private bool CanHandleExceptionDuringClose(Exception error) - { - Contract.Assert(error != null, "'error' MUST NOT be NULL."); - - if (State != WebSocketState.Closed) - { - return false; - } - - return error is OperationCanceledException || - error is WebSocketException || - // error is SocketException || - // error is HttpListenerException || - error is IOException; - } - - // We only want to throw an OperationCanceledException if the CancellationToken passed - // down from the caller is canceled - not when Abort is called on another thread and - // the linkedCancellationToken is canceled. - private void ThrowIfConvertibleException(string methodName, - Exception exception, - CancellationToken cancellationToken, - bool aborted) - { - Contract.Assert(exception != null, "'exception' MUST NOT be NULL."); - /* - if (s_LoggingEnabled && !string.IsNullOrEmpty(methodName)) - { - Logging.Exception(Logging.WebSockets, this, methodName, exception); - }*/ - - OperationCanceledException operationCanceledException = exception as OperationCanceledException; - if (operationCanceledException != null) - { - if (cancellationToken.IsCancellationRequested || - !aborted) - { - return; - } - ThrowIfAborted(aborted, exception); - } - - WebSocketException convertedException = exception as WebSocketException; - if (convertedException != null) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfAborted(aborted, convertedException); - return; - } - /* - SocketException socketException = exception as SocketException; - if (socketException != null) - { - convertedException = new WebSocketException(socketException.NativeErrorCode, socketException); - } - HttpListenerException httpListenerException = exception as HttpListenerException; - if (httpListenerException != null) - { - convertedException = new WebSocketException(httpListenerException.ErrorCode, httpListenerException); - } - - IOException ioException = exception as IOException; - if (ioException != null) - { - socketException = exception.InnerException as SocketException; - if (socketException != null) - { - convertedException = new WebSocketException(socketException.NativeErrorCode, ioException); - } - } -*/ - if (convertedException != null) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfAborted(aborted, convertedException); - throw convertedException; - } - - AggregateException aggregateException = exception as AggregateException; - if (aggregateException != null) - { - // Collapse possibly nested graph into a flat list. - // Empty inner exception list is unlikely but possible via public api. - ReadOnlyCollection unwrappedExceptions = aggregateException.Flatten().InnerExceptions; - if (unwrappedExceptions.Count == 0) - { - return; - } - - foreach (Exception unwrappedException in unwrappedExceptions) - { - ThrowIfConvertibleException(null, unwrappedException, cancellationToken, aborted); - } - } - } - - private void CleanUp() - { - // Multithreading: This method is always called under the m_ThisLock lock - if (_cleanedUp) - { - return; - } - - _cleanedUp = true; - - if (SessionHandle != null) - { - SessionHandle.Dispose(); - } - - if (_internalBuffer != null) - { - _internalBuffer.Dispose(this.State); - } - - if (_receiveOutstandingOperationHelper != null) - { - _receiveOutstandingOperationHelper.Dispose(); - } - - if (_sendOutstandingOperationHelper != null) - { - _sendOutstandingOperationHelper.Dispose(); - } - - if (_closeOutputOutstandingOperationHelper != null) - { - _closeOutputOutstandingOperationHelper.Dispose(); - } - - if (_closeOutstandingOperationHelper != null) - { - _closeOutstandingOperationHelper.Dispose(); - } - - if (_innerStream != null) - { - try - { - _innerStream.Dispose(); - } - catch (ObjectDisposedException) - { - } - catch (IOException) - { - } - /*catch (SocketException) - { - }*/ - catch (Exception) - { - } - } - - _keepAliveTracker.Dispose(); - } - - private void OnBackgroundTaskException(Exception exception) - { - if (Interlocked.CompareExchange(ref _pendingException, exception, null) == null) - { - /*if (s_LoggingEnabled) - { - Logging.Exception(Logging.WebSockets, this, Methods.Fault, exception); - }*/ - Abort(); - } - } - - private void ThrowIfPendingException() - { - Exception pendingException = Interlocked.Exchange(ref _pendingException, null); - if (pendingException != null) - { - throw new WebSocketException(WebSocketError.Faulted, pendingException); - } - } - - private void ThrowIfDisposed() - { - if (_isDisposed) - { - throw new ObjectDisposedException(GetType().FullName); - } - } - - private void UpdateReceiveState(int newReceiveState, int expectedReceiveState) - { - int receiveState; - if ((receiveState = Interlocked.Exchange(ref _receiveState, newReceiveState)) != expectedReceiveState) - { - Contract.Assert(false, - string.Format(CultureInfo.InvariantCulture, - "'m_ReceiveState' had an invalid value '{0}'. The expected value was '{1}'.", - receiveState, - expectedReceiveState)); - } - } - - private bool StartOnCloseReceived(ref bool thisLockTaken) - { - ThrowIfDisposed(); - - if (WebSocketHelpers.IsStateTerminal(State) || State == WebSocketState.CloseReceived) - { - return false; - } - - Monitor.Enter(_thisLock, ref thisLockTaken); - if (WebSocketHelpers.IsStateTerminal(State) || State == WebSocketState.CloseReceived) - { - return false; - } - - if (State == WebSocketState.Open) - { - _state = WebSocketState.CloseReceived; - - if (_closeReceivedTaskCompletionSource == null) - { - _closeReceivedTaskCompletionSource = new TaskCompletionSource(); - } - - return false; - } - - return true; - } - - private void FinishOnCloseReceived(WebSocketCloseStatus closeStatus, - string closeStatusDescription) - { - if (_closeReceivedTaskCompletionSource != null) - { - _closeReceivedTaskCompletionSource.TrySetResult(null); - } - - _closeStatus = closeStatus; - _closeStatusDescription = closeStatusDescription; - /* - if (s_LoggingEnabled) - { - string parameters = string.Format(CultureInfo.InvariantCulture, - "closeStatus: {0}, closeStatusDescription: {1}, m_State: {2}", - closeStatus, closeStatusDescription, m_State); - - Logging.PrintInfo(Logging.WebSockets, this, Methods.FinishOnCloseReceived, parameters); - }*/ - } - - private async static void OnKeepAlive(object sender) - { - Contract.Assert(sender != null, "'sender' MUST NOT be NULL."); - Contract.Assert((sender as WebSocketBase) != null, "'sender as WebSocketBase' MUST NOT be NULL."); - - WebSocketBase thisPtr = sender as WebSocketBase; - bool lockTaken = false; - /* - if (s_LoggingEnabled) - { - Logging.Enter(Logging.WebSockets, thisPtr, Methods.OnKeepAlive, string.Empty); - }*/ - - CancellationToken linkedCancellationToken = CancellationToken.None; - try - { - Monitor.Enter(thisPtr.SessionHandle, ref lockTaken); - - if (thisPtr._isDisposed || - thisPtr._state != WebSocketState.Open || - thisPtr._closeOutputTask != null) - { - return; - } - - if (thisPtr._keepAliveTracker.ShouldSendKeepAlive()) - { - bool ownsCancellationTokenSource = false; - try - { - ownsCancellationTokenSource = thisPtr._sendOutstandingOperationHelper.TryStartOperation(CancellationToken.None, out linkedCancellationToken); - if (ownsCancellationTokenSource) - { - thisPtr.EnsureKeepAliveOperation(); - thisPtr._keepAliveTask = thisPtr._keepAliveOperation.Process(null, linkedCancellationToken); - ReleaseLock(thisPtr.SessionHandle, ref lockTaken); - await thisPtr._keepAliveTask.SuppressContextFlow(); - } - } - finally - { - if (!lockTaken) - { - Monitor.Enter(thisPtr.SessionHandle, ref lockTaken); - } - thisPtr._sendOutstandingOperationHelper.CompleteOperation(ownsCancellationTokenSource); - thisPtr._keepAliveTask = null; - } - - thisPtr._keepAliveTracker.ResetTimer(); - } - } - catch (Exception exception) - { - try - { - thisPtr.ThrowIfConvertibleException(Methods.OnKeepAlive, - exception, - CancellationToken.None, - linkedCancellationToken.IsCancellationRequested); - throw; - } - catch (Exception backgroundException) - { - thisPtr.OnBackgroundTaskException(backgroundException); - } - } - finally - { - ReleaseLock(thisPtr.SessionHandle, ref lockTaken); - /* - if (s_LoggingEnabled) - { - Logging.Exit(Logging.WebSockets, thisPtr, Methods.OnKeepAlive, string.Empty); - }*/ - } - } - - private abstract class WebSocketOperation - { - private readonly WebSocketBase _webSocket; - - internal WebSocketOperation(WebSocketBase webSocket) - { - Contract.Assert(webSocket != null, "'webSocket' MUST NOT be NULL."); - _webSocket = webSocket; - } - - public WebSocketReceiveResult ReceiveResult { get; protected set; } - protected abstract int BufferCount { get; } - protected abstract UnsafeNativeMethods.WebSocketProtocolComponent.ActionQueue ActionQueue { get; } - protected abstract void Initialize(ArraySegment? buffer, CancellationToken cancellationToken); - protected abstract bool ShouldContinue(CancellationToken cancellationToken); - - // Multi-Threading: This method has to be called under a SessionHandle-lock. It returns true if a - // close frame was received. Handling the received close frame might involve IO - to make the locking - // strategy easier and reduce one level in the await-hierarchy the IO is kicked off by the caller. - protected abstract bool ProcessAction_NoAction(); - - protected virtual void ProcessAction_IndicateReceiveComplete( - ArraySegment? buffer, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType, - UnsafeNativeMethods.WebSocketProtocolComponent.Action action, - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer[] dataBuffers, - uint dataBufferCount, - IntPtr actionContext) - { - throw new NotImplementedException(); - } - - protected abstract void Cleanup(); - - internal async Task Process(ArraySegment? buffer, - CancellationToken cancellationToken) - { - Contract.Assert(BufferCount >= 1 && BufferCount <= 2, "'bufferCount' MUST ONLY BE '1' or '2'."); - - bool sessionHandleLockTaken = false; - ReceiveResult = null; - try - { - Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken); - _webSocket.ThrowIfPendingException(); - Initialize(buffer, cancellationToken); - - while (ShouldContinue(cancellationToken)) - { - UnsafeNativeMethods.WebSocketProtocolComponent.Action action; - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType; - - bool completed = false; - while (!completed) - { - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer[] dataBuffers = - new UnsafeNativeMethods.WebSocketProtocolComponent.Buffer[BufferCount]; - uint dataBufferCount = (uint)BufferCount; - IntPtr actionContext; - - _webSocket.ThrowIfDisposed(); - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketGetAction(_webSocket, - ActionQueue, - dataBuffers, - ref dataBufferCount, - out action, - out bufferType, - out actionContext); - - switch (action) - { - case UnsafeNativeMethods.WebSocketProtocolComponent.Action.NoAction: - if (ProcessAction_NoAction()) - { - // A close frame was received - - Contract.Assert(ReceiveResult.Count == 0, "'receiveResult.Count' MUST be 0."); - Contract.Assert(ReceiveResult.CloseStatus != null, "'receiveResult.CloseStatus' MUST NOT be NULL for message type 'Close'."); - bool thisLockTaken = false; - try - { - if (_webSocket.StartOnCloseReceived(ref thisLockTaken)) - { - // If StartOnCloseReceived returns true the WebSocket close handshake has been completed - // so there is no need to retake the SessionHandle-lock. - // m_ThisLock lock is guaranteed to be taken by StartOnCloseReceived when returning true - ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken); - bool callCompleteOnCloseCompleted = false; - - try - { - callCompleteOnCloseCompleted = await _webSocket.StartOnCloseCompleted( - thisLockTaken, sessionHandleLockTaken, cancellationToken).SuppressContextFlow(); - } - catch (Exception) - { - // If an exception is thrown we know that the locks have been released, - // because we enforce IWebSocketStream.CloseNetworkConnectionAsync to yield - _webSocket.ResetFlagAndTakeLock(_webSocket._thisLock, ref thisLockTaken); - throw; - } - - if (callCompleteOnCloseCompleted) - { - _webSocket.ResetFlagAndTakeLock(_webSocket._thisLock, ref thisLockTaken); - _webSocket.FinishOnCloseCompleted(); - } - } - _webSocket.FinishOnCloseReceived(ReceiveResult.CloseStatus.Value, ReceiveResult.CloseStatusDescription); - } - finally - { - if (thisLockTaken) - { - ReleaseLock(_webSocket._thisLock, ref thisLockTaken); - } - } - } - completed = true; - break; - case UnsafeNativeMethods.WebSocketProtocolComponent.Action.IndicateReceiveComplete: - ProcessAction_IndicateReceiveComplete(buffer, - bufferType, - action, - dataBuffers, - dataBufferCount, - actionContext); - break; - case UnsafeNativeMethods.WebSocketProtocolComponent.Action.ReceiveFromNetwork: - int count = 0; - try - { - ArraySegment payload = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType); - - ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken); - WebSocketHelpers.ThrowIfConnectionAborted(_webSocket._innerStream, true); - try - { - Task readTask = _webSocket._innerStream.ReadAsync(payload.Array, - payload.Offset, - payload.Count, - cancellationToken); - count = await readTask.SuppressContextFlow(); - _webSocket._keepAliveTracker.OnDataReceived(); - } - catch (ObjectDisposedException objectDisposedException) - { - throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, objectDisposedException); - } - catch (NotSupportedException notSupportedException) - { - throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, notSupportedException); - } - Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken); - _webSocket.ThrowIfPendingException(); - // If the client unexpectedly closed the socket we throw an exception as we didn't get any close message - if (count == 0) - { - throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely); - } - } - finally - { - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket, - actionContext, - count); - } - break; - case UnsafeNativeMethods.WebSocketProtocolComponent.Action.IndicateSendComplete: - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket, actionContext, 0); - ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken); - await _webSocket._innerStream.FlushAsync().SuppressContextFlow(); - Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken); - break; - case UnsafeNativeMethods.WebSocketProtocolComponent.Action.SendToNetwork: - int bytesSent = 0; - try - { - if (_webSocket.State != WebSocketState.CloseSent || - (bufferType != UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.PingPong && - bufferType != UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UnsolicitedPong)) - { - if (dataBufferCount == 0) - { - break; - } - - List> sendBuffers = new List>((int)dataBufferCount); - int sendBufferSize = 0; - ArraySegment framingBuffer = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType); - sendBuffers.Add(framingBuffer); - sendBufferSize += framingBuffer.Count; - - // There can be at most 2 dataBuffers - // - one for the framing header and one for the payload - if (dataBufferCount == 2) - { - ArraySegment payload = _webSocket._internalBuffer.ConvertPinnedSendPayloadFromNative(dataBuffers[1], bufferType); - sendBuffers.Add(payload); - sendBufferSize += payload.Count; - } - - ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken); - WebSocketHelpers.ThrowIfConnectionAborted(_webSocket._innerStream, false); - await _webSocket.SendFrameAsync(sendBuffers, cancellationToken).SuppressContextFlow(); - Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken); - _webSocket.ThrowIfPendingException(); - bytesSent += sendBufferSize; - _webSocket._keepAliveTracker.OnDataSent(); - } - } - finally - { - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket, - actionContext, - bytesSent); - } - - break; - default: - string assertMessage = string.Format(CultureInfo.InvariantCulture, - "Invalid action '{0}' returned from WebSocketGetAction.", - action); - Contract.Assert(false, assertMessage); - throw new InvalidOperationException(); - } - } - } - } - finally - { - Cleanup(); - ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken); - } - - return ReceiveResult; - } - - public class ReceiveOperation : WebSocketOperation - { - private int _receiveState; - private bool _pongReceived; - private bool _receiveCompleted; - - public ReceiveOperation(WebSocketBase webSocket) - : base(webSocket) - { - } - - protected override UnsafeNativeMethods.WebSocketProtocolComponent.ActionQueue ActionQueue - { - get { return UnsafeNativeMethods.WebSocketProtocolComponent.ActionQueue.Receive; } - } - - protected override int BufferCount - { - get { return 1; } - } - - protected override void Initialize(ArraySegment? buffer, CancellationToken cancellationToken) - { - Contract.Assert(buffer != null, "'buffer' MUST NOT be NULL."); - _pongReceived = false; - _receiveCompleted = false; - _webSocket.ThrowIfDisposed(); - - int originalReceiveState = Interlocked.CompareExchange(ref _webSocket._receiveState, - ReceiveState.Application, ReceiveState.Idle); - - switch (originalReceiveState) - { - case ReceiveState.Idle: - _receiveState = ReceiveState.Application; - break; - case ReceiveState.Application: - Contract.Assert(false, "'originalReceiveState' MUST NEVER be ReceiveState.Application at this point."); - break; - case ReceiveState.PayloadAvailable: - WebSocketReceiveResult receiveResult; - if (!_webSocket._internalBuffer.ReceiveFromBufferedPayload(buffer.Value, out receiveResult)) - { - _webSocket.UpdateReceiveState(ReceiveState.Idle, ReceiveState.PayloadAvailable); - } - ReceiveResult = receiveResult; - _receiveCompleted = true; - break; - default: - Contract.Assert(false, - string.Format(CultureInfo.InvariantCulture, "Invalid ReceiveState '{0}'.", originalReceiveState)); - break; - } - } - - protected override void Cleanup() - { - } - - protected override bool ShouldContinue(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (_receiveCompleted) - { - return false; - } - - _webSocket.ThrowIfDisposed(); - _webSocket.ThrowIfPendingException(); - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketReceive(_webSocket); - - return true; - } - - protected override bool ProcessAction_NoAction() - { - if (_pongReceived) - { - _receiveCompleted = false; - _pongReceived = false; - return false; - } - - Contract.Assert(ReceiveResult != null, - "'ReceiveResult' MUST NOT be NULL."); - _receiveCompleted = true; - - if (ReceiveResult.MessageType == WebSocketMessageType.Close) - { - return true; - } - - return false; - } - - protected override void ProcessAction_IndicateReceiveComplete( - ArraySegment? buffer, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType, - UnsafeNativeMethods.WebSocketProtocolComponent.Action action, - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer[] dataBuffers, - uint dataBufferCount, - IntPtr actionContext) - { - Contract.Assert(buffer != null, "'buffer MUST NOT be NULL."); - - int bytesTransferred = 0; - _pongReceived = false; - - if (bufferType == UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.PingPong) - { - // ignoring received pong frame - _pongReceived = true; - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket, - actionContext, - bytesTransferred); - return; - } - - WebSocketReceiveResult receiveResult; - try - { - ArraySegment payload; - WebSocketMessageType messageType = GetMessageType(bufferType); - int newReceiveState = ReceiveState.Idle; - - if (bufferType == UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.Close) - { - payload = WebSocketHelpers.EmptyPayload; - string reason; - WebSocketCloseStatus closeStatus; - _webSocket._internalBuffer.ConvertCloseBuffer(action, dataBuffers[0], out closeStatus, out reason); - - receiveResult = new WebSocketReceiveResult(bytesTransferred, - messageType, true, closeStatus, reason); - } - else - { - payload = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType); - - bool endOfMessage = bufferType == - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryMessage || - bufferType == UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Message || - bufferType == UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.Close; - - if (payload.Count > buffer.Value.Count) - { - _webSocket._internalBuffer.BufferPayload(payload, buffer.Value.Count, messageType, endOfMessage); - newReceiveState = ReceiveState.PayloadAvailable; - endOfMessage = false; - } - - bytesTransferred = Math.Min(payload.Count, (int)buffer.Value.Count); - if (bytesTransferred > 0) - { - Buffer.BlockCopy(payload.Array, - payload.Offset, - buffer.Value.Array, - buffer.Value.Offset, - bytesTransferred); - } - - receiveResult = new WebSocketReceiveResult(bytesTransferred, messageType, endOfMessage); - } - - _webSocket.UpdateReceiveState(newReceiveState, _receiveState); - } - finally - { - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket, - actionContext, - bytesTransferred); - } - - ReceiveResult = receiveResult; - } - } - - public class SendOperation : WebSocketOperation - { - private bool _completed; - protected bool _bufferHasBeenPinned; - - public SendOperation(WebSocketBase webSocket) - : base(webSocket) - { - } - - protected override UnsafeNativeMethods.WebSocketProtocolComponent.ActionQueue ActionQueue - { - get { return UnsafeNativeMethods.WebSocketProtocolComponent.ActionQueue.Send; } - } - - protected override int BufferCount - { - get { return 2; } - } - - protected virtual UnsafeNativeMethods.WebSocketProtocolComponent.Buffer? CreateBuffer(ArraySegment? buffer) - { - if (buffer == null) - { - return null; - } - - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer payloadBuffer; - payloadBuffer = new UnsafeNativeMethods.WebSocketProtocolComponent.Buffer(); - _webSocket._internalBuffer.PinSendBuffer(buffer.Value, out _bufferHasBeenPinned); - payloadBuffer.Data.BufferData = _webSocket._internalBuffer.ConvertPinnedSendPayloadToNative(buffer.Value); - payloadBuffer.Data.BufferLength = (uint)buffer.Value.Count; - return payloadBuffer; - } - - protected override bool ProcessAction_NoAction() - { - _completed = true; - return false; - } - - protected override void Cleanup() - { - if (_bufferHasBeenPinned) - { - _bufferHasBeenPinned = false; - _webSocket._internalBuffer.ReleasePinnedSendBuffer(); - } - } - - internal UnsafeNativeMethods.WebSocketProtocolComponent.BufferType BufferType { get; set; } - - protected override void Initialize(ArraySegment? buffer, - CancellationToken cancellationToken) - { - Contract.Assert(!_bufferHasBeenPinned, "'m_BufferHasBeenPinned' MUST NOT be pinned at this point."); - _webSocket.ThrowIfDisposed(); - _webSocket.ThrowIfPendingException(); - _completed = false; - - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer? payloadBuffer = CreateBuffer(buffer); - if (payloadBuffer != null) - { - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketSend(_webSocket, BufferType, payloadBuffer.Value); - } - else - { - UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketSendWithoutBody(_webSocket, BufferType); - } - } - - protected override bool ShouldContinue(CancellationToken cancellationToken) - { - Contract.Assert(ReceiveResult == null, "'ReceiveResult' MUST be NULL."); - if (_completed) - { - return false; - } - - cancellationToken.ThrowIfCancellationRequested(); - return true; - } - } - - public class CloseOutputOperation : SendOperation - { - public CloseOutputOperation(WebSocketBase webSocket) - : base(webSocket) - { - BufferType = UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.Close; - } - - internal WebSocketCloseStatus CloseStatus { get; set; } - internal string CloseReason { get; set; } - - protected override UnsafeNativeMethods.WebSocketProtocolComponent.Buffer? CreateBuffer(ArraySegment? buffer) - { - Contract.Assert(buffer == null, "'buffer' MUST BE NULL."); - _webSocket.ThrowIfDisposed(); - _webSocket.ThrowIfPendingException(); - - if (CloseStatus == WebSocketCloseStatus.Empty) - { - return null; - } - - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer payloadBuffer = new UnsafeNativeMethods.WebSocketProtocolComponent.Buffer(); - if (CloseReason != null) - { - byte[] blob = UTF8Encoding.UTF8.GetBytes(CloseReason); - Contract.Assert(blob.Length <= WebSocketHelpers.MaxControlFramePayloadLength, - "The close reason is too long."); - ArraySegment closeBuffer = new ArraySegment(blob, 0, Math.Min(WebSocketHelpers.MaxControlFramePayloadLength, blob.Length)); - _webSocket._internalBuffer.PinSendBuffer(closeBuffer, out _bufferHasBeenPinned); - payloadBuffer.CloseStatus.ReasonData = _webSocket._internalBuffer.ConvertPinnedSendPayloadToNative(closeBuffer); - payloadBuffer.CloseStatus.ReasonLength = (uint)closeBuffer.Count; - } - - payloadBuffer.CloseStatus.CloseStatus = (ushort)CloseStatus; - return payloadBuffer; - } - } - } - - private abstract class KeepAliveTracker : IDisposable - { - // Multi-Threading: only one thread at a time is allowed to call OnDataReceived or OnDataSent - // - but both methods can be called from different threads at the same time. - public abstract void OnDataReceived(); - public abstract void OnDataSent(); - public abstract void Dispose(); - public abstract void StartTimer(WebSocketBase webSocket); - public abstract void ResetTimer(); - public abstract bool ShouldSendKeepAlive(); - - public static KeepAliveTracker Create(TimeSpan keepAliveInterval) - { - if ((int)keepAliveInterval.TotalMilliseconds > 0) - { - return new DefaultKeepAliveTracker(keepAliveInterval); - } - - return new DisabledKeepAliveTracker(); - } - - private class DisabledKeepAliveTracker : KeepAliveTracker - { - public override void OnDataReceived() - { - } - - public override void OnDataSent() - { - } - - public override void ResetTimer() - { - } - - public override void StartTimer(WebSocketBase webSocket) - { - } - - public override bool ShouldSendKeepAlive() - { - return false; - } - - public override void Dispose() - { - } - } - - private class DefaultKeepAliveTracker : KeepAliveTracker - { - private static readonly TimerCallback _keepAliveTimerElapsedCallback = new TimerCallback(OnKeepAlive); - private readonly TimeSpan _keepAliveInterval; - private readonly Stopwatch _lastSendActivity; - private readonly Stopwatch _lastReceiveActivity; - private Timer _keepAliveTimer; - - public DefaultKeepAliveTracker(TimeSpan keepAliveInterval) - { - _keepAliveInterval = keepAliveInterval; - _lastSendActivity = new Stopwatch(); - _lastReceiveActivity = new Stopwatch(); - } - - public override void OnDataReceived() - { - _lastReceiveActivity.Restart(); - } - - public override void OnDataSent() - { - _lastSendActivity.Restart(); - } - - public override void ResetTimer() - { - ResetTimer((int)_keepAliveInterval.TotalMilliseconds); - } - - public override void StartTimer(WebSocketBase webSocket) - { - Contract.Assert(webSocket != null, "'webSocket' MUST NOT be NULL."); - Contract.Assert(webSocket._keepAliveTracker != null, - "'webSocket.m_KeepAliveTracker' MUST NOT be NULL at this point."); - int keepAliveIntervalMilliseconds = (int)_keepAliveInterval.TotalMilliseconds; - Contract.Assert(keepAliveIntervalMilliseconds > 0, "'keepAliveIntervalMilliseconds' MUST be POSITIVE."); -#if NETSTANDARD1_3 - _keepAliveTimer = new Timer(_keepAliveTimerElapsedCallback, webSocket, keepAliveIntervalMilliseconds, Timeout.Infinite); -#else - if (ExecutionContext.IsFlowSuppressed()) - { - _keepAliveTimer = new Timer(_keepAliveTimerElapsedCallback, webSocket, keepAliveIntervalMilliseconds, Timeout.Infinite); - } - else - { - using (ExecutionContext.SuppressFlow()) - { - _keepAliveTimer = new Timer(_keepAliveTimerElapsedCallback, webSocket, keepAliveIntervalMilliseconds, Timeout.Infinite); - } - } -#endif - } - - public override bool ShouldSendKeepAlive() - { - TimeSpan idleTime = GetIdleTime(); - if (idleTime >= _keepAliveInterval) - { - return true; - } - - ResetTimer((int)(_keepAliveInterval - idleTime).TotalMilliseconds); - return false; - } - - public override void Dispose() - { - _keepAliveTimer.Dispose(); - } - - private void ResetTimer(int dueInMilliseconds) - { - _keepAliveTimer.Change(dueInMilliseconds, Timeout.Infinite); - } - - private TimeSpan GetIdleTime() - { - TimeSpan sinceLastSendActivity = GetTimeElapsed(_lastSendActivity); - TimeSpan sinceLastReceiveActivity = GetTimeElapsed(_lastReceiveActivity); - - if (sinceLastReceiveActivity < sinceLastSendActivity) - { - return sinceLastReceiveActivity; - } - - return sinceLastSendActivity; - } - - private TimeSpan GetTimeElapsed(Stopwatch watch) - { - if (watch.IsRunning) - { - return watch.Elapsed; - } - - return _keepAliveInterval; - } - } - } - - private class OutstandingOperationHelper : IDisposable - { - private volatile int _operationsOutstanding; - private volatile CancellationTokenSource _cancellationTokenSource; - private volatile bool _isDisposed; - private readonly object _thisLock = new object(); - - public bool TryStartOperation(CancellationToken userCancellationToken, out CancellationToken linkedCancellationToken) - { - linkedCancellationToken = CancellationToken.None; - ThrowIfDisposed(); - - lock (_thisLock) - { - int operationsOutstanding = ++_operationsOutstanding; - - if (operationsOutstanding == 1) - { - linkedCancellationToken = CreateLinkedCancellationToken(userCancellationToken); - return true; - } - - Contract.Assert(operationsOutstanding >= 1, "'operationsOutstanding' must never be smaller than 1."); - return false; - } - } - - public void CompleteOperation(bool ownsCancellationTokenSource) - { - if (_isDisposed) - { - // no-op if the WebSocket is already aborted - return; - } - - CancellationTokenSource snapshot = null; - - lock (_thisLock) - { - --_operationsOutstanding; - Contract.Assert(_operationsOutstanding >= 0, "'m_OperationsOutstanding' must never be smaller than 0."); - - if (ownsCancellationTokenSource) - { - snapshot = _cancellationTokenSource; - _cancellationTokenSource = null; - } - } - - if (snapshot != null) - { - snapshot.Dispose(); - } - } - - // Has to be called under m_ThisLock lock - private CancellationToken CreateLinkedCancellationToken(CancellationToken cancellationToken) - { - CancellationTokenSource linkedCancellationTokenSource; - - if (cancellationToken == CancellationToken.None) - { - linkedCancellationTokenSource = new CancellationTokenSource(); - } - else - { - linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, - new CancellationTokenSource().Token); - } - - Contract.Assert(_cancellationTokenSource == null, "'m_CancellationTokenSource' MUST be NULL."); - _cancellationTokenSource = linkedCancellationTokenSource; - - return linkedCancellationTokenSource.Token; - } - - public void CancelIO() - { - CancellationTokenSource cancellationTokenSourceSnapshot = null; - - lock (_thisLock) - { - if (_operationsOutstanding == 0) - { - return; - } - - cancellationTokenSourceSnapshot = _cancellationTokenSource; - } - - if (cancellationTokenSourceSnapshot != null) - { - try - { - cancellationTokenSourceSnapshot.Cancel(); - } - catch (ObjectDisposedException) - { - // Simply ignore this exception - There is apparently a rare race condition - // where the cancellationTokensource is disposed before the Cancel method call completed. - } - } - } - - public void Dispose() - { - if (_isDisposed) - { - return; - } - - CancellationTokenSource snapshot = null; - lock (_thisLock) - { - if (_isDisposed) - { - return; - } - - _isDisposed = true; - snapshot = _cancellationTokenSource; - _cancellationTokenSource = null; - } - - if (snapshot != null) - { - snapshot.Dispose(); - } - } - - private void ThrowIfDisposed() - { - if (_isDisposed) - { - throw new ObjectDisposedException(GetType().FullName); - } - } - } - - internal interface IWebSocketStream - { - // Switching to opaque mode will change the behavior to use the knowledge that the WebSocketBase class - // is pinning all payloads already and that we will have at most one outstanding send and receive at any - // given time. This allows us to avoid creation of OverlappedData and pinning for each operation. - - void SwitchToOpaqueMode(WebSocketBase webSocket); - void Abort(); - bool SupportsMultipleWrite { get; } - Task MultipleWriteAsync(IList> buffers, CancellationToken cancellationToken); - - // Any implementation has to guarantee that no exception is thrown synchronously - // for example by enforcing a Task.Yield at the beginning of the method - // This is necessary to enforce an API contract (for WebSocketBase.StartOnCloseCompleted) that ensures - // that all locks have been released whenever an exception is thrown from it. - Task CloseNetworkConnectionAsync(CancellationToken cancellationToken); - } - - private static class ReceiveState - { - internal const int SendOperation = -1; - internal const int Idle = 0; - internal const int Application = 1; - internal const int PayloadAvailable = 2; - } - - internal static class Methods - { - internal const string ReceiveAsync = "ReceiveAsync"; - internal const string SendAsync = "SendAsync"; - internal const string CloseAsync = "CloseAsync"; - internal const string CloseOutputAsync = "CloseOutputAsync"; - internal const string Abort = "Abort"; - internal const string Initialize = "Initialize"; - internal const string Fault = "Fault"; - internal const string StartOnCloseCompleted = "StartOnCloseCompleted"; - internal const string FinishOnCloseReceived = "FinishOnCloseReceived"; - internal const string OnKeepAlive = "OnKeepAlive"; - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketBuffer.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketBuffer.cs deleted file mode 100644 index 1c87e73334..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/WebSocketBuffer.cs +++ /dev/null @@ -1,711 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.Globalization; -using System.Net.WebSockets; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; - -namespace Microsoft.Net.WebSockets -{ - // This class helps to abstract the internal WebSocket buffer, which is used to interact with the native WebSocket - // protocol component (WSPC). It helps to shield the details of the layout and the involved pointer arithmetic. - // The internal WebSocket buffer also contains a segment, which is used by the WebSocketBase class to buffer - // payload (parsed by WSPC already) for the application, if the application requested fewer bytes than the - // WSPC returned. The internal buffer is pinned for the whole lifetime if this class. - // LAYOUT: - // | Native buffer | PayloadReceiveBuffer | PropertyBuffer | - // | RBS + SBS + 144 | RBS | PBS | - // | Only WSPC may modify | Only WebSocketBase may modify | - // - // *RBS = ReceiveBufferSize, *SBS = SendBufferSize - // *PBS = PropertyBufferSize (32-bit: 16, 64 bit: 20 bytes) - public class WebSocketBuffer : IDisposable - { - private const int NativeOverheadBufferSize = 144; - public const int MinSendBufferSize = 16; - internal const int MinReceiveBufferSize = 256; - internal const int MaxBufferSize = 64 * 1024; - private static readonly int SizeOfUInt = Marshal.SizeOf(); - private static readonly int SizeOfBool = Marshal.SizeOf(); - private static readonly int PropertyBufferSize = (2 * SizeOfUInt) + SizeOfBool + IntPtr.Size; - - private readonly int _ReceiveBufferSize; - - // Indicates the range of the pinned byte[] that can be used by the WSPC (nativeBuffer + pinnedSendBuffer) - private readonly long _StartAddress; - private readonly long _EndAddress; - private readonly GCHandle _GCHandle; - private readonly ArraySegment _InternalBuffer; - private readonly ArraySegment _NativeBuffer; - private readonly ArraySegment _PayloadBuffer; - private readonly ArraySegment _PropertyBuffer; - private readonly int _SendBufferSize; - private volatile int _PayloadOffset; - private WebSocketReceiveResult _BufferedPayloadReceiveResult; - private long _PinnedSendBufferStartAddress; - private long _PinnedSendBufferEndAddress; - private ArraySegment _PinnedSendBuffer; - private GCHandle _PinnedSendBufferHandle; - private int _StateWhenDisposing = int.MinValue; - private int _SendBufferState; - - private WebSocketBuffer(ArraySegment internalBuffer, int receiveBufferSize, int sendBufferSize) - { - Contract.Assert(internalBuffer.Array != null, "'internalBuffer' MUST NOT be NULL."); - Contract.Assert(receiveBufferSize >= MinReceiveBufferSize, - "'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Contract.Assert(sendBufferSize >= MinSendBufferSize, - "'sendBufferSize' MUST be at least " + MinSendBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Contract.Assert(receiveBufferSize <= MaxBufferSize, - "'receiveBufferSize' MUST NOT exceed " + MaxBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Contract.Assert(sendBufferSize <= MaxBufferSize, - "'sendBufferSize' MUST NOT exceed " + MaxBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - - _ReceiveBufferSize = receiveBufferSize; - _SendBufferSize = sendBufferSize; - _InternalBuffer = internalBuffer; - _GCHandle = GCHandle.Alloc(internalBuffer.Array, GCHandleType.Pinned); - // Size of the internal buffer owned exclusively by the WSPC. - int nativeBufferSize = _ReceiveBufferSize + _SendBufferSize + NativeOverheadBufferSize; - _StartAddress = Marshal.UnsafeAddrOfPinnedArrayElement(internalBuffer.Array, internalBuffer.Offset).ToInt64(); - _EndAddress = _StartAddress + nativeBufferSize; - _NativeBuffer = new ArraySegment(internalBuffer.Array, internalBuffer.Offset, nativeBufferSize); - _PayloadBuffer = new ArraySegment(internalBuffer.Array, - _NativeBuffer.Offset + _NativeBuffer.Count, - _ReceiveBufferSize); - _PropertyBuffer = new ArraySegment(internalBuffer.Array, - _PayloadBuffer.Offset + _PayloadBuffer.Count, - PropertyBufferSize); - _SendBufferState = SendBufferState.None; - } - - public int ReceiveBufferSize - { - get { return _ReceiveBufferSize; } - } - - public int SendBufferSize - { - get { return _SendBufferSize; } - } - - internal static WebSocketBuffer CreateClientBuffer(ArraySegment internalBuffer, int receiveBufferSize, int sendBufferSize) - { - Contract.Assert(internalBuffer.Count >= GetInternalBufferSize(receiveBufferSize, sendBufferSize, false), - "Array 'internalBuffer' is TOO SMALL. Call Validate before instantiating WebSocketBuffer."); - - return new WebSocketBuffer(internalBuffer, receiveBufferSize, GetNativeSendBufferSize(sendBufferSize, false)); - } - - internal static WebSocketBuffer CreateServerBuffer(ArraySegment internalBuffer, int receiveBufferSize) - { - int sendBufferSize = GetNativeSendBufferSize(MinSendBufferSize, true); - Contract.Assert(internalBuffer.Count >= GetInternalBufferSize(receiveBufferSize, sendBufferSize, true), - "Array 'internalBuffer' is TOO SMALL. Call Validate before instantiating WebSocketBuffer."); - - return new WebSocketBuffer(internalBuffer, receiveBufferSize, sendBufferSize); - } - - public void Dispose(WebSocketState webSocketState) - { - if (Interlocked.CompareExchange(ref _StateWhenDisposing, (int)webSocketState, int.MinValue) != int.MinValue) - { - return; - } - - this.CleanUp(); - } - - public void Dispose() - { - this.Dispose(WebSocketState.None); - } - - internal UnsafeNativeMethods.WebSocketProtocolComponent.Property[] CreateProperties(bool useZeroMaskingKey) - { - ThrowIfDisposed(); - // serialize marshaled property values in the property segment of the internal buffer - IntPtr internalBufferPtr = _GCHandle.AddrOfPinnedObject(); - int offset = _PropertyBuffer.Offset; - Marshal.WriteInt32(internalBufferPtr, offset, _ReceiveBufferSize); - offset += SizeOfUInt; - Marshal.WriteInt32(internalBufferPtr, offset, _SendBufferSize); - offset += SizeOfUInt; - Marshal.WriteIntPtr(internalBufferPtr, offset, internalBufferPtr); - offset += IntPtr.Size; - Marshal.WriteInt32(internalBufferPtr, offset, useZeroMaskingKey ? (int)1 : (int)0); - - int propertyCount = useZeroMaskingKey ? 4 : 3; - UnsafeNativeMethods.WebSocketProtocolComponent.Property[] properties = - new UnsafeNativeMethods.WebSocketProtocolComponent.Property[propertyCount]; - - // Calculate the pointers to the positions of the properties within the internal buffer - offset = _PropertyBuffer.Offset; - properties[0] = new UnsafeNativeMethods.WebSocketProtocolComponent.Property() - { - Type = UnsafeNativeMethods.WebSocketProtocolComponent.PropertyType.ReceiveBufferSize, - PropertySize = (uint)SizeOfUInt, - PropertyData = IntPtr.Add(internalBufferPtr, offset) - }; - offset += SizeOfUInt; - - properties[1] = new UnsafeNativeMethods.WebSocketProtocolComponent.Property() - { - Type = UnsafeNativeMethods.WebSocketProtocolComponent.PropertyType.SendBufferSize, - PropertySize = (uint)SizeOfUInt, - PropertyData = IntPtr.Add(internalBufferPtr, offset) - }; - offset += SizeOfUInt; - - properties[2] = new UnsafeNativeMethods.WebSocketProtocolComponent.Property() - { - Type = UnsafeNativeMethods.WebSocketProtocolComponent.PropertyType.AllocatedBuffer, - PropertySize = (uint)_NativeBuffer.Count, - PropertyData = IntPtr.Add(internalBufferPtr, offset) - }; - offset += IntPtr.Size; - - if (useZeroMaskingKey) - { - properties[3] = new UnsafeNativeMethods.WebSocketProtocolComponent.Property() - { - Type = UnsafeNativeMethods.WebSocketProtocolComponent.PropertyType.DisableMasking, - PropertySize = (uint)SizeOfBool, - PropertyData = IntPtr.Add(internalBufferPtr, offset) - }; - } - - return properties; - } - - // This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation - internal void PinSendBuffer(ArraySegment payload, out bool bufferHasBeenPinned) - { - bufferHasBeenPinned = false; - WebSocketHelpers.ValidateBuffer(payload.Array, payload.Offset, payload.Count); - int previousState = Interlocked.Exchange(ref _SendBufferState, SendBufferState.SendPayloadSpecified); - - if (previousState != SendBufferState.None) - { - Contract.Assert(false, "'m_SendBufferState' MUST BE 'None' at this point."); - // Indicates a violation in the API contract that could indicate - // memory corruption because the pinned sendbuffer is shared between managed and native code - throw new AccessViolationException(); - } - _PinnedSendBuffer = payload; - _PinnedSendBufferHandle = GCHandle.Alloc(_PinnedSendBuffer.Array, GCHandleType.Pinned); - bufferHasBeenPinned = true; - _PinnedSendBufferStartAddress = - Marshal.UnsafeAddrOfPinnedArrayElement(_PinnedSendBuffer.Array, _PinnedSendBuffer.Offset).ToInt64(); - _PinnedSendBufferEndAddress = _PinnedSendBufferStartAddress + _PinnedSendBuffer.Count; - } - - // This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation - internal IntPtr ConvertPinnedSendPayloadToNative(ArraySegment payload) - { - return ConvertPinnedSendPayloadToNative(payload.Array, payload.Offset, payload.Count); - } - - // This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation - internal IntPtr ConvertPinnedSendPayloadToNative(byte[] buffer, int offset, int count) - { - if (!IsPinnedSendPayloadBuffer(buffer, offset, count)) - { - // Indicates a violation in the API contract that could indicate - // memory corruption because the pinned sendbuffer is shared between managed and native code - throw new AccessViolationException(); - } - - Contract.Assert(Marshal.UnsafeAddrOfPinnedArrayElement(_PinnedSendBuffer.Array, - _PinnedSendBuffer.Offset).ToInt64() == _PinnedSendBufferStartAddress, - "'m_PinnedSendBuffer.Array' MUST be pinned during the entire send operation."); - - return new IntPtr(_PinnedSendBufferStartAddress + offset - _PinnedSendBuffer.Offset); - } - - // This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation - internal ArraySegment ConvertPinnedSendPayloadFromNative(UnsafeNativeMethods.WebSocketProtocolComponent.Buffer buffer, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType) - { - if (!IsPinnedSendPayloadBuffer(buffer, bufferType)) - { - // Indicates a violation in the API contract that could indicate - // memory corruption because the pinned sendbuffer is shared between managed and native code - throw new AccessViolationException(); - } - - Contract.Assert(Marshal.UnsafeAddrOfPinnedArrayElement(_PinnedSendBuffer.Array, - _PinnedSendBuffer.Offset).ToInt64() == _PinnedSendBufferStartAddress, - "'m_PinnedSendBuffer.Array' MUST be pinned during the entire send operation."); - - IntPtr bufferData; - uint bufferSize; - - UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferSize); - - int internalOffset = (int)(bufferData.ToInt64() - _PinnedSendBufferStartAddress); - - return new ArraySegment(_PinnedSendBuffer.Array, _PinnedSendBuffer.Offset + internalOffset, (int)bufferSize); - } - - // This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation - private bool IsPinnedSendPayloadBuffer(byte[] buffer, int offset, int count) - { - if (_SendBufferState != SendBufferState.SendPayloadSpecified) - { - return false; - } - - return object.ReferenceEquals(buffer, _PinnedSendBuffer.Array) && - offset >= _PinnedSendBuffer.Offset && - offset + count <= _PinnedSendBuffer.Offset + _PinnedSendBuffer.Count; - } - - // This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation - private bool IsPinnedSendPayloadBuffer(UnsafeNativeMethods.WebSocketProtocolComponent.Buffer buffer, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType) - { - if (_SendBufferState != SendBufferState.SendPayloadSpecified) - { - return false; - } - - IntPtr bufferData; - uint bufferSize; - - UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferSize); - - long nativeBufferStartAddress = bufferData.ToInt64(); - long nativeBufferEndAddress = nativeBufferStartAddress + bufferSize; - - return nativeBufferStartAddress >= _PinnedSendBufferStartAddress && - nativeBufferEndAddress >= _PinnedSendBufferStartAddress && - nativeBufferStartAddress <= _PinnedSendBufferEndAddress && - nativeBufferEndAddress <= _PinnedSendBufferEndAddress; - } - - // This method is only thread safe for races between Abort and at most 1 uncompleted send operation - internal void ReleasePinnedSendBuffer() - { - int previousState = Interlocked.Exchange(ref _SendBufferState, SendBufferState.None); - - if (previousState != SendBufferState.SendPayloadSpecified) - { - return; - } - - if (_PinnedSendBufferHandle.IsAllocated) - { - _PinnedSendBufferHandle.Free(); - } - - _PinnedSendBuffer = WebSocketHelpers.EmptyPayload; - } - - internal void BufferPayload(ArraySegment payload, - int unconsumedDataOffset, - WebSocketMessageType messageType, - bool endOfMessage) - { - ThrowIfDisposed(); - int bytesBuffered = payload.Count - unconsumedDataOffset; - - Contract.Assert(_PayloadOffset == 0, - "'m_PayloadOffset' MUST be '0' at this point."); - Contract.Assert(_BufferedPayloadReceiveResult == null || _BufferedPayloadReceiveResult.Count == 0, - "'m_BufferedPayloadReceiveResult.Count' MUST be '0' at this point."); - - Buffer.BlockCopy(payload.Array, - payload.Offset + unconsumedDataOffset, - _PayloadBuffer.Array, - _PayloadBuffer.Offset, - bytesBuffered); - - _BufferedPayloadReceiveResult = - new WebSocketReceiveResult(bytesBuffered, messageType, endOfMessage); - - this.ValidateBufferedPayload(); - } - - internal bool ReceiveFromBufferedPayload(ArraySegment buffer, out WebSocketReceiveResult receiveResult) - { - ThrowIfDisposed(); - ValidateBufferedPayload(); - - int bytesTransferred = Math.Min(buffer.Count, _BufferedPayloadReceiveResult.Count); - receiveResult = WebSocketReceiveResultExtensions.DecrementAndClone(ref _BufferedPayloadReceiveResult, bytesTransferred); - - Buffer.BlockCopy(_PayloadBuffer.Array, - _PayloadBuffer.Offset + _PayloadOffset, - buffer.Array, - buffer.Offset, - bytesTransferred); - - bool morePayloadBuffered; - if (_BufferedPayloadReceiveResult.Count == 0) - { - _PayloadOffset = 0; - _BufferedPayloadReceiveResult = null; - morePayloadBuffered = false; - } - else - { - _PayloadOffset += bytesTransferred; - morePayloadBuffered = true; - this.ValidateBufferedPayload(); - } - - return morePayloadBuffered; - } - - internal ArraySegment ConvertNativeBuffer(UnsafeNativeMethods.WebSocketProtocolComponent.Action action, - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer buffer, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType) - { - ThrowIfDisposed(); - - IntPtr bufferData; - uint bufferLength; - - UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferLength); - - if (bufferData == IntPtr.Zero) - { - return WebSocketHelpers.EmptyPayload; - } - - if (this.IsNativeBuffer(bufferData, bufferLength)) - { - return new ArraySegment(_InternalBuffer.Array, - this.GetOffset(bufferData), - (int)bufferLength); - } - - Contract.Assert(false, "'buffer' MUST reference a memory segment within the pinned InternalBuffer."); - // Indicates a violation in the contract with native Websocket.dll and could indicate - // memory corruption because the internal buffer is shared between managed and native code - throw new AccessViolationException(); - } - - internal void ConvertCloseBuffer(UnsafeNativeMethods.WebSocketProtocolComponent.Action action, - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer buffer, - out WebSocketCloseStatus closeStatus, - out string reason) - { - ThrowIfDisposed(); - IntPtr bufferData; - uint bufferLength; - closeStatus = (WebSocketCloseStatus)buffer.CloseStatus.CloseStatus; - - UnwrapWebSocketBuffer(buffer, UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.Close, out bufferData, out bufferLength); - - if (bufferData == IntPtr.Zero) - { - reason = null; - } - else - { - ArraySegment reasonBlob; - if (this.IsNativeBuffer(bufferData, bufferLength)) - { - reasonBlob = new ArraySegment(_InternalBuffer.Array, - this.GetOffset(bufferData), - (int)bufferLength); - } - else - { - Contract.Assert(false, "'buffer' MUST reference a memory segment within the pinned InternalBuffer."); - // Indicates a violation in the contract with native Websocket.dll and could indicate - // memory corruption because the internal buffer is shared between managed and native code - throw new AccessViolationException(); - } - - // No need to wrap DecoderFallbackException for invalid UTF8 chacters, because - // Encoding.UTF8 will not throw but replace invalid characters instead. - reason = Encoding.UTF8.GetString(reasonBlob.Array, reasonBlob.Offset, reasonBlob.Count); - } - } - - internal void ValidateNativeBuffers(UnsafeNativeMethods.WebSocketProtocolComponent.Action action, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType, - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer[] dataBuffers, - uint dataBufferCount) - { - Contract.Assert(dataBufferCount <= (uint)int.MaxValue, - "'dataBufferCount' MUST NOT be bigger than Int32.MaxValue."); - Contract.Assert(dataBuffers != null, "'dataBuffers' MUST NOT be NULL."); - - ThrowIfDisposed(); - if (dataBufferCount > dataBuffers.Length) - { - Contract.Assert(false, "'dataBufferCount' MUST NOT be bigger than 'dataBuffers.Length'."); - // Indicates a violation in the contract with native Websocket.dll and could indicate - // memory corruption because the internal buffer is shared between managed and native code - throw new AccessViolationException(); - } - - int count = dataBuffers.Length; - bool isSendActivity = action == UnsafeNativeMethods.WebSocketProtocolComponent.Action.IndicateSendComplete || - action == UnsafeNativeMethods.WebSocketProtocolComponent.Action.SendToNetwork; - - if (isSendActivity) - { - count = (int)dataBufferCount; - } - - bool nonZeroBufferFound = false; - for (int i = 0; i < count; i++) - { - UnsafeNativeMethods.WebSocketProtocolComponent.Buffer dataBuffer = dataBuffers[i]; - - IntPtr bufferData; - uint bufferLength; - UnwrapWebSocketBuffer(dataBuffer, bufferType, out bufferData, out bufferLength); - - if (bufferData == IntPtr.Zero) - { - continue; - } - - nonZeroBufferFound = true; - - bool isPinnedSendPayloadBuffer = IsPinnedSendPayloadBuffer(dataBuffer, bufferType); - - if (bufferLength > GetMaxBufferSize()) - { - if (!isSendActivity || !isPinnedSendPayloadBuffer) - { - Contract.Assert(false, - "'dataBuffer.BufferLength' MUST NOT be bigger than 'm_ReceiveBufferSize' and 'm_SendBufferSize'."); - // Indicates a violation in the contract with native Websocket.dll and could indicate - // memory corruption because the internal buffer is shared between managed and native code - throw new AccessViolationException(); - } - } - - if (!isPinnedSendPayloadBuffer && !IsNativeBuffer(bufferData, bufferLength)) - { - Contract.Assert(false, - "WebSocketGetAction MUST return a pointer within the pinned internal buffer."); - // Indicates a violation in the contract with native Websocket.dll and could indicate - // memory corruption because the internal buffer is shared between managed and native code - throw new AccessViolationException(); - } - } - - if (!nonZeroBufferFound && - action != UnsafeNativeMethods.WebSocketProtocolComponent.Action.NoAction && - action != UnsafeNativeMethods.WebSocketProtocolComponent.Action.IndicateReceiveComplete && - action != UnsafeNativeMethods.WebSocketProtocolComponent.Action.IndicateSendComplete) - { - Contract.Assert(false, "At least one 'dataBuffer.Buffer' MUST NOT be NULL."); - } - } - - private static int GetNativeSendBufferSize(int sendBufferSize, bool isServerBuffer) - { - return isServerBuffer ? MinSendBufferSize : sendBufferSize; - } - - internal static void UnwrapWebSocketBuffer(UnsafeNativeMethods.WebSocketProtocolComponent.Buffer buffer, - UnsafeNativeMethods.WebSocketProtocolComponent.BufferType bufferType, - out IntPtr bufferData, - out uint bufferLength) - { - bufferData = IntPtr.Zero; - bufferLength = 0; - - switch (bufferType) - { - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.Close: - bufferData = buffer.CloseStatus.ReasonData; - bufferLength = buffer.CloseStatus.ReasonLength; - break; - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.None: - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryFragment: - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.BinaryMessage: - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Fragment: - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UTF8Message: - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.PingPong: - case UnsafeNativeMethods.WebSocketProtocolComponent.BufferType.UnsolicitedPong: - bufferData = buffer.Data.BufferData; - bufferLength = buffer.Data.BufferLength; - break; - default: - Contract.Assert(false, - string.Format(CultureInfo.InvariantCulture, - "BufferType '{0}' is invalid/unknown.", - bufferType)); - break; - } - } - - private void ThrowIfDisposed() - { - switch (_StateWhenDisposing) - { - case int.MinValue: - return; - case (int)WebSocketState.Closed: - case (int)WebSocketState.Aborted: - throw new WebSocketException(WebSocketError.InvalidState, - SR.GetString(SR.net_WebSockets_InvalidState_ClosedOrAborted, typeof(WebSocketBase), _StateWhenDisposing)); - default: - throw new ObjectDisposedException(GetType().FullName); - } - } - - [Conditional("DEBUG"), Conditional("CONTRACTS_FULL")] - private void ValidateBufferedPayload() - { - Contract.Assert(_BufferedPayloadReceiveResult != null, - "'m_BufferedPayloadReceiveResult' MUST NOT be NULL."); - Contract.Assert(_BufferedPayloadReceiveResult.Count >= 0, - "'m_BufferedPayloadReceiveResult.Count' MUST NOT be negative."); - Contract.Assert(_PayloadOffset >= 0, "'m_PayloadOffset' MUST NOT be smaller than 0."); - Contract.Assert(_PayloadOffset <= _PayloadBuffer.Count, - "'m_PayloadOffset' MUST NOT be bigger than 'm_PayloadBuffer.Count'."); - Contract.Assert(_PayloadOffset + _BufferedPayloadReceiveResult.Count <= _PayloadBuffer.Count, - "'m_PayloadOffset + m_PayloadBytesBuffered' MUST NOT be bigger than 'm_PayloadBuffer.Count'."); - } - - private int GetOffset(IntPtr pBuffer) - { - Contract.Assert(pBuffer != IntPtr.Zero, "'pBuffer' MUST NOT be IntPtr.Zero."); - int offset = (int)(pBuffer.ToInt64() - _StartAddress + _InternalBuffer.Offset); - - Contract.Assert(offset >= 0, "'offset' MUST NOT be negative."); - return offset; - } - - [Pure] - private int GetMaxBufferSize() - { - return Math.Max(_ReceiveBufferSize, _SendBufferSize); - } - - internal bool IsInternalBuffer(byte[] buffer, int offset, int count) - { - Contract.Assert(buffer != null, "'buffer' MUST NOT be NULL."); - Contract.Assert(_InternalBuffer.Array != null, "'m_InternalBuffer.Array' MUST NOT be NULL."); - Contract.Assert(offset >= 0, "'offset' MUST NOT be negative."); - Contract.Assert(count >= 0, "'count' MUST NOT be negative."); - Contract.Assert(offset + count <= buffer.Length, "'offset + count' MUST NOT exceed 'buffer.Length'."); - - return object.ReferenceEquals(buffer, _InternalBuffer.Array); - } - - internal IntPtr ToIntPtr(int offset) - { - Contract.Assert(offset >= 0, "'offset' MUST NOT be negative."); - Contract.Assert(_StartAddress + offset <= _EndAddress, "'offset' is TOO BIG."); - return new IntPtr(_StartAddress + offset); - } - - private bool IsNativeBuffer(IntPtr pBuffer, uint bufferSize) - { - Contract.Assert(pBuffer != IntPtr.Zero, "'pBuffer' MUST NOT be NULL."); - Contract.Assert(bufferSize <= GetMaxBufferSize(), - "'bufferSize' MUST NOT be bigger than 'm_ReceiveBufferSize' and 'm_SendBufferSize'."); - - long nativeBufferStartAddress = pBuffer.ToInt64(); - long nativeBufferEndAddress = bufferSize + nativeBufferStartAddress; - - Contract.Assert(Marshal.UnsafeAddrOfPinnedArrayElement(_InternalBuffer.Array, _InternalBuffer.Offset).ToInt64() == _StartAddress, - "'m_InternalBuffer.Array' MUST be pinned for the whole lifetime of a WebSocket."); - - if (nativeBufferStartAddress >= _StartAddress && - nativeBufferStartAddress <= _EndAddress && - nativeBufferEndAddress >= _StartAddress && - nativeBufferEndAddress <= _EndAddress) - { - return true; - } - - return false; - } - - private void CleanUp() - { - if (_GCHandle.IsAllocated) - { - _GCHandle.Free(); - } - - ReleasePinnedSendBuffer(); - } - - public static ArraySegment CreateInternalBufferArraySegment(int receiveBufferSize, int sendBufferSize, bool isServerBuffer) - { - Contract.Assert(receiveBufferSize >= MinReceiveBufferSize, - "'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Contract.Assert(sendBufferSize >= MinSendBufferSize, - "'sendBufferSize' MUST be at least " + MinSendBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - - int internalBufferSize = GetInternalBufferSize(receiveBufferSize, sendBufferSize, isServerBuffer); - return new ArraySegment(new byte[internalBufferSize]); - } - - public static void Validate(int count, int receiveBufferSize, int sendBufferSize, bool isServerBuffer) - { - Contract.Assert(receiveBufferSize >= MinReceiveBufferSize, - "'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Contract.Assert(sendBufferSize >= MinSendBufferSize, - "'sendBufferSize' MUST be at least " + MinSendBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - - int minBufferSize = GetInternalBufferSize(receiveBufferSize, sendBufferSize, isServerBuffer); - if (count < minBufferSize) - { - throw new ArgumentOutOfRangeException("internalBuffer", - SR.GetString(SR.net_WebSockets_ArgumentOutOfRange_InternalBuffer, minBufferSize)); - } - } - - private static int GetInternalBufferSize(int receiveBufferSize, int sendBufferSize, bool isServerBuffer) - { - Contract.Assert(receiveBufferSize >= MinReceiveBufferSize, - "'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Contract.Assert(sendBufferSize >= MinSendBufferSize, - "'sendBufferSize' MUST be at least " + MinSendBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - - Contract.Assert(receiveBufferSize <= MaxBufferSize, - "'receiveBufferSize' MUST be less than or equal to " + MaxBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - Contract.Assert(sendBufferSize <= MaxBufferSize, - "'sendBufferSize' MUST be at less than or equal to " + MaxBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); - - int nativeSendBufferSize = GetNativeSendBufferSize(sendBufferSize, isServerBuffer); - return (2 * receiveBufferSize) + nativeSendBufferSize + NativeOverheadBufferSize + PropertyBufferSize; - } - - private static class SendBufferState - { - public const int None = 0; - public const int SendPayloadSpecified = 1; - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketConstants.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketConstants.cs deleted file mode 100644 index c5aa4d8a85..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/WebSocketConstants.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Microsoft.Net.WebSockets -{ - public static class WebSocketConstants - { - public static string SupportedProtocolVersion = "13"; - } -} \ No newline at end of file diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketError.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketError.cs deleted file mode 100644 index 09811b81c9..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/WebSocketError.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.Net.WebSockets -{ - internal enum WebSocketError - { - Success = 0, - InvalidMessageType = 1, - Faulted = 2, - NativeError = 3, - NotAWebSocket = 4, - UnsupportedVersion = 5, - UnsupportedProtocol = 6, - HeaderError = 7, - ConnectionClosedPrematurely = 8, - InvalidState = 9 - } -} \ No newline at end of file diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketException.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketException.cs deleted file mode 100644 index a906a5d6aa..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/WebSocketException.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System; -using System.ComponentModel; -using System.Net.WebSockets; -using System.Runtime.InteropServices; - -namespace Microsoft.Net.WebSockets -{ -#if !NETSTANDARD1_3 - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")] -#endif - internal sealed class WebSocketException : Win32Exception - { - private WebSocketError _WebSocketErrorCode; - - public WebSocketException() - : this(Marshal.GetLastWin32Error()) - { - } - - public WebSocketException(WebSocketError error) - : this(error, GetErrorMessage(error)) - { - } - - public WebSocketException(WebSocketError error, string message) : base(message) - { - _WebSocketErrorCode = error; - } - - public WebSocketException(WebSocketError error, Exception innerException) - : this(error, GetErrorMessage(error), innerException) - { - } - - public WebSocketException(WebSocketError error, string message, Exception innerException) - : base(message, innerException) - { - _WebSocketErrorCode = error; - } - - public WebSocketException(int nativeError) - : base(nativeError) - { - _WebSocketErrorCode = !UnsafeNativeMethods.WebSocketProtocolComponent.Succeeded(nativeError) ? WebSocketError.NativeError : WebSocketError.Success; - this.SetErrorCodeOnError(nativeError); - } - - public WebSocketException(int nativeError, string message) - : base(nativeError, message) - { - _WebSocketErrorCode = !UnsafeNativeMethods.WebSocketProtocolComponent.Succeeded(nativeError) ? WebSocketError.NativeError : WebSocketError.Success; - this.SetErrorCodeOnError(nativeError); - } - - public WebSocketException(int nativeError, Exception innerException) - : base(SR.GetString(SR.net_WebSockets_Generic), innerException) - { - _WebSocketErrorCode = !UnsafeNativeMethods.WebSocketProtocolComponent.Succeeded(nativeError) ? WebSocketError.NativeError : WebSocketError.Success; - this.SetErrorCodeOnError(nativeError); - } - - public WebSocketException(WebSocketError error, int nativeError) - : this(error, nativeError, GetErrorMessage(error)) - { - } - - public WebSocketException(WebSocketError error, int nativeError, string message) - : base(message) - { - _WebSocketErrorCode = error; - this.SetErrorCodeOnError(nativeError); - } - - public WebSocketException(WebSocketError error, int nativeError, Exception innerException) - : this(error, nativeError, GetErrorMessage(error), innerException) - { - } - - public WebSocketException(WebSocketError error, int nativeError, string message, Exception innerException) - : base(message, innerException) - { - _WebSocketErrorCode = error; - this.SetErrorCodeOnError(nativeError); - } - - public WebSocketException(string message) - : base(message) - { - } - - public WebSocketException(string message, Exception innerException) - : base(message, innerException) - { - } -#if !NETSTANDARD1_3 - public override int ErrorCode - { - get - { - return base.NativeErrorCode; - } - } -#endif - public WebSocketError WebSocketErrorCode - { - get - { - return _WebSocketErrorCode; - } - } - - private static string GetErrorMessage(WebSocketError error) - { - // provide a canned message for the error type - switch (error) - { - case WebSocketError.InvalidMessageType: - return SR.GetString(SR.net_WebSockets_InvalidMessageType_Generic, - typeof(WebSocket).Name + WebSocketBase.Methods.CloseAsync, - typeof(WebSocket).Name + WebSocketBase.Methods.CloseOutputAsync); - case WebSocketError.Faulted: - return SR.GetString(SR.net_Websockets_WebSocketBaseFaulted); - case WebSocketError.NotAWebSocket: - return SR.GetString(SR.net_WebSockets_NotAWebSocket_Generic); - case WebSocketError.UnsupportedVersion: - return SR.GetString(SR.net_WebSockets_UnsupportedWebSocketVersion_Generic); - case WebSocketError.UnsupportedProtocol: - return SR.GetString(SR.net_WebSockets_UnsupportedProtocol_Generic); - case WebSocketError.HeaderError: - return SR.GetString(SR.net_WebSockets_HeaderError_Generic); - case WebSocketError.ConnectionClosedPrematurely: - return SR.GetString(SR.net_WebSockets_ConnectionClosedPrematurely_Generic); - case WebSocketError.InvalidState: - return SR.GetString(SR.net_WebSockets_InvalidState_Generic); - default: - return SR.GetString(SR.net_WebSockets_Generic); - } - } - - // Set the error code only if there is an error (i.e. nativeError >= 0). Otherwise the code blows up on deserialization - // as the Exception..ctor() throws on setting HResult to 0. The default for HResult is -2147467259. - private void SetErrorCodeOnError(int nativeError) - { - if (!UnsafeNativeMethods.WebSocketProtocolComponent.Succeeded(nativeError)) - { - this.HResult = nativeError; - } - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketExtensions.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketExtensions.cs deleted file mode 100644 index dccc933241..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/WebSocketExtensions.cs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System; -using System.IO; -using System.Net.WebSockets; -using System.Threading.Tasks; -using Microsoft.Net.WebSockets; - -namespace Microsoft.Net.Http.Server -{ - public static class WebSocketExtensions - { - public static bool IsWebSocketRequest(this RequestContext context) - { - if (!WebSocketHelpers.AreWebSocketsSupported) - { - return false; - } - - if (!context.IsUpgradableRequest) - { - return false; - } - - if (!string.Equals("GET", context.Request.Method, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Connection: Upgrade (some odd clients send Upgrade,KeepAlive) - string connection = context.Request.Headers[HttpKnownHeaderNames.Connection]; - if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) - { - return false; - } - - // Upgrade: websocket - string upgrade = context.Request.Headers[HttpKnownHeaderNames.Upgrade]; - if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Sec-WebSocket-Version: 13 - string version = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Sec-WebSocket-Key: {base64string} - string key = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - if (!WebSocketHelpers.IsValidWebSocketKey(key)) - { - return false; - } - - return true; - } - - // Compare IsWebSocketRequest() - private static void ValidateWebSocketRequest(RequestContext context) - { - if (!WebSocketHelpers.AreWebSocketsSupported) - { - throw new NotSupportedException("WebSockets are not supported on this platform."); - } - - if (!context.IsUpgradableRequest) - { - throw new InvalidOperationException("This request is not a valid upgrade request."); - } - - if (!string.Equals("GET", context.Request.Method, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("This request is not a valid upgrade request; invalid verb: " + context.Request.Method); - } - - // Connection: Upgrade (some odd clients send Upgrade,KeepAlive) - string connection = context.Request.Headers[HttpKnownHeaderNames.Connection]; - if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) - { - throw new InvalidOperationException("The Connection header is invalid: " + connection); - } - - // Upgrade: websocket - string upgrade = context.Request.Headers[HttpKnownHeaderNames.Upgrade]; - if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("The Upgrade header is invalid: " + upgrade); - } - - // Sec-WebSocket-Version: 13 - string version = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("The Sec-WebSocket-Version header is invalid or not supported: " + version); - } - - // Sec-WebSocket-Key: {base64string} - string key = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - if (!WebSocketHelpers.IsValidWebSocketKey(key)) - { - throw new InvalidOperationException("The Sec-WebSocket-Key header is invalid: " + upgrade); - } - } - - public static Task AcceptWebSocketAsync(this RequestContext context) - { - return context.AcceptWebSocketAsync(null, - WebSocketHelpers.DefaultReceiveBufferSize, - WebSocketHelpers.DefaultKeepAliveInterval); - } - - public static Task AcceptWebSocketAsync(this RequestContext context, string subProtocol) - { - return context.AcceptWebSocketAsync(subProtocol, - WebSocketHelpers.DefaultReceiveBufferSize, - WebSocketHelpers.DefaultKeepAliveInterval); - } - - public static Task AcceptWebSocketAsync(this RequestContext context, string subProtocol, TimeSpan keepAliveInterval) - { - return context.AcceptWebSocketAsync(subProtocol, - WebSocketHelpers.DefaultReceiveBufferSize, - keepAliveInterval); - } - - public static Task AcceptWebSocketAsync( - this RequestContext context, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval) - { - WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval); - - ArraySegment internalBuffer = WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); - return context.AcceptWebSocketAsync(subProtocol, - receiveBufferSize, - keepAliveInterval, - internalBuffer); - } - - public static Task AcceptWebSocketAsync( - this RequestContext context, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) - { - if (!context.IsUpgradableRequest) - { - throw new InvalidOperationException("This request is cannot be upgraded."); - } - WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval); - WebSocketHelpers.ValidateArraySegment(internalBuffer, "internalBuffer"); - WebSocketBuffer.Validate(internalBuffer.Count, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); - - return AcceptWebSocketAsyncCore(context, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer); - } - - private static async Task AcceptWebSocketAsyncCore( - RequestContext context, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) - { - ValidateWebSocketRequest(context); - - var subProtocols = context.Request.Headers.GetValues(HttpKnownHeaderNames.SecWebSocketProtocol); - bool shouldSendSecWebSocketProtocolHeader = WebSocketHelpers.ProcessWebSocketProtocolHeader(subProtocols, subProtocol); - if (shouldSendSecWebSocketProtocolHeader) - { - context.Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = subProtocol; - } - - // negotiate the websocket key return value - string secWebSocketKey = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); - - context.Response.Headers.Append(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); - context.Response.Headers.Append(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken); - context.Response.Headers.Append(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); - - Stream opaqueStream = await context.UpgradeAsync(); - - return WebSocketHelpers.CreateServerWebSocket( - opaqueStream, - subProtocol, - receiveBufferSize, - keepAliveInterval, - internalBuffer); - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketHelpers.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketHelpers.cs deleted file mode 100644 index 21e0b11838..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/WebSocketHelpers.cs +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net.WebSockets; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Net.WebSockets -{ - public static class WebSocketHelpers - { - internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - public const string WebSocketUpgradeToken = "websocket"; - public const int DefaultReceiveBufferSize = 16 * 1024; - internal const int DefaultClientSendBufferSize = 16 * 1024; - internal const int MaxControlFramePayloadLength = 123; - - // RFC 6455 requests WebSocket clients to let the server initiate the TCP close to avoid that client sockets - // end up in TIME_WAIT-state - // - // After both sending and receiving a Close message, an endpoint considers the WebSocket connection closed and - // MUST close the underlying TCP connection. The server MUST close the underlying TCP connection immediately; - // the client SHOULD wait for the server to close the connection but MAY close the connection at any time after - // sending and receiving a Close message, e.g., if it has not received a TCP Close from the server in a - // reasonable time period. - internal const int ClientTcpCloseTimeout = 1000; // 1s - - 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 readonly ArraySegment EmptyPayload = new ArraySegment(new byte[] { }, 0, 0); - private static readonly Random KeyGenerator = new Random(); - private static TimeSpan? _defaultKeepAliveInterval; - - public static bool AreWebSocketsSupported - { - get - { - return UnsafeNativeMethods.WebSocketProtocolComponent.IsSupported; - } - } - - public static TimeSpan DefaultKeepAliveInterval - { - get - { - if (!_defaultKeepAliveInterval.HasValue) - { - if (AreWebSocketsSupported) - { - _defaultKeepAliveInterval = new TimeSpan?(UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketGetDefaultKeepAliveInterval()); - } - else - { - _defaultKeepAliveInterval = new TimeSpan?(Timeout.InfiniteTimeSpan); - } - } - return _defaultKeepAliveInterval.Value; - } - } - - public static bool IsValidWebSocketKey(string key) - { - if (string.IsNullOrWhiteSpace(key)) - { - return false; - } - // TODO: - // throw new NotImplementedException(); - return true; - } - - [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5354:SHA1CannotBeUsed", - Justification = "SHA1 used only for hashing purposes, not for crypto.")] - public static string GetSecWebSocketAcceptString(string secWebSocketKey) - { - string retVal; - // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat. - using (SHA1 sha1 = SHA1.Create()) - { - string acceptString = string.Concat(secWebSocketKey, WebSocketHelpers.SecWebSocketKeyGuid); - byte[] toHash = Encoding.UTF8.GetBytes(acceptString); - retVal = Convert.ToBase64String(sha1.ComputeHash(toHash)); - } - return retVal; - } - - public static WebSocket CreateServerWebSocket(Stream opaqueStream, string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment internalBuffer) - { - return new ServerWebSocket(opaqueStream, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer); - } - - internal static string GetTraceMsgForParameters(int offset, int count, CancellationToken cancellationToken) - { - return string.Format(CultureInfo.InvariantCulture, - "offset: {0}, count: {1}, cancellationToken.CanBeCanceled: {2}", - offset, - count, - cancellationToken.CanBeCanceled); - } - - // return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server. - public static bool ProcessWebSocketProtocolHeader(IEnumerable clientSecWebSocketProtocols, string subProtocol) - { - if (clientSecWebSocketProtocols == null || !clientSecWebSocketProtocols.Any()) - { - // client hasn't specified any Sec-WebSocket-Protocol header - if (subProtocol != null) - { - // If the server specified _anything_ this isn't valid. - throw new WebSocketException(WebSocketError.UnsupportedProtocol, - SR.GetString(SR.net_WebSockets_ClientAcceptingNoProtocols, subProtocol)); - } - // Treat empty and null from the server as the same thing here, server should not send headers. - return false; - } - - // here, we know the client specified something and it's non-empty. - - if (string.IsNullOrEmpty(subProtocol)) - { - // client specified some protocols, server specified 'null'. So server should send headers. - return false; - } - - // here, we know that the client has specified something, it's not empty - // and the server has specified exactly one protocol - - // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that - // this exists in the list the client specified. - foreach (var currentRequestProtocol in clientSecWebSocketProtocols) - { - if (string.Compare(subProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0) - { - return true; - } - } - - throw new WebSocketException(WebSocketError.UnsupportedProtocol, - SR.GetString(SR.net_WebSockets_AcceptUnsupportedProtocol, - string.Join(", ", clientSecWebSocketProtocols), - subProtocol)); - } - - internal static ConfiguredTaskAwaitable SuppressContextFlow(this Task task) - { - // We don't flow the synchronization context within WebSocket.xxxAsync - but the calling application - // can decide whether the completion callback for the task returned from WebSocket.xxxAsync runs - // under the caller's synchronization context. - return task.ConfigureAwait(false); - } - - internal static ConfiguredTaskAwaitable SuppressContextFlow(this Task task) - { - // We don't flow the synchronization context within WebSocket.xxxAsync - but the calling application - // can decide whether the completion callback for the task returned from WebSocket.xxxAsync runs - // under the caller's synchronization context. - return task.ConfigureAwait(false); - } - - internal static bool IsStateTerminal(WebSocketState state) - { - return state == WebSocketState.Closed || state == WebSocketState.Aborted; - } - - internal static void ThrowOnInvalidState(WebSocketState state, params WebSocketState[] validStates) - { - string text = string.Empty; - if (validStates != null && validStates.Length > 0) - { - for (int i = 0; i < validStates.Length; i++) - { - WebSocketState webSocketState = validStates[i]; - if (state == webSocketState) - { - return; - } - } - text = string.Join(", ", validStates); - } - throw new WebSocketException(SR.GetString("net_WebSockets_InvalidState", new object[] - { - state, - text - })); - } - - internal static void ValidateBuffer(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException("offset"); - } - - if (count < 0 || count > (buffer.Length - offset)) - { - throw new ArgumentOutOfRangeException("count"); - } - } - - internal static void ValidateSubprotocol(string subProtocol) - { - if (string.IsNullOrWhiteSpace(subProtocol)) - { - throw new ArgumentException(SR.GetString(SR.net_WebSockets_InvalidEmptySubProtocol), "subProtocol"); - } - - char[] chars = subProtocol.ToCharArray(); - string invalidChar = null; - int i = 0; - while (i < chars.Length) - { - char ch = chars[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.GetString(SR.net_WebSockets_InvalidCharInProtocolString, subProtocol, invalidChar), - "subProtocol"); - } - } - - internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string statusDescription) - { - if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription)) - { - throw new ArgumentException(SR.GetString(SR.net_WebSockets_ReasonNotNull, - statusDescription, - WebSocketCloseStatus.Empty), - "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.GetString(SR.net_WebSockets_InvalidCloseStatusCode, - closeStatusCode), - "closeStatus"); - } - - int length = 0; - if (!string.IsNullOrEmpty(statusDescription)) - { - length = UTF8Encoding.UTF8.GetByteCount(statusDescription); - } - - if (length > WebSocketHelpers.MaxControlFramePayloadLength) - { - throw new ArgumentException(SR.GetString(SR.net_WebSockets_InvalidCloseStatusDescription, - statusDescription, - WebSocketHelpers.MaxControlFramePayloadLength), - "statusDescription"); - } - } - - public static void ValidateOptions(string subProtocol, - int receiveBufferSize, - int sendBufferSize, - TimeSpan keepAliveInterval) - { - // We allow the subProtocol to be null. Validate if it is not null. - if (subProtocol != null) - { - ValidateSubprotocol(subProtocol); - } - - ValidateBufferSizes(receiveBufferSize, sendBufferSize); - - // -1 - if (keepAliveInterval < Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException("keepAliveInterval", keepAliveInterval, - SR.GetString(SR.net_WebSockets_ArgumentOutOfRange_TooSmall, Timeout.InfiniteTimeSpan.ToString())); - } - } - - internal static void ValidateBufferSizes(int receiveBufferSize, int sendBufferSize) - { - if (receiveBufferSize < WebSocketBuffer.MinReceiveBufferSize) - { - throw new ArgumentOutOfRangeException("receiveBufferSize", receiveBufferSize, - SR.GetString(SR.net_WebSockets_ArgumentOutOfRange_TooSmall, WebSocketBuffer.MinReceiveBufferSize)); - } - - if (sendBufferSize < WebSocketBuffer.MinSendBufferSize) - { - throw new ArgumentOutOfRangeException("sendBufferSize", sendBufferSize, - SR.GetString(SR.net_WebSockets_ArgumentOutOfRange_TooSmall, WebSocketBuffer.MinSendBufferSize)); - } - - if (receiveBufferSize > WebSocketBuffer.MaxBufferSize) - { - throw new ArgumentOutOfRangeException("receiveBufferSize", receiveBufferSize, - SR.GetString(SR.net_WebSockets_ArgumentOutOfRange_TooBig, - "receiveBufferSize", - receiveBufferSize, - WebSocketBuffer.MaxBufferSize)); - } - - if (sendBufferSize > WebSocketBuffer.MaxBufferSize) - { - throw new ArgumentOutOfRangeException("sendBufferSize", sendBufferSize, - SR.GetString(SR.net_WebSockets_ArgumentOutOfRange_TooBig, - "sendBufferSize", - sendBufferSize, - WebSocketBuffer.MaxBufferSize)); - } - } - - internal static void ValidateInnerStream(Stream innerStream) - { - if (innerStream == null) - { - throw new ArgumentNullException("innerStream"); - } - - if (!innerStream.CanRead) - { - throw new ArgumentException(SR.GetString(SR.NotReadableStream), "innerStream"); - } - - if (!innerStream.CanWrite) - { - throw new ArgumentException(SR.GetString(SR.NotWriteableStream), "innerStream"); - } - } - - internal static void ThrowIfConnectionAborted(Stream connection, bool read) - { - if ((!read && !connection.CanWrite) || - (read && !connection.CanRead)) - { - throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely); - } - } - - internal static void ThrowPlatformNotSupportedException_WSPC() - { - throw new PlatformNotSupportedException(SR.GetString(SR.net_WebSockets_UnsupportedPlatform)); - } - - public static void ValidateArraySegment(ArraySegment arraySegment, string parameterName) - { - Contract.Requires(!string.IsNullOrEmpty(parameterName), "'parameterName' MUST NOT be NULL or string.Empty"); - - if (arraySegment.Array == null) - { - throw new ArgumentNullException(parameterName + ".Array"); - } - - if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length) - { - throw new ArgumentOutOfRangeException(parameterName + ".Offset"); - } - if (arraySegment.Count < 0 || arraySegment.Count > (arraySegment.Array.Length - arraySegment.Offset)) - { - throw new ArgumentOutOfRangeException(parameterName + ".Count"); - } - } - } -} diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketReceiveResultExtensions.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketReceiveResultExtensions.cs deleted file mode 100644 index 12841af2df..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/WebSocketReceiveResultExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics.Contracts; -using System.Net.WebSockets; - -namespace Microsoft.Net.WebSockets -{ - internal static class WebSocketReceiveResultExtensions - { - internal static WebSocketReceiveResult DecrementAndClone(ref WebSocketReceiveResult original, int count) - { - Contract.Assert(count >= 0, "'count' MUST NOT be negative."); - Contract.Assert(count <= original.Count, "'count' MUST NOT be bigger than 'this.Count'."); - int remaining = original.Count - count; - original = new WebSocketReceiveResult(remaining, - original.MessageType, - original.EndOfMessage, - original.CloseStatus, - original.CloseStatusDescription); - return new WebSocketReceiveResult(count, - original.MessageType, - remaining == 0 && original.EndOfMessage, - original.CloseStatus, - original.CloseStatusDescription); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.Net.WebSockets.Server/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs b/src/Microsoft.Net.WebSockets.Server/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs deleted file mode 100644 index aef2c8b323..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -// ==++== -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// ==--== - -#if NETSTANDARD1_3 - -namespace Microsoft.Win32.SafeHandles -{ - using System; - using System.Runtime.InteropServices; - using System.Runtime.CompilerServices; - - // Class of safe handle which uses 0 or -1 as an invalid handle. - [System.Security.SecurityCritical] // auto-generated_required - internal abstract class SafeHandleZeroOrMinusOneIsInvalid : SafeHandle - { - protected SafeHandleZeroOrMinusOneIsInvalid(bool ownsHandle) - : base(IntPtr.Zero, ownsHandle) - { - } - - public override bool IsInvalid - { - [System.Security.SecurityCritical] - get { return handle == new IntPtr(0) || handle == new IntPtr(-1); } - } - } -} -#endif diff --git a/src/Microsoft.Net.WebSockets.Server/fx/System/AccessViolationException.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/AccessViolationException.cs deleted file mode 100644 index 0345bbc1a1..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/fx/System/AccessViolationException.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -#if NETSTANDARD1_3 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace System -{ - internal class AccessViolationException : SystemException - { - } -} - -#endif diff --git a/src/Microsoft.Net.WebSockets.Server/fx/System/ExternDll.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/ExternDll.cs deleted file mode 100644 index 989211c70c..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/fx/System/ExternDll.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -#if NETSTANDARD1_3 - -namespace System -{ - internal static class ExternDll - { - public const string api_ms_win_core_localization_LIB = "api-ms-win-core-localization-l2-1-0.dll"; - } -} -#endif diff --git a/src/Microsoft.Net.WebSockets.Server/fx/System/Runtime/InteropServices/ExternalException.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/Runtime/InteropServices/ExternalException.cs deleted file mode 100644 index 807bdd2a60..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/fx/System/Runtime/InteropServices/ExternalException.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -// ==++== -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// ==--== -/*============================================================================= -** -** Class: ExternalException -** -** -** Purpose: Exception base class for all errors from Interop or Structured -** Exception Handling code. -** -** -=============================================================================*/ - -#if NETSTANDARD1_3 - -namespace System.Runtime.InteropServices -{ - using System; - using System.Globalization; - - // Base exception for COM Interop errors &; Structured Exception Handler - // exceptions. - // - internal class ExternalException : Exception - { - public ExternalException() - { - SetErrorCode(__HResults.E_FAIL); - } - - public ExternalException(String message) - : base(message) - { - SetErrorCode(__HResults.E_FAIL); - } - - public ExternalException(String message, Exception inner) - : base(message, inner) - { - SetErrorCode(__HResults.E_FAIL); - } - - public ExternalException(String message, int errorCode) - : base(message) - { - SetErrorCode(errorCode); - } - - private void SetErrorCode(int errorCode) - { - HResult = ErrorCode; - } - - private static class __HResults - { - internal const int E_FAIL = unchecked((int)0x80004005); - } - - public virtual int ErrorCode - { - get - { - return HResult; - } - } - - public override String ToString() - { - String message = Message; - String s; - String _className = GetType().ToString(); - s = _className + " (0x" + HResult.ToString("X8", CultureInfo.InvariantCulture) + ")"; - - if (!(String.IsNullOrEmpty(message))) - { - s = s + ": " + message; - } - - Exception _innerException = InnerException; - - if (_innerException != null) - { - s = s + " ---> " + _innerException.ToString(); - } - - - if (StackTrace != null) - s += Environment.NewLine + StackTrace; - - return s; - } - } -} - -#endif diff --git a/src/Microsoft.Net.WebSockets.Server/fx/System/SafeNativeMethods.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/SafeNativeMethods.cs deleted file mode 100644 index 27a17a745c..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/fx/System/SafeNativeMethods.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -#if NETSTANDARD1_3 -using System.Runtime.InteropServices; -using System.Text; - -namespace System -{ - internal static class SafeNativeMethods - { - public const int - FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100, - FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200, - FORMAT_MESSAGE_FROM_STRING = 0x00000400, - FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000, - FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000; - - [DllImport(ExternDll.api_ms_win_core_localization_LIB, CharSet = System.Runtime.InteropServices.CharSet.Unicode, SetLastError = true, BestFitMapping = true)] - public static unsafe extern int FormatMessage(int dwFlags, IntPtr lpSource_mustBeNull, uint dwMessageId, - int dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr[] arguments); - } -} -#endif diff --git a/src/Microsoft.Net.WebSockets.Server/fx/System/SystemException.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/SystemException.cs deleted file mode 100644 index 3b4436eb4e..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/fx/System/SystemException.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -#if NETSTANDARD1_3 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace System -{ - internal class SystemException : Exception - { - } -} - -#endif diff --git a/src/Microsoft.Net.WebSockets.Server/project.json b/src/Microsoft.Net.WebSockets.Server/project.json deleted file mode 100644 index dda1bf4231..0000000000 --- a/src/Microsoft.Net.WebSockets.Server/project.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": "0.2.0-*", - "description": "Implementation of WebSocket abstract base class. Used by WebListener.", - "dependencies": { - "Microsoft.Net.Http.Server": "0.2.0-*" - }, - "buildOptions": { - "allowUnsafe": true, - "warningsAsErrors": true, - "keyFile": "../../tools/Key.snk", - "nowarn": [ - "CS1591" - ], - "xmlDoc": true - }, - "frameworks": { - "net451": {}, - "netstandard1.3": { - "dependencies": { - "System.Collections": "4.0.11-*", - "System.Linq": "4.1.0-*", - "System.Net.WebSockets": "4.0.0-*", - "System.Resources.ResourceManager": "4.0.1-*", - "System.Runtime.Extensions": "4.1.0-*", - "System.Security.Cryptography.Algorithms": "4.2.0-*", - "System.Threading": "4.0.11-*", - "System.Threading.Tasks": "4.0.11-*", - "System.Threading.Timer": "4.0.1-*", - "System.Threading.ThreadPool": "4.0.10-*" - } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/WebSocketTests.cs b/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/WebSocketTests.cs index c34936f454..896dc42a57 100644 --- a/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/WebSocketTests.cs +++ b/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/WebSocketTests.cs @@ -14,7 +14,7 @@ // NON-INFRINGEMENT. // See the Apache 2 License for the specific language governing // permissions and limitations under the License. -#if WEBSOCKETS + using System; using System.Net.Http; using System.Net.WebSockets; @@ -159,10 +159,9 @@ namespace Microsoft.AspNetCore.Server.WebListener private async Task SendWebSocketRequestAsync(string address) { - ClientWebSocket client = new ClientWebSocket(); + var client = new ClientWebSocket(); await client.ConnectAsync(new Uri(address), CancellationToken.None); return client; } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/project.json b/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/project.json index c79eb2c3f8..d25973f484 100644 --- a/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/project.json +++ b/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/project.json @@ -19,7 +19,8 @@ }, "System.Net.Http.WinHttpHandler": "4.0.0-*", "System.Net.Requests": "4.0.11-*", - "System.Net.WebHeaderCollection": "4.0.1-*" + "System.Net.WebHeaderCollection": "4.0.1-*", + "System.Net.WebSockets.Client": "4.0.0" } }, "net451": { diff --git a/test/Microsoft.Net.Http.Server.FunctionalTests/WebSocketTests.cs b/test/Microsoft.Net.Http.Server.FunctionalTests/WebSocketTests.cs index 96bbedaea8..7fb09686ee 100644 --- a/test/Microsoft.Net.Http.Server.FunctionalTests/WebSocketTests.cs +++ b/test/Microsoft.Net.Http.Server.FunctionalTests/WebSocketTests.cs @@ -62,7 +62,7 @@ namespace Microsoft.Net.Http.Server Task clientTask = SendWebSocketRequestAsync(ConvertToWebSocketAddress(address)); var context = await server.AcceptAsync(); - Assert.True(context.IsWebSocketRequest()); + Assert.True(context.IsWebSocketRequest); WebSocket serverWebSocket = await context.AcceptWebSocketAsync(); WebSocket clientWebSocket = await clientTask; diff --git a/test/Microsoft.Net.Http.Server.FunctionalTests/project.json b/test/Microsoft.Net.Http.Server.FunctionalTests/project.json index d823390cbc..399d8dc19d 100644 --- a/test/Microsoft.Net.Http.Server.FunctionalTests/project.json +++ b/test/Microsoft.Net.Http.Server.FunctionalTests/project.json @@ -3,7 +3,6 @@ "dependencies": { "dotnet-test-xunit": "2.2.0-*", "Microsoft.Net.Http.Server": "0.2.0-*", - "Microsoft.Net.WebSockets.Server": "0.2.0-*", "Microsoft.AspNetCore.Testing": "1.1.0-*", "xunit": "2.2.0-*" },