From d9fa1c9f6262996d13ebf4b04f4e6ca4e1ad48d6 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Fri, 19 Jul 2019 17:00:09 -0700 Subject: [PATCH 01/10] stash --- .../WebSockets/src/HandshakeHelpers.cs | 20 +++++++++++++++---- .../WebSockets/src/WebSocketMiddleware.cs | 4 +++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index 0162751f160c..b64b89b47e1f 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Http; @@ -65,7 +67,7 @@ public static bool CheckSupportedWebSocketRequest(string method, IEnumerable mergedBytes = stackalloc byte[count]; + Encoding.UTF8.GetBytes(merged, mergedBytes); + + Span hashedBytes = stackalloc byte[20]; + var success = algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written); + Debug.Assert(success); + Debug.Assert(written == 20); + return Convert.ToBase64String(hashedBytes); } } diff --git a/src/Middleware/WebSockets/src/WebSocketMiddleware.cs b/src/Middleware/WebSockets/src/WebSocketMiddleware.cs index bff8a770d26a..98c1764b61c8 100644 --- a/src/Middleware/WebSockets/src/WebSocketMiddleware.cs +++ b/src/Middleware/WebSockets/src/WebSocketMiddleware.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Net; using System.Net.WebSockets; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; @@ -146,7 +148,7 @@ public async Task AcceptAsync(WebSocketAcceptContext acceptContext) } } - string key = string.Join(", ", _context.Request.Headers[HeaderNames.SecWebSocketKey]); + string key = _context.Request.Headers[HeaderNames.SecWebSocketKey]; HandshakeHelpers.GenerateResponseHeaders(key, subProtocol, _context.Response.Headers); From df8bbf68f11da7ad5c4d0af07a17823c21072050 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Fri, 19 Jul 2019 17:04:18 -0700 Subject: [PATCH 02/10] stash --- src/Middleware/WebSockets/src/HandshakeHelpers.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index b64b89b47e1f..6ce11b9f0ce6 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -91,9 +91,9 @@ public static bool IsRequestKeyValid(string value) } try { - //Convert.TryFromBase64String(); - byte[] data = Convert.FromBase64String(value); - return data.Length == 16; + Span temp = stackalloc byte[20]; + var success = Convert.TryFromBase64String(value, temp, out var written); + return written == 16 && success; } catch (Exception) { From ac10857e5bc61d53a571223f031df5ecdbe757d0 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Fri, 19 Jul 2019 22:30:10 -0700 Subject: [PATCH 03/10] more things --- src/Middleware/Middleware.sln | 14 +++++ .../WebSockets/src/HandshakeHelpers.cs | 11 ++-- .../Microsoft.AspNetCore.WebSockets.csproj | 6 +- .../test/UnitTests/HandshakeTests.cs | 59 +++++++++++++++++++ .../perf/Microbenchmarks/AssemblyInfo.cs | 1 + .../Microbenchmarks/HandshakeBenchmark.cs | 41 +++++++++++++ ...pNetCore.WebSockets.Microbenchmarks.csproj | 13 ++++ 7 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 src/Middleware/WebSockets/test/UnitTests/HandshakeTests.cs create mode 100644 src/Middleware/perf/Microbenchmarks/AssemblyInfo.cs create mode 100644 src/Middleware/perf/Microbenchmarks/HandshakeBenchmark.cs create mode 100644 src/Middleware/perf/Microbenchmarks/Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj diff --git a/src/Middleware/Middleware.sln b/src/Middleware/Middleware.sln index e09bbfb88c42..cdce6f321e48 100644 --- a/src/Middleware/Middleware.sln +++ b/src/Middleware/Middleware.sln @@ -293,6 +293,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SpaServices.Extensions.Tests", "SpaServices.Extensions\test\Microsoft.AspNetCore.SpaServices.Extensions.Tests.csproj", "{D0CB733B-4CE8-4F6C-BBB9-548EA1A96966}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSockets.Microbenchmarks", "perf\Microbenchmarks\Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj", "{C4D624B3-749E-41D8-A43B-B304BC3885EA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1575,6 +1577,18 @@ Global {46B4FE62-06A1-4D54-B3E8-D8B4B3560075}.Release|x64.Build.0 = Release|Any CPU {46B4FE62-06A1-4D54-B3E8-D8B4B3560075}.Release|x86.ActiveCfg = Release|Any CPU {46B4FE62-06A1-4D54-B3E8-D8B4B3560075}.Release|x86.Build.0 = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x64.Build.0 = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x86.Build.0 = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|Any CPU.Build.0 = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x64.ActiveCfg = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x64.Build.0 = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x86.ActiveCfg = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x86.Build.0 = Release|Any CPU {92E11EBB-759E-4DA8-AB61-A9977D9F97D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92E11EBB-759E-4DA8-AB61-A9977D9F97D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {92E11EBB-759E-4DA8-AB61-A9977D9F97D0}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index 6ce11b9f0ce6..118038809cfa 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Security.Cryptography; @@ -91,7 +90,7 @@ public static bool IsRequestKeyValid(string value) } try { - Span temp = stackalloc byte[20]; + Span temp = stackalloc byte[16]; var success = Convert.TryFromBase64String(value, temp, out var written); return written == 16 && success; } @@ -101,7 +100,7 @@ public static bool IsRequestKeyValid(string value) } } - internal static string CreateResponseKey(string requestKey) + public static string CreateResponseKey(string requestKey) { // "The value of this header field is constructed by concatenating /key/, defined above in step 4 // in Section 4.2.2, with the string "258EAFA5- E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of @@ -117,9 +116,9 @@ internal static string CreateResponseKey(string requestKey) { string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - var count = Encoding.UTF8.GetByteCount(merged); - // requestKey is already verified to be small (24 bytes) by 'IsRequestKeyValid()' so stackalloc is safe - Span mergedBytes = stackalloc byte[count]; + // requestKey is already verified to be small (24 bytes) by 'IsRequestKeyValid()' and everything is 1:1 mapping to UTF8 bytes + // so this can be hardcoded to 60 bytes for the requestKey + static websocket string + Span mergedBytes = stackalloc byte[60]; Encoding.UTF8.GetBytes(merged, mergedBytes); Span hashedBytes = stackalloc byte[20]; diff --git a/src/Middleware/WebSockets/src/Microsoft.AspNetCore.WebSockets.csproj b/src/Middleware/WebSockets/src/Microsoft.AspNetCore.WebSockets.csproj index a6fe40e2b211..87fed578b95e 100644 --- a/src/Middleware/WebSockets/src/Microsoft.AspNetCore.WebSockets.csproj +++ b/src/Middleware/WebSockets/src/Microsoft.AspNetCore.WebSockets.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core web socket middleware for use on top of opaque servers. @@ -17,4 +17,8 @@ + + + + diff --git a/src/Middleware/WebSockets/test/UnitTests/HandshakeTests.cs b/src/Middleware/WebSockets/test/UnitTests/HandshakeTests.cs new file mode 100644 index 000000000000..bc3f6c29f101 --- /dev/null +++ b/src/Middleware/WebSockets/test/UnitTests/HandshakeTests.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using Xunit; + +namespace Microsoft.AspNetCore.WebSockets.Tests +{ + public class HandshakeTests + { + [Theory] + [MemberData(nameof(SecWebsocketKeys))] + public void CreatesCorrectResponseKey(string key) + { + var response = HandshakeHelpers.CreateResponseKey(key); + + using var sha1 = SHA1.Create(); + var requestKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + var hashedBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(requestKey)); + var expectedResponse = Convert.ToBase64String(hashedBytes); + Assert.Equal(expectedResponse, response); + } + + [Theory] + [InlineData("VUfWn1u2Ot0AICM6f+/8Zg==")] + public void AcceptsValidRequestKeys(string key) + { + Assert.True(HandshakeHelpers.IsRequestKeyValid(key)); + } + + [Theory] + [InlineData("+/UH3kSoKpZsAG1dbr0gt/s=")] + [InlineData("klbuwX3CkgUIA8x23owW")] + [InlineData("")] + [InlineData("24 length not base64 str")] + public void RejectsInvalidRequestKeys(string key) + { + Assert.False(HandshakeHelpers.IsRequestKeyValid(key)); + } + + public static IEnumerable SecWebsocketKeys + { + get + { + var random = new Random(); + for (var i = 0; i < 10; i++) + { + byte[] buffer = new byte[16]; + random.NextBytes(buffer); + var base64String = Convert.ToBase64String(buffer); + yield return new string[] { base64String }; + } + } + } + } +} diff --git a/src/Middleware/perf/Microbenchmarks/AssemblyInfo.cs b/src/Middleware/perf/Microbenchmarks/AssemblyInfo.cs new file mode 100644 index 000000000000..32248e0d1b0a --- /dev/null +++ b/src/Middleware/perf/Microbenchmarks/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark] diff --git a/src/Middleware/perf/Microbenchmarks/HandshakeBenchmark.cs b/src/Middleware/perf/Microbenchmarks/HandshakeBenchmark.cs new file mode 100644 index 000000000000..ba736578b090 --- /dev/null +++ b/src/Middleware/perf/Microbenchmarks/HandshakeBenchmark.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BenchmarkDotNet.Attributes; + +namespace Microsoft.AspNetCore.WebSockets.Microbenchmarks +{ + public class HandshakeBenchmark + { + private string[] _requestKeys = { + "F8/qpj9RYr2/sIymdDvlmw==", + "PyQi8nyMkKnI7JKiAJ/IrA==", + "CUe0z8ItSBRtgJlPqP1+SQ==", + "w9vo1A9oM56M31qPQYKL6g==", + "+vqFGD9U04QOxKdWHrduTQ==", + "xsfuh2ZOm5O7zTzFPWJGUA==", + "TvmUzr4DgBLcDYX88kEAyw==", + "EZ5tcEOxWm7tF6adFXLSQg==", + "bkmoBhqwbbRzL8H9hvH1tQ==", + "EUwBrmmwivd5czsxz9eRzQ==", + }; + + [Benchmark(OperationsPerInvoke = 10)] + public void CreateResponseKey() + { + foreach (var key in _requestKeys) + { + HandshakeHelpers.CreateResponseKey(key); + } + } + + [Benchmark(OperationsPerInvoke = 10)] + public void IsRequestKeyValid() + { + foreach (var key in _requestKeys) + { + HandshakeHelpers.IsRequestKeyValid(key); + } + } + } +} diff --git a/src/Middleware/perf/Microbenchmarks/Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj b/src/Middleware/perf/Microbenchmarks/Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj new file mode 100644 index 000000000000..3d26d98c27fc --- /dev/null +++ b/src/Middleware/perf/Microbenchmarks/Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp3.0 + + + + + + + + From cf0c84901dfdce444cef393f90b0c13ec905bf4c Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Fri, 19 Jul 2019 22:45:39 -0700 Subject: [PATCH 04/10] cleanup --- src/Middleware/WebSockets/src/HandshakeHelpers.cs | 2 +- src/Middleware/WebSockets/src/WebSocketMiddleware.cs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index 118038809cfa..d597cb6d13e9 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -66,7 +66,7 @@ public static bool CheckSupportedWebSocketRequest(string method, IEnumerable Date: Sat, 20 Jul 2019 20:45:44 -0700 Subject: [PATCH 05/10] fb --- .../WebSockets/src/HandshakeHelpers.cs | 22 ++++++++--- .../test/UnitTests/HandshakeTests.cs | 38 +++++-------------- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index d597cb6d13e9..b86fe088784c 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -24,6 +24,15 @@ internal static class HandshakeHelpers HeaderNames.SecWebSocketVersion }; + // "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + private static ReadOnlySpan _encodedWebSocketKey => new byte[] + { + (byte)'2', (byte)'5', (byte)'8', (byte)'E', (byte)'A', (byte)'F', (byte)'A', (byte)'5', (byte)'-', + (byte)'E', (byte)'9', (byte)'1', (byte)'4', (byte)'-', (byte)'4', (byte)'7', (byte)'D', (byte)'A', + (byte)'-', (byte)'9', (byte)'5', (byte)'C', (byte)'A', (byte)'-', (byte)'C', (byte)'5', (byte)'A', + (byte)'B', (byte)'0', (byte)'D', (byte)'C', (byte)'8', (byte)'5', (byte)'B', (byte)'1', (byte)'1' + }; + // Verify Method, Upgrade, Connection, version, key, etc.. public static bool CheckSupportedWebSocketRequest(string method, IEnumerable> headers) { @@ -103,7 +112,7 @@ public static bool IsRequestKeyValid(string value) public static string CreateResponseKey(string requestKey) { // "The value of this header field is constructed by concatenating /key/, defined above in step 4 - // in Section 4.2.2, with the string "258EAFA5- E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of + // in Section 4.2.2, with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of // this concatenated value to obtain a 20-byte value and base64-encoding" // https://tools.ietf.org/html/rfc6455#section-4.2.2 @@ -114,17 +123,18 @@ public static string CreateResponseKey(string requestKey) using (var algorithm = SHA1.Create()) { - string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - // requestKey is already verified to be small (24 bytes) by 'IsRequestKeyValid()' and everything is 1:1 mapping to UTF8 bytes // so this can be hardcoded to 60 bytes for the requestKey + static websocket string Span mergedBytes = stackalloc byte[60]; - Encoding.UTF8.GetBytes(merged, mergedBytes); + Encoding.UTF8.GetBytes(requestKey, mergedBytes); + _encodedWebSocketKey.CopyTo(mergedBytes.Slice(24)); Span hashedBytes = stackalloc byte[20]; var success = algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written); - Debug.Assert(success); - Debug.Assert(written == 20); + if (!success || written != 20) + { + throw new InvalidOperationException("Could not compute the hash for the 'Sec-WebSocket-Accept' header."); + } return Convert.ToBase64String(hashedBytes); } diff --git a/src/Middleware/WebSockets/test/UnitTests/HandshakeTests.cs b/src/Middleware/WebSockets/test/UnitTests/HandshakeTests.cs index bc3f6c29f101..ec19793ddc2e 100644 --- a/src/Middleware/WebSockets/test/UnitTests/HandshakeTests.cs +++ b/src/Middleware/WebSockets/test/UnitTests/HandshakeTests.cs @@ -1,26 +1,21 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text; using Xunit; namespace Microsoft.AspNetCore.WebSockets.Tests { public class HandshakeTests { - [Theory] - [MemberData(nameof(SecWebsocketKeys))] - public void CreatesCorrectResponseKey(string key) + [Fact] + public void CreatesCorrectResponseKey() { + // Example taken from https://tools.ietf.org/html/rfc6455#section-1.3 + var key = "dGhlIHNhbXBsZSBub25jZQ=="; + var expectedResponse = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="; + var response = HandshakeHelpers.CreateResponseKey(key); - using var sha1 = SHA1.Create(); - var requestKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - var hashedBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(requestKey)); - var expectedResponse = Convert.ToBase64String(hashedBytes); Assert.Equal(expectedResponse, response); } @@ -32,28 +27,15 @@ public void AcceptsValidRequestKeys(string key) } [Theory] - [InlineData("+/UH3kSoKpZsAG1dbr0gt/s=")] - [InlineData("klbuwX3CkgUIA8x23owW")] + // 17 bytes when decoded + [InlineData("dGhpcyBpcyAxNyBieXRlcy4=")] + // 15 bytes when decoded + [InlineData("dGhpcyBpcyAxNWJ5dGVz")] [InlineData("")] [InlineData("24 length not base64 str")] public void RejectsInvalidRequestKeys(string key) { Assert.False(HandshakeHelpers.IsRequestKeyValid(key)); } - - public static IEnumerable SecWebsocketKeys - { - get - { - var random = new Random(); - for (var i = 0; i < 10; i++) - { - byte[] buffer = new byte[16]; - random.NextBytes(buffer); - var base64String = Convert.ToBase64String(buffer); - yield return new string[] { base64String }; - } - } - } } } From 453abd8208863fe1e54bbc213636ffc790f4cbf5 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Mon, 22 Jul 2019 19:35:01 -0700 Subject: [PATCH 06/10] cache SHA1 --- .../WebSockets/src/HandshakeHelpers.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index b86fe088784c..dfc07eedafb2 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Http; @@ -33,6 +32,9 @@ internal static class HandshakeHelpers (byte)'B', (byte)'0', (byte)'D', (byte)'C', (byte)'8', (byte)'5', (byte)'B', (byte)'1', (byte)'1' }; + [ThreadStatic] + private static SHA1 _algorithm; + // Verify Method, Upgrade, Connection, version, key, etc.. public static bool CheckSupportedWebSocketRequest(string method, IEnumerable> headers) { @@ -121,23 +123,25 @@ public static string CreateResponseKey(string requestKey) throw new ArgumentNullException(nameof(requestKey)); } - using (var algorithm = SHA1.Create()) + if (_algorithm == null) { - // requestKey is already verified to be small (24 bytes) by 'IsRequestKeyValid()' and everything is 1:1 mapping to UTF8 bytes - // so this can be hardcoded to 60 bytes for the requestKey + static websocket string - Span mergedBytes = stackalloc byte[60]; - Encoding.UTF8.GetBytes(requestKey, mergedBytes); - _encodedWebSocketKey.CopyTo(mergedBytes.Slice(24)); - - Span hashedBytes = stackalloc byte[20]; - var success = algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written); - if (!success || written != 20) - { - throw new InvalidOperationException("Could not compute the hash for the 'Sec-WebSocket-Accept' header."); - } + _algorithm = SHA1.Create(); + } - return Convert.ToBase64String(hashedBytes); + // requestKey is already verified to be small (24 bytes) by 'IsRequestKeyValid()' and everything is 1:1 mapping to UTF8 bytes + // so this can be hardcoded to 60 bytes for the requestKey + static websocket string + Span mergedBytes = stackalloc byte[60]; + Encoding.UTF8.GetBytes(requestKey, mergedBytes); + _encodedWebSocketKey.CopyTo(mergedBytes.Slice(24)); + + Span hashedBytes = stackalloc byte[20]; + var success = _algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written); + if (!success || written != 20) + { + throw new InvalidOperationException("Could not compute the hash for the 'Sec-WebSocket-Accept' header."); } + + return Convert.ToBase64String(hashedBytes); } } } From 6c4a760d5e5bf9c05a654e546109d2e6b2ef6fb3 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Wed, 31 Jul 2019 14:21:12 -0700 Subject: [PATCH 07/10] update --- .../WebSockets/src/HandshakeHelpers.cs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index dfc07eedafb2..c4ce795adfcd 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -99,16 +99,10 @@ public static bool IsRequestKeyValid(string value) { return false; } - try - { - Span temp = stackalloc byte[16]; - var success = Convert.TryFromBase64String(value, temp, out var written); - return written == 16 && success; - } - catch (Exception) - { - return false; - } + + Span temp = stackalloc byte[16]; + var success = Convert.TryFromBase64String(value, temp, out var written); + return success && written == 16; } public static string CreateResponseKey(string requestKey) @@ -118,11 +112,6 @@ public static string CreateResponseKey(string requestKey) // this concatenated value to obtain a 20-byte value and base64-encoding" // https://tools.ietf.org/html/rfc6455#section-4.2.2 - if (requestKey == null) - { - throw new ArgumentNullException(nameof(requestKey)); - } - if (_algorithm == null) { _algorithm = SHA1.Create(); From 3507ce952a37d2653f8e3ca2dbaf5078b9c2de84 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 23 Sep 2019 14:56:07 -0700 Subject: [PATCH 08/10] un-cache sha1 --- .../WebSockets/src/HandshakeHelpers.cs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index c4ce795adfcd..58a176348ec1 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -32,9 +32,6 @@ internal static class HandshakeHelpers (byte)'B', (byte)'0', (byte)'D', (byte)'C', (byte)'8', (byte)'5', (byte)'B', (byte)'1', (byte)'1' }; - [ThreadStatic] - private static SHA1 _algorithm; - // Verify Method, Upgrade, Connection, version, key, etc.. public static bool CheckSupportedWebSocketRequest(string method, IEnumerable> headers) { @@ -112,25 +109,23 @@ public static string CreateResponseKey(string requestKey) // this concatenated value to obtain a 20-byte value and base64-encoding" // https://tools.ietf.org/html/rfc6455#section-4.2.2 - if (_algorithm == null) + using (var algorithm = SHA1.Create()) { - _algorithm = SHA1.Create(); - } - - // requestKey is already verified to be small (24 bytes) by 'IsRequestKeyValid()' and everything is 1:1 mapping to UTF8 bytes - // so this can be hardcoded to 60 bytes for the requestKey + static websocket string - Span mergedBytes = stackalloc byte[60]; - Encoding.UTF8.GetBytes(requestKey, mergedBytes); - _encodedWebSocketKey.CopyTo(mergedBytes.Slice(24)); + // requestKey is already verified to be small (24 bytes) by 'IsRequestKeyValid()' and everything is 1:1 mapping to UTF8 bytes + // so this can be hardcoded to 60 bytes for the requestKey + static websocket string + Span mergedBytes = stackalloc byte[60]; + Encoding.UTF8.GetBytes(requestKey, mergedBytes); + _encodedWebSocketKey.CopyTo(mergedBytes.Slice(24)); + + Span hashedBytes = stackalloc byte[20]; + var success = algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written); + if (!success || written != 20) + { + throw new InvalidOperationException("Could not compute the hash for the 'Sec-WebSocket-Accept' header."); + } - Span hashedBytes = stackalloc byte[20]; - var success = _algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written); - if (!success || written != 20) - { - throw new InvalidOperationException("Could not compute the hash for the 'Sec-WebSocket-Accept' header."); + return Convert.ToBase64String(hashedBytes); } - - return Convert.ToBase64String(hashedBytes); } } } From 9ec7ce52c31ce8106fcdc92a4b64c689cd3d9e66 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 23 Sep 2019 15:55:32 -0700 Subject: [PATCH 09/10] sln --- src/Middleware/Middleware.sln | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Middleware/Middleware.sln b/src/Middleware/Middleware.sln index cdce6f321e48..b0f2023e7bf0 100644 --- a/src/Middleware/Middleware.sln +++ b/src/Middleware/Middleware.sln @@ -295,6 +295,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SpaSer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSockets.Microbenchmarks", "perf\Microbenchmarks\Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj", "{C4D624B3-749E-41D8-A43B-B304BC3885EA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Perf", "Perf", "{4623F52E-2070-4631-8DEE-7D2F48733FFD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1577,18 +1579,6 @@ Global {46B4FE62-06A1-4D54-B3E8-D8B4B3560075}.Release|x64.Build.0 = Release|Any CPU {46B4FE62-06A1-4D54-B3E8-D8B4B3560075}.Release|x86.ActiveCfg = Release|Any CPU {46B4FE62-06A1-4D54-B3E8-D8B4B3560075}.Release|x86.Build.0 = Release|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x64.ActiveCfg = Debug|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x64.Build.0 = Debug|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x86.ActiveCfg = Debug|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x86.Build.0 = Debug|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|Any CPU.Build.0 = Release|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x64.ActiveCfg = Release|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x64.Build.0 = Release|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x86.ActiveCfg = Release|Any CPU - {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x86.Build.0 = Release|Any CPU {92E11EBB-759E-4DA8-AB61-A9977D9F97D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92E11EBB-759E-4DA8-AB61-A9977D9F97D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {92E11EBB-759E-4DA8-AB61-A9977D9F97D0}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1613,6 +1603,18 @@ Global {D0CB733B-4CE8-4F6C-BBB9-548EA1A96966}.Release|x64.Build.0 = Release|Any CPU {D0CB733B-4CE8-4F6C-BBB9-548EA1A96966}.Release|x86.ActiveCfg = Release|Any CPU {D0CB733B-4CE8-4F6C-BBB9-548EA1A96966}.Release|x86.Build.0 = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x64.Build.0 = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Debug|x86.Build.0 = Debug|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|Any CPU.Build.0 = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x64.ActiveCfg = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x64.Build.0 = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x86.ActiveCfg = Release|Any CPU + {C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1739,6 +1741,7 @@ Global {46B4FE62-06A1-4D54-B3E8-D8B4B3560075} = {ACA6DDB9-7592-47CE-A740-D15BF307E9E0} {92E11EBB-759E-4DA8-AB61-A9977D9F97D0} = {ACA6DDB9-7592-47CE-A740-D15BF307E9E0} {D0CB733B-4CE8-4F6C-BBB9-548EA1A96966} = {D6FA4ABE-E685-4EDD-8B06-D8777E76B472} + {C4D624B3-749E-41D8-A43B-B304BC3885EA} = {4623F52E-2070-4631-8DEE-7D2F48733FFD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {83786312-A93B-4BB4-AB06-7C6913A59AFA} From 00d65d55685fb2940f708e5aef6421684c8c8785 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Tue, 15 Oct 2019 16:15:41 -0700 Subject: [PATCH 10/10] fix targetframework --- .../Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/perf/Microbenchmarks/Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj b/src/Middleware/perf/Microbenchmarks/Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj index 3d26d98c27fc..96a0535de79f 100644 --- a/src/Middleware/perf/Microbenchmarks/Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj +++ b/src/Middleware/perf/Microbenchmarks/Microsoft.AspNetCore.WebSockets.Microbenchmarks.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.0 + $(DefaultNetCoreTargetFramework)