培训考核三期,新版培训,网页版培训登录器
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

798 lines
34 KiB

#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace BestHTTP.Connections.HTTP2
{
public sealed class HPACKEncoder
{
private HTTP2SettingsManager settingsRegistry;
// https://http2.github.io/http2-spec/compression.html#encoding.context
// When used for bidirectional communication, such as in HTTP, the encoding and decoding dynamic tables
// maintained by an endpoint are completely independent, i.e., the request and response dynamic tables are separate.
private HeaderTable requestTable;
private HeaderTable responseTable;
private HTTP2Handler parent;
public HPACKEncoder(HTTP2Handler parentHandler, HTTP2SettingsManager registry)
{
this.parent = parentHandler;
this.settingsRegistry = registry;
// I'm unsure what settings (local or remote) we should use for these two tables!
this.requestTable = new HeaderTable(this.settingsRegistry.MySettings);
this.responseTable = new HeaderTable(this.settingsRegistry.RemoteSettings);
}
public void Encode(HTTP2Stream context, HTTPRequest request, Queue<HTTP2FrameHeaderAndPayload> to, UInt32 streamId)
{
// Add usage of SETTINGS_MAX_HEADER_LIST_SIZE to be able to create a header and one or more continuation fragments
// (https://httpwg.org/specs/rfc7540.html#SettingValues)
using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream())
{
WriteHeader(bufferStream, ":method", HTTPRequest.MethodNames[(int)request.MethodType]);
// add path
WriteHeader(bufferStream, ":path", request.CurrentUri.PathAndQuery);
// add authority
WriteHeader(bufferStream, ":authority", request.CurrentUri.Authority);
// add scheme
WriteHeader(bufferStream, ":scheme", "https");
//bool hasBody = false;
// add other, regular headers
request.EnumerateHeaders((header, values) =>
{
if (header.Equals("connection", StringComparison.OrdinalIgnoreCase) ||
header.Equals("te", StringComparison.OrdinalIgnoreCase) ||
header.Equals("host", StringComparison.OrdinalIgnoreCase) ||
header.Equals("keep-alive", StringComparison.OrdinalIgnoreCase) ||
header.StartsWith("proxy-", StringComparison.OrdinalIgnoreCase))
return;
//if (!hasBody)
// hasBody = header.Equals("content-length", StringComparison.OrdinalIgnoreCase) && int.Parse(values[0]) > 0;
// https://httpwg.org/specs/rfc7540.html#HttpSequence
// The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
if (header.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase))
{
// error!
return;
}
// https://httpwg.org/specs/rfc7540.html#HttpHeaders
// Just as in HTTP/1.x, header field names are strings of ASCII characters that are compared in a case-insensitive fashion.
// However, header field names MUST be converted to lowercase prior to their encoding in HTTP/2.
// A request or response containing uppercase header field names MUST be treated as malformed
if (header.Any(Char.IsUpper))
header = header.ToLower();
for (int i = 0; i < values.Count; ++i)
{
WriteHeader(bufferStream, header, values[i]);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] - Encode - Header({1}/{2}): '{3}': '{4}'", context.Id, i + 1, values.Count, header, values[i]), this.parent.Context, context.Context, request.Context);
}
}, true);
var upStreamInfo = request.GetUpStream();
CreateHeaderFrames(to,
streamId,
bufferStream.ToArray(true),
(UInt32)bufferStream.Length,
upStreamInfo.Stream != null);
}
}
public void Decode(HTTP2Stream context, Stream stream, List<KeyValuePair<string, string>> to)
{
int headerType = stream.ReadByte();
while (headerType != -1)
{
byte firstDataByte = (byte)headerType;
// https://http2.github.io/http2-spec/compression.html#indexed.header.representation
if (BufferHelper.ReadBit(firstDataByte, 0) == 1)
{
var header = ReadIndexedHeader(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - IndexedHeader: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
to.Add(header);
}
else if (BufferHelper.ReadValue(firstDataByte, 0, 1) == 1)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
if (BufferHelper.ReadValue(firstDataByte, 2, 7) == 0)
{
// Literal Header Field with Incremental Indexing — New Name
var header = ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
this.responseTable.Add(header);
to.Add(header);
}
else
{
// Literal Header Field with Incremental Indexing — Indexed Name
var header = ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
this.responseTable.Add(header);
to.Add(header);
}
} else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 0)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
{
// Literal Header Field without Indexing — New Name
var header = ReadLiteralHeaderFieldwithoutIndexing_NewName(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
to.Add(header);
}
else
{
// Literal Header Field without Indexing — Indexed Name
var header = ReadLiteralHeaderFieldwithoutIndexing_IndexedName(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
to.Add(header);
}
}
else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 1)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
{
// Literal Header Field Never Indexed — New Name
var header = ReadLiteralHeaderFieldNeverIndexed_NewName(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
to.Add(header);
}
else
{
// Literal Header Field Never Indexed — Indexed Name
var header = ReadLiteralHeaderFieldNeverIndexed_IndexedName(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
to.Add(header);
}
}
else if (BufferHelper.ReadValue(firstDataByte, 0, 2) == 1)
{
// https://http2.github.io/http2-spec/compression.html#encoding.context.update
UInt32 newMaxSize = DecodeInteger(5, firstDataByte, stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - Dynamic Table Size Update: {1}", context.Id, newMaxSize), this.parent.Context, context.Context, context.AssignedRequest.Context);
//this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE] = (UInt16)newMaxSize;
this.responseTable.MaxDynamicTableSize = (UInt16)newMaxSize;
}
else
{
// ERROR
}
headerType = stream.ReadByte();
}
}
private KeyValuePair<string, string> ReadIndexedHeader(byte firstByte, Stream stream)
{
// https://http2.github.io/http2-spec/compression.html#indexed.header.representation
UInt32 index = DecodeInteger(7, firstByte, stream);
return this.responseTable.GetHeader(index);
}
private KeyValuePair<string, string> ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(byte firstByte, Stream stream)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
UInt32 keyIndex = DecodeInteger(6, firstByte, stream);
string header = this.responseTable.GetKey(keyIndex);
string value = DecodeString(stream);
return new KeyValuePair<string, string>(header, value);
}
private KeyValuePair<string, string> ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(byte firstByte, Stream stream)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
string header = DecodeString(stream);
string value = DecodeString(stream);
return new KeyValuePair<string, string>(header, value);
}
private KeyValuePair<string, string> ReadLiteralHeaderFieldwithoutIndexing_IndexedName(byte firstByte, Stream stream)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
UInt32 index = DecodeInteger(4, firstByte, stream);
string header = this.responseTable.GetKey(index);
string value = DecodeString(stream);
return new KeyValuePair<string, string>(header, value);
}
private KeyValuePair<string, string> ReadLiteralHeaderFieldwithoutIndexing_NewName(byte firstByte, Stream stream)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
string header = DecodeString(stream);
string value = DecodeString(stream);
return new KeyValuePair<string, string>(header, value);
}
private KeyValuePair<string, string> ReadLiteralHeaderFieldNeverIndexed_IndexedName(byte firstByte, Stream stream)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
UInt32 index = DecodeInteger(4, firstByte, stream);
string header = this.responseTable.GetKey(index);
string value = DecodeString(stream);
return new KeyValuePair<string, string>(header, value);
}
private KeyValuePair<string, string> ReadLiteralHeaderFieldNeverIndexed_NewName(byte firstByte, Stream stream)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
string header = DecodeString(stream);
string value = DecodeString(stream);
return new KeyValuePair<string, string>(header, value);
}
private string DecodeString(Stream stream)
{
byte start = (byte)stream.ReadByte();
bool rawString = BufferHelper.ReadBit(start, 0) == 0;
UInt32 stringLength = DecodeInteger(7, start, stream);
if (stringLength == 0)
return string.Empty;
if (rawString)
{
byte[] buffer = BufferPool.Get(stringLength, true);
stream.Read(buffer, 0, (int)stringLength);
BufferPool.Release(buffer);
return System.Text.Encoding.UTF8.GetString(buffer, 0, (int)stringLength);
}
else
{
var node = HuffmanEncoder.GetRoot();
byte currentByte = (byte)stream.ReadByte();
byte bitIdx = 0; // 0..7
using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream())
{
do
{
byte bitValue = BufferHelper.ReadBit(currentByte, bitIdx);
if (++bitIdx > 7)
{
stringLength--;
if (stringLength > 0)
{
bitIdx = 0;
currentByte = (byte)stream.ReadByte();
}
}
node = HuffmanEncoder.GetNext(node, bitValue);
if (node.Value != 0)
{
if (node.Value != HuffmanEncoder.EOS)
bufferStream.WriteByte((byte)node.Value);
node = HuffmanEncoder.GetRoot();
}
} while (stringLength > 0);
byte[] buffer = bufferStream.ToArray(true);
string result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)bufferStream.Length);
BufferPool.Release(buffer);
return result;
}
}
}
private void CreateHeaderFrames(Queue<HTTP2FrameHeaderAndPayload> to, UInt32 streamId, byte[] dataToSend, UInt32 payloadLength, bool hasBody)
{
UInt32 maxFrameSize = this.settingsRegistry.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE];
// Only one headers frame
if (payloadLength <= maxFrameSize)
{
HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
frameHeader.Type = HTTP2FrameTypes.HEADERS;
frameHeader.StreamId = streamId;
frameHeader.Flags = (byte)(HTTP2HeadersFlags.END_HEADERS);
if (!hasBody)
frameHeader.Flags |= (byte)(HTTP2HeadersFlags.END_STREAM);
frameHeader.PayloadLength = payloadLength;
frameHeader.Payload = dataToSend;
to.Enqueue(frameHeader);
}
else
{
HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
frameHeader.Type = HTTP2FrameTypes.HEADERS;
frameHeader.StreamId = streamId;
frameHeader.PayloadLength = maxFrameSize;
frameHeader.Payload = dataToSend;
frameHeader.DontUseMemPool = true;
frameHeader.PayloadOffset = 0;
if (!hasBody)
frameHeader.Flags = (byte)(HTTP2HeadersFlags.END_STREAM);
to.Enqueue(frameHeader);
UInt32 offset = maxFrameSize;
while (offset < payloadLength)
{
frameHeader = new HTTP2FrameHeaderAndPayload();
frameHeader.Type = HTTP2FrameTypes.CONTINUATION;
frameHeader.StreamId = streamId;
frameHeader.PayloadLength = maxFrameSize;
frameHeader.Payload = dataToSend;
frameHeader.PayloadOffset = offset;
offset += maxFrameSize;
if (offset >= payloadLength)
{
frameHeader.Flags = (byte)(HTTP2ContinuationFlags.END_HEADERS);
// last sent continuation fragment will release back the payload buffer
frameHeader.DontUseMemPool = false;
}
else
frameHeader.DontUseMemPool = true;
to.Enqueue(frameHeader);
}
}
}
private void WriteHeader(Stream stream, string header, string value)
{
// https://http2.github.io/http2-spec/compression.html#header.representation
KeyValuePair<UInt32, UInt32> index = this.requestTable.GetIndex(header, value);
if (index.Key == 0 && index.Value == 0)
{
WriteLiteralHeaderFieldWithIncrementalIndexing_NewName(stream, header, value);
this.requestTable.Add(new KeyValuePair<string, string>(header, value));
}
else if (index.Key != 0 && index.Value == 0)
{
WriteLiteralHeaderFieldWithIncrementalIndexing_IndexedName(stream, index.Key, value);
this.requestTable.Add(new KeyValuePair<string, string>(header, value));
}
else
{
WriteIndexedHeaderField(stream, index.Key);
}
}
private static void WriteIndexedHeaderField(Stream stream, UInt32 index)
{
byte requiredBytes = RequiredBytesToEncodeInteger(index, 7);
byte[] buffer = BufferPool.Get(requiredBytes, true);
UInt32 offset = 0;
buffer[0] = 0x80;
EncodeInteger(index, 7, buffer, ref offset);
stream.Write(buffer, 0, (int)offset);
BufferPool.Release(buffer);
}
private static void WriteLiteralHeaderFieldWithIncrementalIndexing_IndexedName(Stream stream, UInt32 index, string value)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 6) +
RequiredBytesToEncodeString(value);
byte[] buffer = BufferPool.Get(requiredBytes, true);
UInt32 offset = 0;
buffer[0] = 0x40;
EncodeInteger(index, 6, buffer, ref offset);
EncodeString(value, buffer, ref offset);
stream.Write(buffer, 0, (int)offset);
BufferPool.Release(buffer);
}
private static void WriteLiteralHeaderFieldWithIncrementalIndexing_NewName(Stream stream, string header, string value)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
byte[] buffer = BufferPool.Get(requiredBytes, true);
UInt32 offset = 0;
buffer[offset++] = 0x40;
EncodeString(header, buffer, ref offset);
EncodeString(value, buffer, ref offset);
stream.Write(buffer, 0, (int)offset);
BufferPool.Release(buffer);
}
private static void WriteLiteralHeaderFieldWithoutIndexing_IndexedName(Stream stream, UInt32 index, string value)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 4) + RequiredBytesToEncodeString(value);
byte[] buffer = BufferPool.Get(requiredBytes, true);
UInt32 offset = 0;
buffer[0] = 0;
EncodeInteger(index, 4, buffer, ref offset);
EncodeString(value, buffer, ref offset);
stream.Write(buffer, 0, (int)offset);
BufferPool.Release(buffer);
}
private static void WriteLiteralHeaderFieldWithoutIndexing_NewName(Stream stream, string header, string value)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
byte[] buffer = BufferPool.Get(requiredBytes, true);
UInt32 offset = 0;
buffer[offset++] = 0;
EncodeString(header, buffer, ref offset);
EncodeString(value, buffer, ref offset);
stream.Write(buffer, 0, (int)offset);
BufferPool.Release(buffer);
}
private static void WriteLiteralHeaderFieldNeverIndexed_IndexedName(Stream stream, UInt32 index, string value)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 4) + RequiredBytesToEncodeString(value);
byte[] buffer = BufferPool.Get(requiredBytes, true);
UInt32 offset = 0;
buffer[0] = 0x10;
EncodeInteger(index, 4, buffer, ref offset);
EncodeString(value, buffer, ref offset);
stream.Write(buffer, 0, (int)offset);
BufferPool.Release(buffer);
}
private static void WriteLiteralHeaderFieldNeverIndexed_NewName(Stream stream, string header, string value)
{
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
byte[] buffer = BufferPool.Get(requiredBytes, true);
UInt32 offset = 0;
buffer[offset++] = 0x10;
EncodeString(header, buffer, ref offset);
EncodeString(value, buffer, ref offset);
stream.Write(buffer, 0, (int)offset);
BufferPool.Release(buffer);
}
private static void WriteDynamicTableSizeUpdate(Stream stream, UInt16 maxSize)
{
// https://http2.github.io/http2-spec/compression.html#encoding.context.update
UInt32 requiredBytes = RequiredBytesToEncodeInteger(maxSize, 5);
byte[] buffer = BufferPool.Get(requiredBytes, true);
UInt32 offset = 0;
buffer[offset] = 0x20;
EncodeInteger(maxSize, 5, buffer, ref offset);
stream.Write(buffer, 0, (int)offset);
BufferPool.Release(buffer);
}
private static UInt32 RequiredBytesToEncodeString(string str)
{
uint requiredBytesForRawStr = RequiredBytesToEncodeRawString(str);
uint requiredBytesForHuffman = RequiredBytesToEncodeStringWithHuffman(str);
requiredBytesForHuffman += RequiredBytesToEncodeInteger(requiredBytesForHuffman, 7);
return Math.Min(requiredBytesForRawStr, requiredBytesForHuffman);
}
private static void EncodeString(string str, byte[] buffer, ref UInt32 offset)
{
uint requiredBytesForRawStr = RequiredBytesToEncodeRawString(str);
uint requiredBytesForHuffman = RequiredBytesToEncodeStringWithHuffman(str);
// if using huffman encoding would produce the same length, we choose raw encoding instead as it requires
// less CPU cicles
if (requiredBytesForRawStr <= requiredBytesForHuffman + RequiredBytesToEncodeInteger(requiredBytesForHuffman, 7))
EncodeRawStringTo(str, buffer, ref offset);
else
EncodeStringWithHuffman(str, requiredBytesForHuffman, buffer, ref offset);
}
// This calculates only the length of the compressed string,
// additional header length must be calculated using the value returned by this function
private static UInt32 RequiredBytesToEncodeStringWithHuffman(string str)
{
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
byte[] strBytes = BufferPool.Get(requiredBytesForStr, true);
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, strBytes, 0);
UInt32 requiredBits = 0;
for (int i = 0; i < requiredBytesForStr; ++i)
requiredBits += HuffmanEncoder.GetEntryForCodePoint(strBytes[i]).Bits;
BufferPool.Release(strBytes);
return (UInt32)((requiredBits / 8) + ((requiredBits % 8) == 0 ? 0 : 1));
}
private static void EncodeStringWithHuffman(string str, UInt32 encodedLength, byte[] buffer, ref UInt32 offset)
{
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
byte[] strBytes = BufferPool.Get(requiredBytesForStr, true);
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, strBytes, 0);
// 0. bit: huffman flag
buffer[offset] = 0x80;
// 1..7+ bit: length
EncodeInteger(encodedLength, 7, buffer, ref offset);
byte bufferBitIdx = 0;
for (int i = 0; i < requiredBytesForStr; ++i)
AddCodePointToBuffer(HuffmanEncoder.GetEntryForCodePoint(strBytes[i]), buffer, ref offset, ref bufferBitIdx);
// https://http2.github.io/http2-spec/compression.html#string.literal.representation
// As the Huffman-encoded data doesn't always end at an octet boundary, some padding is inserted after it,
// up to the next octet boundary. To prevent this padding from being misinterpreted as part of the string literal,
// the most significant bits of the code corresponding to the EOS (end-of-string) symbol are used.
if (bufferBitIdx != 0)
AddCodePointToBuffer(HuffmanEncoder.GetEntryForCodePoint(256), buffer, ref offset, ref bufferBitIdx, true);
BufferPool.Release(strBytes);
}
private static void AddCodePointToBuffer(HuffmanEncoder.TableEntry code, byte[] buffer, ref UInt32 offset, ref byte bufferBitIdx, bool finishOnBoundary = false)
{
for (byte codeBitIdx = 1; codeBitIdx <= code.Bits; ++codeBitIdx)
{
byte bit = code.GetBitAtIdx(codeBitIdx);
buffer[offset] = BufferHelper.SetBit(buffer[offset], bufferBitIdx, bit);
// octet boundary reached, proceed to the next octet
if (++bufferBitIdx == 8)
{
if (++offset < buffer.Length)
buffer[offset] = 0;
if (finishOnBoundary)
return;
bufferBitIdx = 0;
}
}
}
private static UInt32 RequiredBytesToEncodeRawString(string str)
{
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);
return (UInt32)(requiredBytesForStr + requiredBytesForLengthPrefix);
}
// This method encodes a string without huffman encoding
private static void EncodeRawStringTo(string str, byte[] buffer, ref UInt32 offset)
{
uint requiredBytesForStr = (uint)System.Text.Encoding.UTF8.GetByteCount(str);
int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);
UInt32 originalOffset = offset;
buffer[offset] = 0;
EncodeInteger(requiredBytesForStr, 7, buffer, ref offset);
// Zero out the huffman flag
buffer[originalOffset] = BufferHelper.SetBit(buffer[originalOffset], 0, false);
if (offset != originalOffset + requiredBytesForLengthPrefix)
throw new Exception(string.Format("offset({0}) != originalOffset({1}) + requiredBytesForLengthPrefix({1})", offset, originalOffset, requiredBytesForLengthPrefix));
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, (int)offset);
offset += requiredBytesForStr;
}
private static byte RequiredBytesToEncodeInteger(UInt32 value, byte N)
{
UInt32 maxValue = (1u << N) - 1;
byte count = 0;
// If the integer value is small enough, i.e., strictly less than 2^N-1, it is encoded within the N-bit prefix.
if (value < maxValue)
{
count++;
}
else
{
// Otherwise, all the bits of the prefix are set to 1, and the value, decreased by 2^N-1
count++;
value -= maxValue;
while (value >= 0x80)
{
// The most significant bit of each octet is used as a continuation flag: its value is set to 1 except for the last octet in the list.
count++;
value = value / 0x80;
}
count++;
}
return count;
}
// https://http2.github.io/http2-spec/compression.html#integer.representation
private static void EncodeInteger(UInt32 value, byte N, byte[] buffer, ref UInt32 offset)
{
// 2^N - 1
UInt32 maxValue = (1u << N) - 1;
// If the integer value is small enough, i.e., strictly less than 2^N-1, it is encoded within the N-bit prefix.
if (value < maxValue)
{
buffer[offset++] |= (byte)value;
}
else
{
// Otherwise, all the bits of the prefix are set to 1, and the value, decreased by 2^N-1
buffer[offset++] |= (byte)(0xFF >> (8 - N));
value -= maxValue;
while (value >= 0x80)
{
// The most significant bit of each octet is used as a continuation flag: its value is set to 1 except for the last octet in the list.
buffer[offset++] = (byte)(0x80 | (0x7F & value));
value = value / 0x80;
}
buffer[offset++] = (byte)value;
}
}
// https://http2.github.io/http2-spec/compression.html#integer.representation
private static UInt32 DecodeInteger(byte N, byte[] buffer, ref UInt32 offset)
{
// The starting value is the value behind the mask of the N bits
UInt32 value = (UInt32)(buffer[offset++] & (byte)(0xFF >> (8 - N)));
// All N bits are 1s ? If so, we have at least one another byte to decode
if (value == (1u << N) - 1)
{
byte shift = 0;
do
{
// The most significant bit is a continuation flag, so we have to mask it out
value += (UInt32)((buffer[offset] & 0x7F) << shift);
shift += 7;
} while ((buffer[offset++] & 0x80) == 0x80);
}
return value;
}
// https://http2.github.io/http2-spec/compression.html#integer.representation
private static UInt32 DecodeInteger(byte N, byte data, Stream stream)
{
// The starting value is the value behind the mask of the N bits
UInt32 value = (UInt32)(data & (byte)(0xFF >> (8 - N)));
// All N bits are 1s ? If so, we have at least one another byte to decode
if (value == (1u << N) - 1)
{
byte shift = 0;
do
{
data = (byte)stream.ReadByte();
// The most significant bit is a continuation flag, so we have to mask it out
value += (UInt32)((data & 0x7F) << shift);
shift += 7;
} while ((data & 0x80) == 0x80);
}
return value;
}
public override string ToString()
{
return this.requestTable.ToString() + this.responseTable.ToString();
}
}
}
#endif