diff --git a/.gitignore b/.gitignore index 34cf1a84c2..42243f01a8 100644 --- a/.gitignore +++ b/.gitignore @@ -268,3 +268,6 @@ doc/ # Deployment artifacts dist *.exe + +# BenchmarkDotNet artifacts +BenchmarkDotNet.Artifacts diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 34b49120bb..8d6fabdb44 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -12,7 +12,7 @@ - netstandard2.0 + netstandard2.1 false true diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index 2e539f2c7f..85cecdc44e 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 false true true @@ -17,9 +17,4 @@ - - - latest - - diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj index cbd3bde4f9..004ded77b1 100644 --- a/Emby.Notifications/Emby.Notifications.csproj +++ b/Emby.Notifications/Emby.Notifications.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 false true diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index b57b93a8c5..a71c751276 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -14,7 +14,7 @@ - netstandard2.0 + netstandard2.1 false true true diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index c95b00ede2..85110c21cf 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -2,11 +2,11 @@ using System; using System.Linq; using System.Text; using System.Threading.Tasks; +using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Cryptography; -using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Library { @@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(user.EasyPassword) ? null - : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash); + : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); } /// diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 2b6ae12977..60d16c8a05 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Events; using MediaBrowser.Common.Net; @@ -31,7 +32,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; -using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Library { @@ -490,7 +490,7 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(user.EasyPassword) ? null - : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash); + : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); } private void ResetInvalidLoginAttemptCount(User user) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 49308b2b1e..d4bd598e38 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2304,8 +2304,10 @@ namespace Emby.Server.Implementations.LiveTv if (provider == null) { throw new ResourceNotFoundException( - string.Format("Couldn't find provider of type: '{0}'", info.Type) - ); + string.Format( + CultureInfo.InvariantCulture, + "Couldn't find provider of type: '{0}'", + info.Type)); } await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 0c0c77cda1..024bc9a474 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; @@ -19,7 +18,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Logging; -using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Updates { @@ -455,7 +453,7 @@ namespace Emby.Server.Implementations.Updates { cancellationToken.ThrowIfCancellationRequested(); - var hash = ToHexString(md5.ComputeHash(stream)); + var hash = Hex.Encode(md5.ComputeHash(stream)); if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase)) { _logger.LogError( diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 396bdd4b71..988ac364ae 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 false true diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index b05e8c9495..2b9a64e975 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Api.UserLibrary; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -25,7 +26,6 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Net.Http.Headers; -using static MediaBrowser.Common.HexHelper; namespace MediaBrowser.Api.LiveTv { @@ -887,8 +887,9 @@ namespace MediaBrowser.Api.LiveTv { // SchedulesDirect requires a SHA1 hash of the user's password // https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token - using (SHA1 sha = SHA1.Create()) { - return ToHexString( + using (SHA1 sha = SHA1.Create()) + { + return Hex.Encode( sha.ComputeHash(Encoding.UTF8.GetBytes(str))); } } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index f653270a6c..0d62cf8c59 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -10,7 +10,7 @@ - netstandard2.0 + netstandard2.1 false true diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index dca31cd926..4c68040978 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; -using static MediaBrowser.Common.HexHelper; namespace MediaBrowser.Common.Cryptography { @@ -102,13 +101,13 @@ namespace MediaBrowser.Common.Cryptography // Check if the string also contains a salt if (splitted.Length - index == 2) { - salt = FromHexString(splitted[index++]); - hash = FromHexString(splitted[index++]); + salt = Hex.Decode(splitted[index++]); + hash = Hex.Decode(splitted[index++]); } else { salt = Array.Empty(); - hash = FromHexString(splitted[index++]); + hash = Hex.Decode(splitted[index++]); } return new PasswordHash(id, hash, salt, parameters); @@ -145,11 +144,11 @@ namespace MediaBrowser.Common.Cryptography if (Salt.Length != 0) { str.Append('$') - .Append(ToHexString(Salt)); + .Append(Hex.Encode(Salt, false)); } return str.Append('$') - .Append(ToHexString(Hash)).ToString(); + .Append(Hex.Encode(Hash, false)).ToString(); } } } diff --git a/MediaBrowser.Common/Extensions/CollectionExtensions.cs b/MediaBrowser.Common/Extensions/CollectionExtensions.cs deleted file mode 100644 index 2152243985..0000000000 --- a/MediaBrowser.Common/Extensions/CollectionExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace MediaBrowser.Common.Extensions -{ - // The MS CollectionExtensions are only available in netcoreapp - public static class CollectionExtensions - { - public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key) - { - dictionary.TryGetValue(key, out var ret); - return ret; - } - - /// - /// Copies all the elements of the current collection to the specified list - /// starting at the specified destination array index. The index is specified as a 32-bit integer. - /// - /// The current collection that is the source of the elements. - /// The list that is the destination of the elements copied from the current collection. - /// A 32-bit integer that represents the index in destination at which copying begins. - /// - public static void CopyTo(this IReadOnlyList source, IList destination, int index = 0) - { - for (int i = 0; i < source.Count; i++) - { - destination[index + i] = source[i]; - } - } - - /// - /// Copies all the elements of the current collection to the specified list - /// starting at the specified destination array index. The index is specified as a 32-bit integer. - /// - /// The current collection that is the source of the elements. - /// The list that is the destination of the elements copied from the current collection. - /// A 32-bit integer that represents the index in destination at which copying begins. - /// - public static void CopyTo(this IReadOnlyCollection source, IList destination, int index = 0) - { - foreach (T item in source) - { - destination[index++] = item; - } - } - } -} diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs new file mode 100644 index 0000000000..78a73f07e0 --- /dev/null +++ b/MediaBrowser.Common/Extensions/CopyToExtensions.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Provides CopyTo extensions methods for . + /// + public static class CollectionExtensions + { + /// + /// Copies all the elements of the current collection to the specified list + /// starting at the specified destination array index. The index is specified as a 32-bit integer. + /// + /// The current collection that is the source of the elements. + /// The list that is the destination of the elements copied from the current collection. + /// A 32-bit integer that represents the index in destination at which copying begins. + /// + public static void CopyTo(this IReadOnlyList source, IList destination, int index = 0) + { + for (int i = 0; i < source.Count; i++) + { + destination[index + i] = source[i]; + } + } + } +} diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs new file mode 100644 index 0000000000..e19a9a1f40 --- /dev/null +++ b/MediaBrowser.Common/Hex.cs @@ -0,0 +1,57 @@ +using System; +using System.Globalization; + +namespace MediaBrowser.Common +{ + /// + /// Encoding and decoding hex strings. + /// + public static class Hex + { + internal const string HexCharsLower = "0123456789abcdef"; + internal const string HexCharsUpper = "0123456789ABCDEF"; + + /// + /// Encodes bytes as a hex string. + /// + /// + /// + /// bytes as a hex string. + public static string Encode(ReadOnlySpan bytes, bool lowercase = true) + { + var hexChars = lowercase ? HexCharsLower : HexCharsUpper; + + // TODO: use string.Create when it's supports spans + // Ref: https://github.com/dotnet/corefx/issues/29120 + char[] s = new char[bytes.Length * 2]; + int j = 0; + for (int i = 0; i < bytes.Length; i++) + { + s[j++] = hexChars[bytes[i] >> 4]; + s[j++] = hexChars[bytes[i] & 0x0f]; + } + + return new string(s); + } + + /// + /// Decodes a hex string into bytes. + /// + /// The . + /// The decoded bytes. + public static byte[] Decode(ReadOnlySpan str) + { + byte[] bytes = new byte[str.Length / 2]; + int j = 0; + for (int i = 0; i < str.Length; i += 2) + { + bytes[j++] = byte.Parse( + str.Slice(i, 2), + NumberStyles.HexNumber, + CultureInfo.InvariantCulture); + } + + return bytes; + } + } +} diff --git a/MediaBrowser.Common/HexHelper.cs b/MediaBrowser.Common/HexHelper.cs deleted file mode 100644 index 61007b5b2e..0000000000 --- a/MediaBrowser.Common/HexHelper.cs +++ /dev/null @@ -1,24 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Globalization; - -namespace MediaBrowser.Common -{ - public static class HexHelper - { - public static byte[] FromHexString(string str) - { - byte[] bytes = new byte[str.Length / 2]; - for (int i = 0; i < str.Length; i += 2) - { - bytes[i / 2] = byte.Parse(str.Substring(i, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); - } - - return bytes; - } - - public static string ToHexString(byte[] bytes) - => BitConverter.ToString(bytes).Replace("-", ""); - } -} diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index cf3f6c2a44..922a62a5f3 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -21,7 +21,7 @@ - netstandard2.0 + netstandard2.1 false true true diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index c6bca25182..276eb71bcf 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -17,7 +17,7 @@ - netstandard2.0 + netstandard2.1 false true diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index a8f8da9b83..71eb62693c 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -10,7 +10,7 @@ - netstandard2.0 + netstandard2.1 false true diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 264f31f3c9..083c361d04 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 false true diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index c7ecc59c9f..9e108a18be 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -19,7 +19,7 @@ - netstandard2.0 + netstandard2.1 false true @@ -28,5 +28,5 @@ latest - + diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index a439493673..1d256d6895 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -16,7 +16,7 @@ - netstandard2.0 + netstandard2.1 false true diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index 1ca9e43bb8..ecc61a8d81 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -10,7 +10,7 @@ - netstandard2.0 + netstandard2.1 false true diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj index edfd5c9bb0..c143000b37 100644 --- a/Mono.Nat/Mono.Nat.csproj +++ b/Mono.Nat/Mono.Nat.csproj @@ -10,7 +10,7 @@ - netstandard2.0 + netstandard2.1 false diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj index 456a93aa80..9753ae9b1f 100644 --- a/RSSDP/RSSDP.csproj +++ b/RSSDP/RSSDP.csproj @@ -7,7 +7,7 @@ - netstandard2.0 + netstandard2.1 false diff --git a/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs new file mode 100644 index 0000000000..2812755978 --- /dev/null +++ b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs @@ -0,0 +1,42 @@ +using System; +using System.Globalization; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using MediaBrowser.Common; + +namespace Jellyfin.Common.Benches +{ + [MemoryDiagnoser] + public class HexDecodeBenches + { + private const int N = 1000000; + private readonly string data; + + public HexDecodeBenches() + { + var tmp = new byte[N]; + new Random(42).NextBytes(tmp); + data = Hex.Encode(tmp); + } + + public static byte[] DecodeSubString(string str) + { + byte[] bytes = new byte[str.Length / 2]; + for (int i = 0; i < str.Length; i += 2) + { + bytes[i / 2] = byte.Parse( + str.Substring(i, 2), + NumberStyles.HexNumber, + CultureInfo.InvariantCulture); + } + + return bytes; + } + + [Benchmark] + public byte[] Decode() => Hex.Decode(data); + + [Benchmark] + public byte[] DecodeSubString() => DecodeSubString(data); + } +} diff --git a/benches/Jellyfin.Common.Benches/HexEncodeBenches.cs b/benches/Jellyfin.Common.Benches/HexEncodeBenches.cs new file mode 100644 index 0000000000..e7b446cc20 --- /dev/null +++ b/benches/Jellyfin.Common.Benches/HexEncodeBenches.cs @@ -0,0 +1,29 @@ +using System; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using MediaBrowser.Common; + +namespace Jellyfin.Common.Benches +{ + [MemoryDiagnoser] + public class HexEncodeBenches + { + private const int N = 1000; + private readonly byte[] data; + + public HexEncodeBenches() + { + data = new byte[N]; + new Random(42).NextBytes(data); + } + + [Benchmark] + public string HexEncode() => Hex.Encode(data); + + [Benchmark] + public string BitConverterToString() => BitConverter.ToString(data); + + [Benchmark] + public string BitConverterToStringWithReplace() => BitConverter.ToString(data).Replace("-", ""); + } +} diff --git a/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj b/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj new file mode 100644 index 0000000000..4d5046bf90 --- /dev/null +++ b/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp3.0 + + + + + + + + + + + diff --git a/benches/Jellyfin.Common.Benches/Program.cs b/benches/Jellyfin.Common.Benches/Program.cs new file mode 100644 index 0000000000..b218b0dc10 --- /dev/null +++ b/benches/Jellyfin.Common.Benches/Program.cs @@ -0,0 +1,14 @@ +using System; +using BenchmarkDotNet.Running; + +namespace Jellyfin.Common.Benches +{ + public static class Program + { + public static void Main(string[] args) + { + _ = BenchmarkRunner.Run(); + _ = BenchmarkRunner.Run(); + } + } +} diff --git a/tests/Jellyfin.Common.Tests/HexTests.cs b/tests/Jellyfin.Common.Tests/HexTests.cs new file mode 100644 index 0000000000..5b578d38cb --- /dev/null +++ b/tests/Jellyfin.Common.Tests/HexTests.cs @@ -0,0 +1,19 @@ +using MediaBrowser.Common; +using Xunit; + +namespace Jellyfin.Common.Tests +{ + public class HexTests + { + [Theory] + [InlineData("")] + [InlineData("00")] + [InlineData("01")] + [InlineData("000102030405060708090a0b0c0d0e0f")] + [InlineData("0123456789abcdef")] + public void RoundTripTest(string data) + { + Assert.Equal(data, Hex.Encode(Hex.Decode(data))); + } + } +} diff --git a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs index 5fa86f3bd6..03523dbc45 100644 --- a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs +++ b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs @@ -1,6 +1,6 @@ +using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using Xunit; -using static MediaBrowser.Common.HexHelper; namespace Jellyfin.Common.Tests { @@ -15,8 +15,8 @@ namespace Jellyfin.Common.Tests { var pass = PasswordHash.Parse(passwordHash); Assert.Equal(id, pass.Id); - Assert.Equal(salt, ToHexString(pass.Salt)); - Assert.Equal(hash, ToHexString(pass.Hash)); + Assert.Equal(salt, Hex.Encode(pass.Salt, false)); + Assert.Equal(hash, Hex.Encode(pass.Hash, false)); } [Theory]