-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use span based metadata parser on Core 3.1 or newer
- Loading branch information
Showing
12 changed files
with
279 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using BenchmarkDotNet.Attributes; | ||
using tusdotnet.Parsers; | ||
using tusdotnet.Parsers.MetadataParserHelpers; | ||
|
||
namespace tusdotnet.benchmark.Benchmarks | ||
{ | ||
[MemoryDiagnoser] | ||
public class MetadataParser | ||
{ | ||
private const string UploadMetadata = "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,othermeta c29tZSBvdGhlciBkYXRh,utf8key wrbDgMSaxafMsw==,emptyKey,loremipsum TG9yZW0gSXBzdW0gaXMgc2ltcGx5IGR1bW15IHRleHQgb2YgdGhlIHByaW50aW5nIGFuZCB0eXBlc2V0dGluZyBpbmR1c3RyeS4gTG9yZW0gSXBzdW0gaGFzIGJlZW4gdGhlIGluZHVzdHJ5J3Mgc3RhbmRhcmQgZHVtbXkgdGV4dCBldmVyIHNpbmNlIHRoZSAxNTAwcywgd2hlbiBhbiB1bmtub3duIHByaW50ZXIgdG9vayBhIGdhbGxleSBvZiB0eXBlIGFuZCBzY3JhbWJsZWQgaXQgdG8gbWFrZSBhIHR5cGUgc3BlY2ltZW4gYm9vay4gSXQgaGFzIHN1cnZpdmVkIG5vdCBvbmx5IGZpdmUgY2VudHVyaWVzLCBidXQgYWxzbyB0aGUgbGVhcCBpbnRvIGVsZWN0cm9uaWMgdHlwZXNldHRpbmcsIHJlbWFpbmluZyBlc3NlbnRpYWxseSB1bmNoYW5nZWQuIEl0IHdhcyBwb3B1bGFyaXNlZCBpbiB0aGUgMTk2MHMgd2l0aCB0aGUgcmVsZWFzZSBvZiBMZXRyYXNldCBzaGVldHMgY29udGFpbmluZyBMb3JlbSBJcHN1bSBwYXNzYWdlcywgYW5kIG1vcmUgcmVjZW50bHkgd2l0aCBkZXNrdG9wIHB1Ymxpc2hpbmcgc29mdHdhcmUgbGlrZSBBbGR1cyBQYWdlTWFrZXIgaW5jbHVkaW5nIHZlcnNpb25zIG9mIExvcmVtIElwc3VtLg=="; | ||
|
||
[Benchmark(Baseline = true)] | ||
public MetadataParserResult MetadataParserStringBased_Test() | ||
{ | ||
return MetadataParserStringBased.ParseAndValidate(Models.MetadataParsingStrategy.AllowEmptyValues, UploadMetadata); | ||
} | ||
|
||
[Benchmark] | ||
public MetadataParserResult MetadataParserSpanBased_Test() | ||
{ | ||
return MetadataParserSpanBased.ParseAndValidate(UploadMetadata); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
Source/tusdotnet/Extensions/Internal/ReadOnlySpanExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#if NETCOREAPP3_1_OR_GREATER | ||
|
||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace tusdotnet.Extensions.Internal | ||
{ | ||
internal static class ReadOnlySpanExtensions | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static int Count(this ReadOnlySpan<char> span, char characterToFind) | ||
{ | ||
var numberOfCharacters = 0; | ||
for (int i = 0; i < span.Length; i++) | ||
{ | ||
if (span[i] == characterToFind) | ||
{ | ||
numberOfCharacters++; | ||
} | ||
} | ||
|
||
return numberOfCharacters; | ||
} | ||
} | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...dotnet/Parsers/IInternalMetadataParser.cs → ...aParserHelpers/IInternalMetadataParser.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
Source/tusdotnet/Parsers/MetadataParserHelpers/MetadataParserErrorTexts.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.Runtime.CompilerServices; | ||
using tusdotnet.Constants; | ||
|
||
namespace tusdotnet.Parsers.MetadataParserHelpers | ||
{ | ||
internal class MetadataParserErrorTexts | ||
{ | ||
internal const string INVALID_FORMAT_ALLOW_EMPTY_VALUES = $"Header {HeaderConstants.UploadMetadata}: The Upload-Metadata request and response header MUST consist of one or more comma - separated key - value pairs. The key and value MUST be separated by a space.The key MUST NOT contain spaces and commas and MUST NOT be empty. The key SHOULD be ASCII encoded and the value MUST be Base64 encoded. All keys MUST be unique. The value MAY be empty. In these cases, the space, which would normally separate the key and the value, MAY be left out."; | ||
internal const string INVALID_FORMAT_ORIGINAL = $"Header {HeaderConstants.UploadMetadata}: The Upload-Metadata request and response header MUST consist of one or more comma - separated key - value pairs. The key and value MUST be separated by a space.The key MUST NOT contain spaces and commas and MUST NOT be empty. The key SHOULD be ASCII encoded and the value MUST be Base64 encoded. All keys MUST be unique."; | ||
internal const string KEY_EMPTY = $"Header {HeaderConstants.UploadMetadata}: Key must not be empty"; | ||
internal const string DUPLICATE_KEY_FOUND = $"Header {HeaderConstants.UploadMetadata}: Duplicate keys are not allowed"; | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static string InvalidBase64Value(string metadataKey) => $"Header {HeaderConstants.UploadMetadata}: Value for {metadataKey} is not properly encoded using base64"; | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
Source/tusdotnet/Parsers/MetadataParserHelpers/MetadataParserSpanBased.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
#if NETCOREAPP3_1_OR_GREATER | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using tusdotnet.Extensions.Internal; | ||
using tusdotnet.Models; | ||
|
||
namespace tusdotnet.Parsers.MetadataParserHelpers | ||
{ | ||
internal class MetadataParserSpanBased | ||
{ | ||
internal static MetadataParserResult ParseAndValidate(string uploadMetadataHeaderValue) | ||
{ | ||
if (string.IsNullOrEmpty(uploadMetadataHeaderValue)) | ||
return MetadataParserResult.FromResult(new Dictionary<string, Metadata>()); | ||
|
||
var span = uploadMetadataHeaderValue.AsSpan(); | ||
var result = new Dictionary<string, Metadata>(); | ||
|
||
int indexOfNextPair = -1; | ||
while (!span.IsEmpty) | ||
{ | ||
indexOfNextPair = span.IndexOf(','); | ||
var pair = indexOfNextPair == -1 ? span : span[0..indexOfNextPair].TrimEnd(); | ||
|
||
var indexOfSpaceInPair = pair.IndexOf(' '); | ||
|
||
ReadOnlySpan<char> key; | ||
ReadOnlySpan<char> value; | ||
|
||
if (indexOfSpaceInPair == -1) | ||
{ | ||
key = pair; | ||
value = ReadOnlySpan<char>.Empty; | ||
} | ||
else | ||
{ | ||
key = pair[0..indexOfSpaceInPair].TrimEnd(); | ||
value = pair[(indexOfSpaceInPair + 1)..].TrimEnd(); | ||
} | ||
|
||
if (value.IndexOf(' ') > -1) | ||
{ | ||
return MetadataParserResult.FromError(MetadataParserErrorTexts.INVALID_FORMAT_ALLOW_EMPTY_VALUES); | ||
} | ||
|
||
if (key.IsEmpty) | ||
{ | ||
return MetadataParserResult.FromError(MetadataParserErrorTexts.KEY_EMPTY); | ||
} | ||
|
||
var keyString = key.ToString(); | ||
|
||
if (result.ContainsKey(keyString)) | ||
{ | ||
return MetadataParserResult.FromError(MetadataParserErrorTexts.DUPLICATE_KEY_FOUND); | ||
} | ||
|
||
byte[] decodedValue = null; | ||
if (!value.IsEmpty) | ||
{ | ||
var validBase64 = false; | ||
(validBase64, decodedValue) = IsBase64String(value); | ||
if (!validBase64) | ||
{ | ||
return MetadataParserResult.FromError(MetadataParserErrorTexts.InvalidBase64Value(keyString)); | ||
} | ||
} | ||
|
||
result.Add(keyString, Metadata.FromBytes(decodedValue)); | ||
|
||
span = indexOfNextPair == -1 ? ReadOnlySpan<char>.Empty : span[(indexOfNextPair + 1)..]; | ||
} | ||
|
||
return MetadataParserResult.FromResult(result); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
static (bool, byte[]) IsBase64String(ReadOnlySpan<char> value) | ||
{ | ||
var byteLength = (3 * (value.Length / 4)) - value.Count('='); | ||
var bytes = new byte[byteLength]; | ||
return (Convert.TryFromBase64Chars(value, bytes, out var written), bytes); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#endif |
46 changes: 46 additions & 0 deletions
46
Source/tusdotnet/Parsers/MetadataParserHelpers/MetadataParserStringBased.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using tusdotnet.Models; | ||
|
||
namespace tusdotnet.Parsers.MetadataParserHelpers | ||
{ | ||
internal class MetadataParserStringBased | ||
{ | ||
internal static MetadataParserResult ParseAndValidate(MetadataParsingStrategy strategy, string uploadMetadataHeaderValue) | ||
{ | ||
var parser = GetParser(strategy); | ||
|
||
if (string.IsNullOrWhiteSpace(uploadMetadataHeaderValue)) | ||
{ | ||
return parser.GetResultForEmptyHeader(); | ||
} | ||
|
||
var splitMetadataHeader = uploadMetadataHeaderValue.Split(','); | ||
var parsedMetadata = new Dictionary<string, Metadata>(splitMetadataHeader.Length); | ||
|
||
foreach (var pair in splitMetadataHeader) | ||
{ | ||
var singleItemParseResult = parser.ParseSingleItem(pair, parsedMetadata.Keys); | ||
|
||
if (singleItemParseResult.Success) | ||
{ | ||
var parsedKeyAndValue = singleItemParseResult.Metadata.First(); | ||
parsedMetadata.Add(parsedKeyAndValue.Key, parsedKeyAndValue.Value); | ||
} | ||
else | ||
{ | ||
return singleItemParseResult; | ||
} | ||
} | ||
|
||
return MetadataParserResult.FromResult(parsedMetadata); | ||
} | ||
|
||
private static IInternalMetadataParser GetParser(MetadataParsingStrategy strategy) | ||
{ | ||
return strategy == MetadataParsingStrategy.Original | ||
? OriginalMetadataParser.Instance | ||
: AllowEmptyValuesMetadataParser.Instance; | ||
} | ||
} | ||
} |
Oops, something went wrong.