// 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.Diagnostics; using System.Linq; using System.Text; using Microsoft.AspNetCore.Http; namespace CodeGenerator.HttpUtilities { public class HttpUtilities { public static string GeneratedFile() { var httpMethods = new [] { new Tuple("CONNECT ", "Connect"), new Tuple("DELETE ", "Delete"), new Tuple("HEAD ", "Head"), new Tuple("PATCH ", "Patch"), new Tuple("POST ", "Post"), new Tuple("PUT ", "Put"), new Tuple("OPTIONS ", "Options"), new Tuple("TRACE ", "Trace"), new Tuple("GET ", "Get") }; return GenerateFile(httpMethods); } private static string GenerateFile(Tuple[] httpMethods) { var maskLength = (byte)Math.Ceiling(Math.Log(httpMethods.Length, 2)); var methodsInfo = httpMethods.Select(GetMethodStringAndUlongAndMaskLength).ToList(); var methodsInfoWithoutGet = methodsInfo.Where(m => m.HttpMethod != "Get".ToString()).ToList(); var methodsAsciiStringAsLong = methodsInfo.Select(m => m.AsciiStringAsLong).ToArray(); var mask = HttpUtilitiesGeneratorHelpers.SearchKeyByLookThroughMaskCombinations(methodsAsciiStringAsLong, 0, sizeof(ulong) * 8, maskLength); if (mask.HasValue == false) { throw new InvalidOperationException(string.Format("Generated {0} not found.", nameof(mask))); } var functionGetKnownMethodIndex = GetFunctionBodyGetKnownMethodIndex(mask.Value); var methodsSection = GetMethodsSection(methodsInfoWithoutGet); var masksSection = GetMasksSection(methodsInfoWithoutGet); var setKnownMethodSection = GetSetKnownMethodSection(methodsInfoWithoutGet); var methodNamesSection = GetMethodNamesSection(methodsInfo); int knownMethodsArrayLength = (int)(Math.Pow(2, maskLength) + 1); int methodNamesArrayLength = httpMethods.Length; return string.Format(@"// 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.Runtime.CompilerServices; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure {{ public static partial class HttpUtilities {{ // readonly primitive statics can be Jit'd to consts https://github.com/dotnet/coreclr/issues/1079 {0} {1} private static readonly Tuple[] _knownMethods = new Tuple[{2}]; private static readonly string[] _methodNames = new string[{3}]; static HttpUtilities() {{ {4} FillKnownMethodsGaps(); {5} }} [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetKnownMethodIndex(ulong value) {{ {6} }} }} }}", methodsSection, masksSection, knownMethodsArrayLength, methodNamesArrayLength, setKnownMethodSection, methodNamesSection, functionGetKnownMethodIndex); } private static string GetMethodsSection(List methodsInfo) { var result = new StringBuilder(); for (var index = 0; index < methodsInfo.Count; index++) { var methodInfo = methodsInfo[index]; var httpMethodFieldName = GetHttpMethodFieldName(methodInfo); result.AppendFormat(" private static readonly ulong {0} = GetAsciiStringAsLong(\"{1}\");", httpMethodFieldName, methodInfo.MethodAsciiString.Replace("\0", "\\0")); if (index < methodsInfo.Count - 1) { result.AppendLine(); } } return result.ToString(); } private static string GetMasksSection(List methodsInfo) { var distinctLengths = methodsInfo.Select(m => m.MaskLength).Distinct().ToList(); distinctLengths.Sort((t1, t2) => -t1.CompareTo(t2)); var result = new StringBuilder(); for (var index = 0; index < distinctLengths.Count; index++) { var maskBytesLength = distinctLengths[index]; var maskArray = GetMaskArray(maskBytesLength); var hexMaskString = HttpUtilitiesGeneratorHelpers.GeHexString(maskArray, "0x", ", "); var maskFieldName = GetMaskFieldName(maskBytesLength); result.AppendFormat(" private static readonly ulong {0} = GetMaskAsLong(new byte[]\r\n {{{1}}});", maskFieldName, hexMaskString); result.AppendLine(); if (index < distinctLengths.Count - 1) { result.AppendLine(); } } return result.ToString(); } private static string GetSetKnownMethodSection(List methodsInfo) { methodsInfo = methodsInfo.ToList(); methodsInfo.Sort((t1, t2) => t1.MaskLength.CompareTo(t2.MaskLength)); var result = new StringBuilder(); for (var index = 0; index < methodsInfo.Count; index++) { var methodInfo = methodsInfo[index]; var maskFieldName = GetMaskFieldName(methodInfo.MaskLength); var httpMethodFieldName = GetHttpMethodFieldName(methodInfo); result.AppendFormat(" SetKnownMethod({0}, {1}, HttpMethod.{3}, {4});", maskFieldName, httpMethodFieldName, typeof(String).Name, methodInfo.HttpMethod, methodInfo.MaskLength - 1); if (index < methodsInfo.Count - 1) { result.AppendLine(); } } return result.ToString(); } private static string GetMethodNamesSection(List methodsInfo) { methodsInfo = methodsInfo.ToList(); methodsInfo.Sort((t1, t2) => t1.HttpMethod.CompareTo(t2.HttpMethod)); var result = new StringBuilder(); for (var index = 0; index < methodsInfo.Count; index++) { var methodInfo = methodsInfo[index]; result.AppendFormat(" _methodNames[(byte)HttpMethod.{1}] = {2}.{3};", typeof(String).Name, methodInfo.HttpMethod, typeof(HttpMethods).Name, methodInfo.HttpMethod); if (index < methodsInfo.Count - 1) { result.AppendLine(); } } return result.ToString(); } private static string GetFunctionBodyGetKnownMethodIndex(ulong mask) { var shifts = HttpUtilitiesGeneratorHelpers.GetShifts(mask); var maskHexString = HttpUtilitiesGeneratorHelpers.MaskToHexString(mask); string bodyString; if (shifts.Length > 0) { var bitsCount = HttpUtilitiesGeneratorHelpers.CountBits(mask); var tmpReturn = string.Empty; foreach (var item in shifts) { if (tmpReturn.Length > 0) { tmpReturn += " | "; } tmpReturn += string.Format("(tmp >> {1})", HttpUtilitiesGeneratorHelpers.MaskToHexString(item.Mask), item.Shift); } var mask2 = (ulong)(Math.Pow(2, bitsCount) - 1); string returnString = string.Format("return ({0}) & {1};", tmpReturn, HttpUtilitiesGeneratorHelpers.MaskToHexString(mask2)); bodyString = string.Format(" const int magicNumer = {0};\r\n var tmp = (int)value & magicNumer;\r\n {1}", HttpUtilitiesGeneratorHelpers.MaskToHexString(mask), returnString); } else { bodyString = string.Format("return (int)(value & {0});", maskHexString); } return bodyString; } private static string GetHttpMethodFieldName(MethodInfo methodsInfo) { return string.Format("_http{0}MethodLong", methodsInfo.HttpMethod.ToString()); } private static string GetMaskFieldName(int nBytes) { return string.Format("_mask{0}Chars", nBytes); } private static string GetMethodString(string method) { if (method == null) { throw new ArgumentNullException(nameof(method)); } const int length = sizeof(ulong); if (method.Length > length) { throw new ArgumentException(string.Format("MethodAsciiString {0} length is greather than {1}", method, length)); } string result = method; if (result.Length == length) { return result; } if (result.Length < length) { var count = length - result.Length; for (int i = 0; i < count; i++) { result += "\0"; } } return result; } private class MethodInfo { public string MethodAsciiString; public ulong AsciiStringAsLong; public string HttpMethod; public int MaskLength; } private static MethodInfo GetMethodStringAndUlongAndMaskLength(Tuple method) { var methodString = GetMethodString(method.Item1); var asciiAsLong = GetAsciiStringAsLong(methodString); return new MethodInfo { MethodAsciiString = methodString, AsciiStringAsLong = asciiAsLong, HttpMethod = method.Item2.ToString(), MaskLength = method.Item1.Length }; } private static byte[] GetMaskArray(int n, int length = sizeof(ulong)) { var maskArray = new byte[length]; for (int i = 0; i < n; i++) { maskArray[i] = 0xff; } return maskArray; } private unsafe static ulong GetAsciiStringAsLong(string str) { Debug.Assert(str.Length == sizeof(ulong), string.Format("String must be exactly {0} (ASCII) characters long.", sizeof(ulong))); var bytes = Encoding.ASCII.GetBytes(str); fixed (byte* ptr = &bytes[0]) { return *(ulong*)ptr; } } } }