diff --git a/src/DataProtection/Cryptography.Internal/src/SafeHandles/BCryptAlgorithmHandle.cs b/src/DataProtection/Cryptography.Internal/src/SafeHandles/BCryptAlgorithmHandle.cs index 1555c99b9d70..95efc7026541 100644 --- a/src/DataProtection/Cryptography.Internal/src/SafeHandles/BCryptAlgorithmHandle.cs +++ b/src/DataProtection/Cryptography.Internal/src/SafeHandles/BCryptAlgorithmHandle.cs @@ -65,25 +65,22 @@ public BCryptKeyHandle GenerateSymmetricKey(byte* pbSecret, uint cbSecret) /// public string GetAlgorithmName() { + const int StackAllocCharSize = 128; + // First, calculate how many characters are in the name. uint byteLengthOfNameWithTerminatingNull = GetProperty(Constants.BCRYPT_ALGORITHM_NAME, null, 0); - CryptoUtil.Assert(byteLengthOfNameWithTerminatingNull % sizeof(char) == 0 && byteLengthOfNameWithTerminatingNull > sizeof(char), "byteLengthOfNameWithTerminatingNull % sizeof(char) == 0 && byteLengthOfNameWithTerminatingNull > sizeof(char)"); + CryptoUtil.Assert(byteLengthOfNameWithTerminatingNull % sizeof(char) == 0 && byteLengthOfNameWithTerminatingNull > sizeof(char) && byteLengthOfNameWithTerminatingNull <= StackAllocCharSize * sizeof(char), "byteLengthOfNameWithTerminatingNull % sizeof(char) == 0 && byteLengthOfNameWithTerminatingNull > sizeof(char) && byteLengthOfNameWithTerminatingNull <= StackAllocCharSize * sizeof(char)"); uint numCharsWithoutNull = (byteLengthOfNameWithTerminatingNull - 1) / sizeof(char); if (numCharsWithoutNull == 0) { - return String.Empty; // degenerate case + return string.Empty; // degenerate case } - // Allocate a string object and write directly into it (CLR team approves of this mechanism). - string retVal = new String((char)0, checked((int)numCharsWithoutNull)); - uint numBytesCopied; - fixed (char* pRetVal = retVal) - { - numBytesCopied = GetProperty(Constants.BCRYPT_ALGORITHM_NAME, pRetVal, byteLengthOfNameWithTerminatingNull); - } + char* pBuffer = stackalloc char[StackAllocCharSize]; + uint numBytesCopied = GetProperty(Constants.BCRYPT_ALGORITHM_NAME, pBuffer, byteLengthOfNameWithTerminatingNull); CryptoUtil.Assert(numBytesCopied == byteLengthOfNameWithTerminatingNull, "numBytesCopied == byteLengthOfNameWithTerminatingNull"); - return retVal; + return new string(pBuffer, 0, (int)numCharsWithoutNull); } /// diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs index ddb2c9edc04c..0f459aef1f50 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -28,8 +28,7 @@ internal static partial class HttpUtilities private const ulong _http11VersionLong = 3543824036068086856; // GetAsciiStringAsLong("HTTP/1.1"); const results in better codegen private static readonly UTF8EncodingSealed DefaultRequestHeaderEncoding = new UTF8EncodingSealed(); - private static readonly SpanAction _getHeaderName = GetHeaderName; - private static readonly SpanAction _getAsciiStringNonNullCharacters = GetAsciiStringNonNullCharacters; + private static readonly SpanAction s_getHeaderName = GetHeaderName; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void SetKnownMethod(ulong mask, ulong knownMethodUlong, HttpMethod knownMethod, int length) @@ -86,7 +85,7 @@ public static unsafe string GetHeaderName(this ReadOnlySpan span) fixed (byte* source = &MemoryMarshal.GetReference(span)) { - return string.Create(span.Length, new IntPtr(source), _getHeaderName); + return string.Create(span.Length, new IntPtr(source), s_getHeaderName); } } @@ -94,7 +93,7 @@ private static unsafe void GetHeaderName(Span buffer, IntPtr state) { fixed (char* output = &MemoryMarshal.GetReference(buffer)) { - // This version if AsciiUtilities returns null if there are any null (0 byte) characters + // This version of AsciiUtilities returns null if there are any null (0 byte) characters // in the string if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length)) { @@ -104,38 +103,11 @@ private static unsafe void GetHeaderName(Span buffer, IntPtr state) } public static string GetAsciiStringNonNullCharacters(this Span span) - => GetAsciiStringNonNullCharacters((ReadOnlySpan)span); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe string GetAsciiStringNonNullCharacters(this ReadOnlySpan span) - { - if (span.IsEmpty) - { - return string.Empty; - } - - fixed (byte* source = &MemoryMarshal.GetReference(span)) - { - return string.Create(span.Length, new IntPtr(source), _getAsciiStringNonNullCharacters); - } - } + => StringUtilities.GetAsciiStringNonNullCharacters(span); public static string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan span) => StringUtilities.GetAsciiOrUTF8StringNonNullCharacters(span, DefaultRequestHeaderEncoding); - private static unsafe void GetAsciiStringNonNullCharacters(Span buffer, IntPtr state) - { - fixed (char* output = &MemoryMarshal.GetReference(buffer)) - { - // StringUtilities.TryGetAsciiString returns null if there are any null (0 byte) characters - // in the string - if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length)) - { - throw new InvalidOperationException(); - } - } - } - public static string GetRequestHeaderString(this ReadOnlySpan span, string name, Func encodingSelector) { if (ReferenceEquals(KestrelServerOptions.DefaultRequestHeaderEncodingSelector, encodingSelector)) diff --git a/src/Shared/Http2cat/Http2Utilities.cs b/src/Shared/Http2cat/Http2Utilities.cs index 07c81d49c22d..bd49bb13f746 100644 --- a/src/Shared/Http2cat/Http2Utilities.cs +++ b/src/Shared/Http2cat/Http2Utilities.cs @@ -143,64 +143,7 @@ public Http2Utilities(ConnectionContext clientConnectionContext, ILogger logger, void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) { - _decodedHeaders[GetAsciiStringNonNullCharacters(name)] = GetAsciiOrUTF8StringNonNullCharacters(value); - } - - public unsafe string GetAsciiStringNonNullCharacters(ReadOnlySpan span) - { - if (span.IsEmpty) - { - return string.Empty; - } - - var asciiString = new string('\0', span.Length); - - fixed (char* output = asciiString) - fixed (byte* buffer = span) - { - // This version if AsciiUtilities returns null if there are any null (0 byte) characters - // in the string - if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) - { - throw new InvalidOperationException(); - } - } - return asciiString; - } - - public unsafe string GetAsciiOrUTF8StringNonNullCharacters(ReadOnlySpan span) - { - if (span.IsEmpty) - { - return string.Empty; - } - - var resultString = new string('\0', span.Length); - - fixed (char* output = resultString) - fixed (byte* buffer = span) - { - // This version if AsciiUtilities returns null if there are any null (0 byte) characters - // in the string - if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) - { - // null characters are considered invalid - if (span.IndexOf((byte)0) != -1) - { - throw new InvalidOperationException(); - } - - try - { - resultString = HeaderValueEncoding.GetString(buffer, span.Length); - } - catch (DecoderFallbackException) - { - throw new InvalidOperationException(); - } - } - } - return resultString; + _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters(HeaderValueEncoding); } void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } diff --git a/src/Shared/ServerInfrastructure/StringUtilities.cs b/src/Shared/ServerInfrastructure/StringUtilities.cs index 2e9c4cee8936..87fb890d5da6 100644 --- a/src/Shared/ServerInfrastructure/StringUtilities.cs +++ b/src/Shared/ServerInfrastructure/StringUtilities.cs @@ -17,10 +17,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { internal static class StringUtilities { - private static readonly SpanAction s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiStringNonNullCharacters; - - private static string GetAsciiOrUTF8StringNonNullCharacters(this Span span, Encoding defaultEncoding) - => GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan)span, defaultEncoding); + private static readonly SpanAction s_getAsciiOrUTF8StringNonNullCharacters = GetAsciiStringNonNullCharactersWithMarker; + private static readonly SpanAction s_getAsciiStringNonNullCharacters = GetAsciiStringNonNullCharacters; + private static readonly SpanAction s_getLatin1StringNonNullCharacters = GetLatin1StringNonNullCharacters; + private static readonly SpanAction s_populateSpanWithHexSuffix = PopulateSpanWithHexSuffix; public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan span, Encoding defaultEncoding) { @@ -31,7 +31,7 @@ public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlyS fixed (byte* source = &MemoryMarshal.GetReference(span)) { - var resultString = string.Create(span.Length, new IntPtr(source), s_getAsciiOrUtf8StringNonNullCharacters); + var resultString = string.Create(span.Length, (IntPtr)source, s_getAsciiOrUTF8StringNonNullCharacters); // If resultString is marked, perform UTF-8 encoding if (resultString[0] == '\0') @@ -56,11 +56,11 @@ public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlyS } } - private static unsafe void GetAsciiStringNonNullCharacters(Span buffer, IntPtr state) + private static unsafe void GetAsciiStringNonNullCharactersWithMarker(Span buffer, IntPtr state) { fixed (char* output = &MemoryMarshal.GetReference(buffer)) { - // This version if AsciiUtilities returns false if there are any null ('\0') or non-Ascii + // This version of AsciiUtilities returns false if there are any null ('\0') or non-Ascii // character (> 127) in the string. if (!TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length)) { @@ -70,6 +70,32 @@ private static unsafe void GetAsciiStringNonNullCharacters(Span buffer, In } } + public static unsafe string GetAsciiStringNonNullCharacters(this ReadOnlySpan span) + { + if (span.IsEmpty) + { + return string.Empty; + } + + fixed (byte* source = &MemoryMarshal.GetReference(span)) + { + return string.Create(span.Length, (IntPtr)source, s_getAsciiStringNonNullCharacters); + } + } + + private static unsafe void GetAsciiStringNonNullCharacters(Span buffer, IntPtr state) + { + fixed (char* output = &MemoryMarshal.GetReference(buffer)) + { + // This version of AsciiUtilities returns false if there are any null ('\0') or non-Ascii + // character (> 127) in the string. + if (!TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length)) + { + throw new InvalidOperationException(); + } + } + } + public static unsafe string GetLatin1StringNonNullCharacters(this ReadOnlySpan span) { if (span.IsEmpty) @@ -77,20 +103,22 @@ public static unsafe string GetLatin1StringNonNullCharacters(this ReadOnlySpan buffer, IntPtr state) + { + fixed (char* output = &MemoryMarshal.GetReference(buffer)) { - // This returns false if there are any null (0 byte) characters in the string. - if (!TryGetLatin1String(buffer, output, span.Length)) + if (!TryGetLatin1String((byte*)state.ToPointer(), output, buffer.Length)) { // null characters are considered invalid throw new InvalidOperationException(); } } - - return resultString; } [MethodImpl(MethodImplOptions.AggressiveOptimization)] @@ -299,7 +327,7 @@ public static unsafe bool TryGetLatin1String(byte* input, char* output, int coun // If Vector not-accelerated or remaining less than vector size if (!Vector.IsHardwareAccelerated || input > end - Vector.Count) { - if (IntPtr.Size == 8) // Use Intrinsic switch for branch elimination + if (Environment.Is64BitProcess) // Use Intrinsic switch for branch elimination { // 64-bit: Loop longs by default while (input <= end - sizeof(long)) @@ -403,10 +431,6 @@ public static bool BytesOrdinalEqualsStringAndAscii(string previousValue, ReadOn goto NotEqual; } - // Use IntPtr values rather than int, to avoid unnecessary 32 -> 64 movs on 64-bit. - // Unfortunately this means we also need to cast to byte* for comparisons as IntPtr doesn't - // support operator comparisons (e.g. <=, >, etc). - // // Note: Pointer comparison is unsigned, so we use the compare pattern (offset + length <= count) // rather than (offset <= count - length) which we'd do with signed comparison to avoid overflow. // This isn't problematic as we know the maximum length is max string length (from test above) @@ -666,7 +690,6 @@ private static bool IsValidHeaderString(string value) return false; } } - private static readonly SpanAction s_populateSpanWithHexSuffix = PopulateSpanWithHexSuffix; /// /// A faster version of String.Concat(, , .ToString("X8"))