H/QPackEncoder overloads accepting Encoding (#24448)

This commit is contained in:
Miha Zupan 2020-07-30 22:11:58 +02:00 committed by GitHub
parent b9e663bffb
commit e3501ddd4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 186 additions and 73 deletions

View File

@ -4,6 +4,7 @@
#nullable enable #nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
namespace System.Net.Http.HPack namespace System.Net.Http.HPack
{ {
@ -96,7 +97,7 @@ namespace System.Net.Http.HPack
if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) if (IntegerEncoder.Encode(index, 4, destination, out int indexLength))
{ {
Debug.Assert(indexLength >= 1); Debug.Assert(indexLength >= 1);
if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength)) if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength))
{ {
bytesWritten = indexLength + nameLength; bytesWritten = indexLength + nameLength;
return true; return true;
@ -128,7 +129,7 @@ namespace System.Net.Http.HPack
if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) if (IntegerEncoder.Encode(index, 4, destination, out int indexLength))
{ {
Debug.Assert(indexLength >= 1); Debug.Assert(indexLength >= 1);
if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength)) if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength))
{ {
bytesWritten = indexLength + nameLength; bytesWritten = indexLength + nameLength;
return true; return true;
@ -160,7 +161,7 @@ namespace System.Net.Http.HPack
if (IntegerEncoder.Encode(index, 6, destination, out int indexLength)) if (IntegerEncoder.Encode(index, 6, destination, out int indexLength))
{ {
Debug.Assert(indexLength >= 1); Debug.Assert(indexLength >= 1);
if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength)) if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength))
{ {
bytesWritten = indexLength + nameLength; bytesWritten = indexLength + nameLength;
return true; return true;
@ -276,7 +277,7 @@ namespace System.Net.Http.HPack
{ {
destination[0] = mask; destination[0] = mask;
if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) && if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) &&
EncodeStringLiteral(value, destination.Slice(1 + nameLength), out int valueLength)) EncodeStringLiteral(value, valueEncoding: null, destination.Slice(1 + nameLength), out int valueLength))
{ {
bytesWritten = 1 + nameLength + valueLength; bytesWritten = 1 + nameLength + valueLength;
return true; return true;
@ -289,6 +290,11 @@ namespace System.Net.Http.HPack
/// <summary>Encodes a "Literal Header Field without Indexing - New Name".</summary> /// <summary>Encodes a "Literal Header Field without Indexing - New Name".</summary>
public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, ReadOnlySpan<string> values, string separator, Span<byte> destination, out int bytesWritten) public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, ReadOnlySpan<string> values, string separator, Span<byte> destination, out int bytesWritten)
{
return EncodeLiteralHeaderFieldWithoutIndexingNewName(name, values, separator, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, ReadOnlySpan<string> values, string separator, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{ {
// From https://tools.ietf.org/html/rfc7541#section-6.2.2 // From https://tools.ietf.org/html/rfc7541#section-6.2.2
// ------------------------------------------------------ // ------------------------------------------------------
@ -309,7 +315,7 @@ namespace System.Net.Http.HPack
{ {
destination[0] = 0; destination[0] = 0;
if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) && if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) &&
EncodeStringLiterals(values, separator, destination.Slice(1 + nameLength), out int valueLength)) EncodeStringLiterals(values, separator, valueEncoding, destination.Slice(1 + nameLength), out int valueLength))
{ {
bytesWritten = 1 + nameLength + valueLength; bytesWritten = 1 + nameLength + valueLength;
return true; return true;
@ -395,27 +401,20 @@ namespace System.Net.Http.HPack
return false; return false;
} }
private static bool EncodeStringLiteralValue(string value, Span<byte> destination, out int bytesWritten) private static void EncodeValueStringPart(string value, Span<byte> destination)
{ {
if (value.Length <= destination.Length) Debug.Assert(destination.Length >= value.Length);
{
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if ((c & 0xFF80) != 0)
{
throw new HttpRequestException(SR.net_http_request_invalid_char_encoding);
}
destination[i] = (byte)c; for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if ((c & 0xFF80) != 0)
{
throw new HttpRequestException(SR.net_http_request_invalid_char_encoding);
} }
bytesWritten = value.Length; destination[i] = (byte)c;
return true;
} }
bytesWritten = 0;
return false;
} }
public static bool EncodeStringLiteral(ReadOnlySpan<byte> value, Span<byte> destination, out int bytesWritten) public static bool EncodeStringLiteral(ReadOnlySpan<byte> value, Span<byte> destination, out int bytesWritten)
@ -453,6 +452,11 @@ namespace System.Net.Http.HPack
} }
public static bool EncodeStringLiteral(string value, Span<byte> destination, out int bytesWritten) public static bool EncodeStringLiteral(string value, Span<byte> destination, out int bytesWritten)
{
return EncodeStringLiteral(value, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeStringLiteral(string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{ {
// From https://tools.ietf.org/html/rfc7541#section-5.2 // From https://tools.ietf.org/html/rfc7541#section-5.2
// ------------------------------------------------------ // ------------------------------------------------------
@ -466,13 +470,28 @@ namespace System.Net.Http.HPack
if (destination.Length != 0) if (destination.Length != 0)
{ {
destination[0] = 0; // TODO: Use Huffman encoding destination[0] = 0; // TODO: Use Huffman encoding
if (IntegerEncoder.Encode(value.Length, 7, destination, out int integerLength))
int encodedStringLength = valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1)
? value.Length
: valueEncoding.GetByteCount(value);
if (IntegerEncoder.Encode(encodedStringLength, 7, destination, out int integerLength))
{ {
Debug.Assert(integerLength >= 1); Debug.Assert(integerLength >= 1);
destination = destination.Slice(integerLength);
if (EncodeStringLiteralValue(value, destination.Slice(integerLength), out int valueLength)) if (encodedStringLength <= destination.Length)
{ {
bytesWritten = integerLength + valueLength; if (valueEncoding is null)
{
EncodeValueStringPart(value, destination);
}
else
{
int written = valueEncoding.GetBytes(value, destination);
Debug.Assert(written == encodedStringLength);
}
bytesWritten = integerLength + encodedStringLength;
return true; return true;
} }
} }
@ -502,56 +521,87 @@ namespace System.Net.Http.HPack
} }
public static bool EncodeStringLiterals(ReadOnlySpan<string> values, string? separator, Span<byte> destination, out int bytesWritten) public static bool EncodeStringLiterals(ReadOnlySpan<string> values, string? separator, Span<byte> destination, out int bytesWritten)
{
return EncodeStringLiterals(values, separator, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeStringLiterals(ReadOnlySpan<string> values, string? separator, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{ {
bytesWritten = 0; bytesWritten = 0;
if (values.Length == 0) if (values.Length == 0)
{ {
return EncodeStringLiteral("", destination, out bytesWritten); return EncodeStringLiteral("", valueEncoding: null, destination, out bytesWritten);
} }
else if (values.Length == 1) else if (values.Length == 1)
{ {
return EncodeStringLiteral(values[0], destination, out bytesWritten); return EncodeStringLiteral(values[0], valueEncoding, destination, out bytesWritten);
} }
if (destination.Length != 0) if (destination.Length != 0)
{ {
int valueLength = 0; Debug.Assert(separator != null);
int valueLength;
// Calculate length of all parts and separators. // Calculate length of all parts and separators.
foreach (string part in values) if (valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1))
{ {
valueLength = checked((int)(valueLength + part.Length)); valueLength = checked((int)(values.Length - 1) * separator.Length);
foreach (string part in values)
{
valueLength = checked((int)(valueLength + part.Length));
}
}
else
{
valueLength = checked((int)(values.Length - 1) * valueEncoding.GetByteCount(separator));
foreach (string part in values)
{
valueLength = checked((int)(valueLength + valueEncoding.GetByteCount(part)));
}
} }
Debug.Assert(separator != null);
valueLength = checked((int)(valueLength + (values.Length - 1) * separator.Length));
destination[0] = 0; destination[0] = 0;
if (IntegerEncoder.Encode(valueLength, 7, destination, out int integerLength)) if (IntegerEncoder.Encode(valueLength, 7, destination, out int integerLength))
{ {
Debug.Assert(integerLength >= 1); Debug.Assert(integerLength >= 1);
destination = destination.Slice(integerLength);
int encodedLength = 0; if (destination.Length >= valueLength)
for (int j = 0; j < values.Length; j++)
{ {
if (j != 0 && !EncodeStringLiteralValue(separator, destination.Slice(integerLength), out encodedLength)) if (valueEncoding is null)
{ {
return false; string value = values[0];
EncodeValueStringPart(value, destination);
destination = destination.Slice(value.Length);
for (int i = 1; i < values.Length; i++)
{
EncodeValueStringPart(separator, destination);
destination = destination.Slice(separator.Length);
value = values[i];
EncodeValueStringPart(value, destination);
destination = destination.Slice(value.Length);
}
}
else
{
int written = valueEncoding.GetBytes(values[0], destination);
destination = destination.Slice(written);
for (int i = 1; i < values.Length; i++)
{
written = valueEncoding.GetBytes(separator, destination);
destination = destination.Slice(written);
written = valueEncoding.GetBytes(values[i], destination);
destination = destination.Slice(written);
}
} }
integerLength += encodedLength; bytesWritten = integerLength + valueLength;
return true;
if (!EncodeStringLiteralValue(values[j], destination.Slice(integerLength), out encodedLength))
{
return false;
}
integerLength += encodedLength;
} }
bytesWritten = integerLength;
return true;
} }
} }

View File

@ -5,6 +5,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Net.Http.HPack; using System.Net.Http.HPack;
using System.Text;
namespace System.Net.Http.QPack namespace System.Net.Http.QPack
{ {
@ -59,6 +60,11 @@ namespace System.Net.Http.QPack
// - T is constant 1 here, indicating a static table reference. // - T is constant 1 here, indicating a static table reference.
// - H is constant 0 here, as we do not yet perform Huffman coding. // - H is constant 0 here, as we do not yet perform Huffman coding.
public static bool EncodeLiteralHeaderFieldWithStaticNameReference(int index, string value, Span<byte> destination, out int bytesWritten) public static bool EncodeLiteralHeaderFieldWithStaticNameReference(int index, string value, Span<byte> destination, out int bytesWritten)
{
return EncodeLiteralHeaderFieldWithStaticNameReference(index, value, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeLiteralHeaderFieldWithStaticNameReference(int index, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{ {
// Requires at least two bytes (one for name reference header, one for value length) // Requires at least two bytes (one for name reference header, one for value length)
if (destination.Length >= 2) if (destination.Length >= 2)
@ -68,7 +74,7 @@ namespace System.Net.Http.QPack
{ {
destination = destination.Slice(headerBytesWritten); destination = destination.Slice(headerBytesWritten);
if (EncodeValueString(value, destination, out int valueBytesWritten)) if (EncodeValueString(value, valueEncoding, destination, out int valueBytesWritten))
{ {
bytesWritten = headerBytesWritten + valueBytesWritten; bytesWritten = headerBytesWritten + valueBytesWritten;
return true; return true;
@ -81,7 +87,7 @@ namespace System.Net.Http.QPack
} }
/// <summary> /// <summary>
/// Encodes just the name part of a Literal Header Field With Static Name Reference. Must call <see cref="EncodeValueString(string, Span{byte}, out int)"/> after to encode the header's value. /// Encodes just the name part of a Literal Header Field With Static Name Reference. Must call <see cref="EncodeValueString(string, Encoding?, Span{byte}, out int)"/> after to encode the header's value.
/// </summary> /// </summary>
public static byte[] EncodeLiteralHeaderFieldWithStaticNameReferenceToArray(int index) public static byte[] EncodeLiteralHeaderFieldWithStaticNameReferenceToArray(int index)
{ {
@ -119,7 +125,12 @@ namespace System.Net.Http.QPack
// - H is constant 0 here, as we do not yet perform Huffman coding. // - H is constant 0 here, as we do not yet perform Huffman coding.
public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, string value, Span<byte> destination, out int bytesWritten) public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, string value, Span<byte> destination, out int bytesWritten)
{ {
if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(value, destination.Slice(nameLength), out int valueLength)) return EncodeLiteralHeaderFieldWithoutNameReference(name, value, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, string value, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{
if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(value, valueEncoding, destination.Slice(nameLength), out int valueLength))
{ {
bytesWritten = nameLength + valueLength; bytesWritten = nameLength + valueLength;
return true; return true;
@ -136,7 +147,12 @@ namespace System.Net.Http.QPack
/// </summary> /// </summary>
public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, ReadOnlySpan<string> values, string valueSeparator, Span<byte> destination, out int bytesWritten) public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, ReadOnlySpan<string> values, string valueSeparator, Span<byte> destination, out int bytesWritten)
{ {
if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(values, valueSeparator, destination.Slice(nameLength), out int valueLength)) return EncodeLiteralHeaderFieldWithoutNameReference(name, values, valueSeparator, valueEncoding: null, destination, out bytesWritten);
}
public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, ReadOnlySpan<string> values, string valueSeparator, Encoding? valueEncoding, Span<byte> destination, out int bytesWritten)
{
if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(values, valueSeparator, valueEncoding, destination.Slice(nameLength), out int valueLength))
{ {
bytesWritten = nameLength + valueLength; bytesWritten = nameLength + valueLength;
return true; return true;
@ -147,7 +163,7 @@ namespace System.Net.Http.QPack
} }
/// <summary> /// <summary>
/// Encodes just the value part of a Literawl Header Field Without Static Name Reference. Must call <see cref="EncodeValueString(string, Span{byte}, out int)"/> after to encode the header's value. /// Encodes just the value part of a Literawl Header Field Without Static Name Reference. Must call <see cref="EncodeValueString(string, Encoding?, Span{byte}, out int)"/> after to encode the header's value.
/// </summary> /// </summary>
public static byte[] EncodeLiteralHeaderFieldWithoutNameReferenceToArray(string name) public static byte[] EncodeLiteralHeaderFieldWithoutNameReferenceToArray(string name)
{ {
@ -169,19 +185,32 @@ namespace System.Net.Http.QPack
return temp.Slice(0, bytesWritten).ToArray(); return temp.Slice(0, bytesWritten).ToArray();
} }
private static bool EncodeValueString(string s, Span<byte> buffer, out int length) private static bool EncodeValueString(string s, Encoding? valueEncoding, Span<byte> buffer, out int length)
{ {
if (buffer.Length != 0) if (buffer.Length != 0)
{ {
buffer[0] = 0; buffer[0] = 0;
if (IntegerEncoder.Encode(s.Length, 7, buffer, out int nameLength))
int encodedStringLength = valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1)
? s.Length
: valueEncoding.GetByteCount(s);
if (IntegerEncoder.Encode(encodedStringLength, 7, buffer, out int nameLength))
{ {
buffer = buffer.Slice(nameLength); buffer = buffer.Slice(nameLength);
if (buffer.Length >= s.Length) if (buffer.Length >= encodedStringLength)
{ {
EncodeValueStringPart(s, buffer); if (valueEncoding is null)
{
EncodeValueStringPart(s, buffer);
}
else
{
int written = valueEncoding.GetBytes(s, buffer);
Debug.Assert(written == encodedStringLength);
}
length = nameLength + s.Length; length = nameLength + encodedStringLength;
return true; return true;
} }
} }
@ -195,25 +224,42 @@ namespace System.Net.Http.QPack
/// Encodes a value by concatenating a collection of strings, separated by a separator string. /// Encodes a value by concatenating a collection of strings, separated by a separator string.
/// </summary> /// </summary>
public static bool EncodeValueString(ReadOnlySpan<string> values, string? separator, Span<byte> buffer, out int length) public static bool EncodeValueString(ReadOnlySpan<string> values, string? separator, Span<byte> buffer, out int length)
{
return EncodeValueString(values, separator, valueEncoding: null, buffer, out length);
}
public static bool EncodeValueString(ReadOnlySpan<string> values, string? separator, Encoding? valueEncoding, Span<byte> buffer, out int length)
{ {
if (values.Length == 1) if (values.Length == 1)
{ {
return EncodeValueString(values[0], buffer, out length); return EncodeValueString(values[0], valueEncoding, buffer, out length);
} }
if (values.Length == 0) if (values.Length == 0)
{ {
// TODO: this will be called with a string array from HttpHeaderCollection. Can we ever get a 0-length array from that? Assert if not. // TODO: this will be called with a string array from HttpHeaderCollection. Can we ever get a 0-length array from that? Assert if not.
return EncodeValueString(string.Empty, buffer, out length); return EncodeValueString(string.Empty, valueEncoding: null, buffer, out length);
} }
if (buffer.Length > 0) if (buffer.Length > 0)
{ {
Debug.Assert(separator != null); Debug.Assert(separator != null);
int valueLength = separator.Length * (values.Length - 1); int valueLength;
for (int i = 0; i < values.Length; ++i) if (valueEncoding is null || ReferenceEquals(valueEncoding, Encoding.Latin1))
{ {
valueLength += values[i].Length; valueLength = separator.Length * (values.Length - 1);
foreach (string part in values)
{
valueLength += part.Length;
}
}
else
{
valueLength = valueEncoding.GetByteCount(separator) * (values.Length - 1);
foreach (string part in values)
{
valueLength += valueEncoding.GetByteCount(part);
}
} }
buffer[0] = 0; buffer[0] = 0;
@ -222,18 +268,35 @@ namespace System.Net.Http.QPack
buffer = buffer.Slice(nameLength); buffer = buffer.Slice(nameLength);
if (buffer.Length >= valueLength) if (buffer.Length >= valueLength)
{ {
string value = values[0]; if (valueEncoding is null)
EncodeValueStringPart(value, buffer);
buffer = buffer.Slice(value.Length);
for (int i = 1; i < values.Length; ++i)
{ {
EncodeValueStringPart(separator, buffer); string value = values[0];
buffer = buffer.Slice(separator.Length);
value = values[i];
EncodeValueStringPart(value, buffer); EncodeValueStringPart(value, buffer);
buffer = buffer.Slice(value.Length); buffer = buffer.Slice(value.Length);
for (int i = 1; i < values.Length; i++)
{
EncodeValueStringPart(separator, buffer);
buffer = buffer.Slice(separator.Length);
value = values[i];
EncodeValueStringPart(value, buffer);
buffer = buffer.Slice(value.Length);
}
}
else
{
int written = valueEncoding.GetBytes(values[0], buffer);
buffer = buffer.Slice(written);
for (int i = 1; i < values.Length; i++)
{
written = valueEncoding.GetBytes(separator, buffer);
buffer = buffer.Slice(written);
written = valueEncoding.GetBytes(values[i], buffer);
buffer = buffer.Slice(written);
}
} }
length = nameLength + valueLength; length = nameLength + valueLength;