This commit is contained in:
2026-06-15 18:18:16 +08:00
parent 97c9fba14e
commit 2b9f134e5f
4164 changed files with 386922 additions and 79 deletions

View File

@@ -0,0 +1,167 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using System;
using Best.HTTP.Shared.PlatformSupport.Memory;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
public static class BufferHelper
{
public static void SetUInt16(byte[] buffer, int offset, UInt16 value)
{
buffer[offset + 1] = (byte)(value);
buffer[offset + 0] = (byte)(value >> 8);
}
public static void SetUInt24(byte[] buffer, int offset, UInt32 value)
{
buffer[offset + 2] = (byte)(value);
buffer[offset + 1] = (byte)(value >> 8);
buffer[offset + 0] = (byte)(value >> 16);
}
public static void SetUInt24(Span<byte> buffer, int offset, UInt32 value)
{
buffer[offset + 2] = (byte)(value);
buffer[offset + 1] = (byte)(value >> 8);
buffer[offset + 0] = (byte)(value >> 16);
}
public static void SetUInt31(byte[] buffer, int offset, UInt32 value)
{
buffer[offset + 3] = (byte)(value);
buffer[offset + 2] = (byte)(value >> 8);
buffer[offset + 1] = (byte)(value >> 16);
buffer[offset + 0] = (byte)((value >> 24) & 0x7F);
}
public static void SetUInt31(Span<byte> buffer, int offset, UInt32 value)
{
buffer[offset + 3] = (byte)(value);
buffer[offset + 2] = (byte)(value >> 8);
buffer[offset + 1] = (byte)(value >> 16);
buffer[offset + 0] = (byte)((value >> 24) & 0x7F);
}
public static void SetUInt32(byte[] buffer, int offset, UInt32 value)
{
buffer[offset + 3] = (byte)(value);
buffer[offset + 2] = (byte)(value >> 8);
buffer[offset + 1] = (byte)(value >> 16);
buffer[offset + 0] = (byte)(value >> 24);
}
public static void SetLong(byte[] buffer, int offset, long value)
{
buffer[offset + 7] = (byte)(value);
buffer[offset + 6] = (byte)(value >> 8);
buffer[offset + 5] = (byte)(value >> 16);
buffer[offset + 4] = (byte)(value >> 24);
buffer[offset + 3] = (byte)(value >> 32);
buffer[offset + 2] = (byte)(value >> 40);
buffer[offset + 1] = (byte)(value >> 48);
buffer[offset + 0] = (byte)(value >> 56);
}
public static byte SetBit(byte value, byte bitIdx, bool bitValue)
{
// bitIdx: 01234567
return SetBit(value, bitIdx, Convert.ToByte(bitValue));
}
public static byte SetBit(byte value, byte bitIdx, byte bitValue)
{
// bitIdx: 01234567
return (byte)((value ^ (value & (0x80 >> bitIdx))) | bitValue << (7 - bitIdx));
}
public static byte ReadBit(byte value, byte bitIdx)
{
// bitIdx: 01234567
byte mask = (byte)(0x80 >> bitIdx);
return (byte)((value & mask) >> (7 - bitIdx));
}
public static byte ReadValue(byte value, byte fromBit, byte toBit)
{
// bitIdx: 01234567
byte result = 0;
short idx = toBit;
while (idx >= fromBit)
{
result += (byte)(ReadBit(value, (byte)idx) << (toBit - idx));
idx--;
}
return result;
}
public static UInt16 ReadUInt16(byte[] buffer, int offset)
{
return (UInt16)(buffer[offset + 1] | buffer[offset] << 8);
}
public static UInt32 ReadUInt24(byte[] buffer, int offset)
{
return (UInt32)(buffer[offset + 2] |
buffer[offset + 1] << 8 |
buffer[offset + 0] << 16
);
}
public static UInt32 ReadUInt31(byte[] buffer, int offset)
{
return (UInt32)(buffer[offset + 3] |
buffer[offset + 2] << 8 |
buffer[offset + 1] << 16 |
(buffer[offset] & 0x7F) << 24
);
}
public static UInt32 ReadUInt31(BufferSegment buffer, int offset)
{
return (UInt32)( buffer.Data[offset + 3] |
buffer.Data[offset + 2] << 8 |
buffer.Data[offset + 1] << 16 |
(buffer.Data[offset] & 0x7F) << 24
);
}
public static UInt32 ReadUInt32(byte[] buffer, int offset)
{
return (UInt32)(buffer[offset + 3] |
buffer[offset + 2] << 8 |
buffer[offset + 1] << 16 |
buffer[offset + 0] << 24
);
}
public static UInt32 ReadUInt32(BufferSegment buffer, int offset)
{
return (UInt32)(buffer.Data[offset + 3] |
buffer.Data[offset + 2] << 8 |
buffer.Data[offset + 1] << 16 |
buffer.Data[offset + 0] << 24
);
}
public static long ReadLong(BufferSegment buffer, int offset)
{
return (long)buffer.Data[offset + 7] |
(long)buffer.Data[offset + 6] << 8 |
(long)buffer.Data[offset + 5] << 16 |
(long)buffer.Data[offset + 4] << 24 |
(long)buffer.Data[offset + 3] << 32 |
(long)buffer.Data[offset + 2] << 40 |
(long)buffer.Data[offset + 1] << 48 |
(long)buffer.Data[offset + 0] << 56;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ddab2aecdc1bd94688535c6d3532949
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,203 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.PlatformSupport.Text;
using System;
using System.Collections.Generic;
using System.IO;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
public interface IFrameDataView : IDisposable
{
long Length { get; }
long Position { get; }
void AddFrame(HTTP2FrameHeaderAndPayload frame);
int ReadByte();
int Read(byte[] buffer, int offset, int count);
}
public abstract class CommonFrameView : IFrameDataView
{
public long Length { get; protected set; }
public long Position { get; protected set; }
protected List<HTTP2FrameHeaderAndPayload> frames = new List<HTTP2FrameHeaderAndPayload>();
protected int currentFrameIdx = -1;
protected byte[] data;
protected int dataOffset;
protected int maxOffset;
public abstract void AddFrame(HTTP2FrameHeaderAndPayload frame);
protected abstract long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame);
public virtual int Read(byte[] buffer, int offset, int count)
{
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
return -1;
int readCount = 0;
while (count > 0)
{
long copyCount = Math.Min(count, this.maxOffset - this.dataOffset);
Array.Copy(this.data, this.dataOffset, buffer, offset + readCount, copyCount);
count -= (int)copyCount;
readCount += (int)copyCount;
this.dataOffset += (int)copyCount;
this.Position += copyCount;
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
break;
}
return readCount;
}
public virtual int ReadByte()
{
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
return -1;
byte data = this.data[this.dataOffset];
this.dataOffset++;
this.Position++;
return data;
}
protected abstract bool AdvanceFrame();
public virtual void Dispose()
{
for (int i = 0; i < this.frames.Count; ++i)
//if (this.frames[i].Payload != null && !this.frames[i].DontUseMemPool)
BufferPool.Release(this.frames[i].Payload);
this.frames.Clear();
}
public override string ToString()
{
var sb = StringBuilderPool.Get(this.frames.Count + 2);
sb.Append("[CommonFrameView ");
for (int i = 0; i < this.frames.Count; ++i) {
sb.AppendFormat("{0} Payload: {1}\n", this.frames[i], this.frames[i].PayloadAsHex());
}
sb.Append("]");
return StringBuilderPool.ReleaseAndGrab(sb);
}
}
public sealed class HeaderFrameView : CommonFrameView
{
public override void AddFrame(HTTP2FrameHeaderAndPayload frame)
{
if (frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
throw new ArgumentException("HeaderFrameView - Unexpected frame type: " + frame.Type);
this.frames.Add(frame);
this.Length += CalculateDataLengthForFrame(frame);
if (this.currentFrameIdx == -1)
AdvanceFrame();
}
protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame)
{
switch (frame.Type)
{
case HTTP2FrameTypes.HEADERS:
return HTTP2FrameHelper.ReadHeadersFrame(frame).HeaderBlockFragment.Count;
case HTTP2FrameTypes.CONTINUATION:
return frame.Payload.Count;
}
return 0;
}
protected override bool AdvanceFrame()
{
if (this.currentFrameIdx >= this.frames.Count - 1)
return false;
this.currentFrameIdx++;
HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx];
this.data = frame.Payload.Data;
switch (frame.Type)
{
case HTTP2FrameTypes.HEADERS:
var header = HTTP2FrameHelper.ReadHeadersFrame(frame);
this.dataOffset = header.HeaderBlockFragment.Offset;
this.maxOffset = this.dataOffset + header.HeaderBlockFragment.Count;
break;
case HTTP2FrameTypes.CONTINUATION:
this.dataOffset = 0;
this.maxOffset = frame.Payload.Count;
break;
}
return true;
}
}
public sealed class FramesAsStreamView : Stream
{
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return false; } }
public override long Length { get { return this.view.Length; } }
public override long Position { get { return this.view.Position; } set { throw new NotSupportedException(); } }
private IFrameDataView view;
public FramesAsStreamView(IFrameDataView view)
{
this.view = view;
}
public void AddFrame(HTTP2FrameHeaderAndPayload frame)
{
this.view.AddFrame(frame);
}
public override int ReadByte()
{
return this.view.ReadByte();
}
public override int Read(byte[] buffer, int offset, int count)
{
return this.view.Read(buffer, offset, count);
}
public override void Close()
{
base.Close();
this.view.Dispose();
}
public override void Flush() {}
public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); }
public override void SetLength(long value) { throw new NotImplementedException(); }
public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); }
public override string ToString()
{
return this.view.ToString();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3117e531a50d6f74faedada4b320cebf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,815 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using Best.HTTP.Shared;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.Streams;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Best.HTTP.Hosts.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 LoggingContext _context;
public HPACKEncoder(LoggingContext context, HTTP2SettingsManager registry)
{
this._context = context;
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(request.UploadSettings.UploadChunkSize))
{
request.Prepare();
// add :method
switch (request.MethodType)
{
case HTTPMethods.Get: WriteIndexedHeaderField(bufferStream, 2); break;
case HTTPMethods.Post: WriteIndexedHeaderField(bufferStream, 3); break;
default:
WriteLiteralHeaderFieldWithoutIndexing_IndexedName(bufferStream, 2, HTTPRequest.MethodNames[(int)request.MethodType]);
break;
}
// add :authority
WriteLiteralHeaderFieldWithoutIndexing_IndexedName(bufferStream, 1, request.CurrentUri.Authority);
// add :scheme
WriteIndexedHeaderField(bufferStream, 7);
// add :path
WriteLiteralHeaderFieldWithoutIndexing_IndexedName(bufferStream, 4, request.CurrentUri.PathAndQuery);
//bool hasBody = false;
// add other, regular headers
request.EnumerateHeaders((header, values) =>
{
if (header.Equals("connection", StringComparison.OrdinalIgnoreCase) ||
(header.Equals("te", StringComparison.OrdinalIgnoreCase) && !values.Contains("trailers") && values.Count <= 1) ||
header.Equals("host", StringComparison.OrdinalIgnoreCase) ||
header.Equals("keep-alive", StringComparison.OrdinalIgnoreCase) ||
header.StartsWith("proxy-", StringComparison.OrdinalIgnoreCase))
return;
if (header.Equals("content-length", StringComparison.OrdinalIgnoreCase) && values.Count == 1 && int.TryParse(values[0], out int contentLength) && contentLength <= 0 && !context.ParentHandler.ConnectionSettings.ForceSendContentLengthHeader)
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.ToLowerInvariant();
for (int i = 0; i < values.Count; ++i)
{
WriteHeader(bufferStream, header, values[i]);
if (HTTPManager.Logger.Level <= 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._context);
}
}, true);
CreateHeaderFrames(to,
streamId,
bufferStream.ToArray(true, this._context),
(UInt32)bufferStream.Length,
request.UploadSettings.UploadStream != 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 <= Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - IndexedHeader: {1}", context.Id, header.ToString()), this._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 <= Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_NewName: {1}", context.Id, header.ToString()), this._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 <= Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_IndexedName: {1}", context.Id, header.ToString()), this._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 <= Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_NewName: {1}", context.Id, header.ToString()), this._context);
to.Add(header);
}
else
{
// Literal Header Field without Indexing — Indexed Name
var header = ReadLiteralHeaderFieldwithoutIndexing_IndexedName(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_IndexedName: {1}", context.Id, header.ToString()), this._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 <= Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_NewName: {1}", context.Id, header.ToString()), this._context);
to.Add(header);
}
else
{
// Literal Header Field Never Indexed — Indexed Name
var header = ReadLiteralHeaderFieldNeverIndexed_IndexedName(firstDataByte, stream);
if (HTTPManager.Logger.Level <= Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_IndexedName: {1}", context.Id, header.ToString()), this._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 <= Loglevels.Information)
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - Dynamic Table Size Update: {1}", context.Id, newMaxSize), this._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);
var result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)stringLength);
BufferPool.Release(buffer);
return result;
}
else
{
var node = HuffmanEncoder.GetRoot();
byte currentByte = (byte)stream.ReadByte();
byte bitIdx = 0; // 0..7
using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream((int)(stringLength * 1.5f)))
{
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.GetBuffer();
string result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)bufferStream.Length);
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.Payload = dataToSend.AsBuffer((int)payloadLength);
to.Enqueue(frameHeader);
}
else
{
HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
frameHeader.Type = HTTP2FrameTypes.HEADERS;
frameHeader.StreamId = streamId;
frameHeader.Payload = dataToSend.AsBuffer((int)maxFrameSize);
frameHeader.DontUseMemPool = true;
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.Payload = dataToSend.AsBuffer((int)offset, (int)maxFrameSize);
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(HuffmanTableEntry 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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa3995669bb00bb4b9662903873154c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using System;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
/// <summary>
/// Settings for HTTP/2 connections when the Connect protocol is available.
/// </summary>
public sealed class WebSocketOverHTTP2Settings
{
/// <summary>
/// Set it to false to disable Websocket Over HTTP/2 (RFC 8441). It's true by default.
/// </summary>
public bool EnableWebSocketOverHTTP2 { get; set; } = true;
/// <summary>
/// Set it to disable fallback logic from the Websocket Over HTTP/2 implementation to the 'old' HTTP/1 implementation when it fails to connect.
/// </summary>
public bool EnableImplementationFallback { get; set; } = true;
}
/// <summary>
/// Settings for HTTP/2 connections.
/// </summary>
public sealed class HTTP2ConnectionSettings
{
/// <summary>
/// When set to false, the plugin will not try to use HTTP/2 connections.
/// </summary>
public bool EnableHTTP2Connections = true;
/// <summary>
/// Maximum size of the HPACK header table.
/// </summary>
public UInt32 HeaderTableSize = 4096; // Spec default: 4096
/// <summary>
/// Maximum concurrent http2 stream on http2 connection will allow. Its default value is 128;
/// </summary>
public UInt32 MaxConcurrentStreams = 128; // Spec default: not defined
/// <summary>
/// Initial window size of a http2 stream. Its default value is 65535, can be controlled through the HTTPRequest's DownloadSettings object.
/// </summary>
public UInt32 InitialStreamWindowSize = UInt16.MaxValue; // Spec default: 65535
/// <summary>
/// Global window size of a http/2 connection. Its default value is the maximum possible value on 31 bits.
/// </summary>
public UInt32 InitialConnectionWindowSize = HTTP2ContentConsumer.MaxValueFor31Bits; // Spec default: 65535
/// <summary>
/// Maximum size of a http2 frame.
/// </summary>
public UInt32 MaxFrameSize = 16384; // 16384 spec def.
/// <summary>
/// Not used.
/// </summary>
public UInt32 MaxHeaderListSize = UInt32.MaxValue; // Spec default: infinite
/// <summary>
/// With HTTP/2 only one connection will be open so we can keep it open longer as we hope it will be reused more.
/// </summary>
public TimeSpan MaxIdleTime = TimeSpan.FromSeconds(120);
/// <summary>
/// Time between two ping messages.
/// </summary>
public TimeSpan PingFrequency = TimeSpan.FromSeconds(5);
/// <summary>
/// Timeout to receive a ping acknowledgement from the server. If no ack received in this time the connection will be treated as broken.
/// </summary>
public TimeSpan Timeout = TimeSpan.FromSeconds(3);
/// <summary>
/// Set to true to enable RFC 8441 "Bootstrapping WebSockets with HTTP/2" (https://tools.ietf.org/html/rfc8441).
/// </summary>
public bool EnableConnectProtocol = false;
/// <summary>
/// Settings for WebSockets over HTTP/2 (RFC 8441)
/// </summary>
public WebSocketOverHTTP2Settings WebSocketOverHTTP2Settings = new WebSocketOverHTTP2Settings();
/// <summary>
/// When set to <c>true</c> the plugin will send a <c>Content-Length</c> header, even if it has a <c>0</c> value.
/// </summary>
public bool ForceSendContentLengthHeader { get; set; } = false;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40bfeb8809240f346a1a067b7fed0f3c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,709 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
#define ENABLE_LOGGING
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.PlatformSupport.Threading;
using Best.HTTP.Shared;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.PlatformSupport.Network.Tcp;
using Best.HTTP.Shared.Streams;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
public delegate HTTP2Stream CustomHTTP2StreamFactory(HTTPRequest request, uint streamId, HTTP2ContentConsumer parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder);
public sealed class HTTP2ContentConsumer : IHTTPRequestHandler, IContentConsumer, IThreadSignaler
{
public KeepAliveHeader KeepAlive { get { return null; } }
public bool CanProcessMultiple { get { return !this.SentGoAwayFrame && this.isRunning && this._maxAssignedRequests > 1; } }
public int AssignedRequests => this._assignedRequest;
private int _assignedRequest;
public int MaxAssignedRequests => this._maxAssignedRequests;
private int _maxAssignedRequests = 1;
public const UInt32 MaxValueFor31Bits = 0xFFFFFFFF >> 1;
public double Latency { get; private set; }
public HTTP2SettingsManager settings;
public HPACKEncoder HPACKEncoder;
public LoggingContext Context { get; private set; }
public PeekableContentProviderStream ContentProvider { get; private set; }
public HTTP2ConnectionSettings ConnectionSettings { get; private set; }
private DateTime lastPingSent = DateTime.MinValue;
private int waitingForPingAck = 0;
private DateTime lastDataFrameReceived = DateTime.MinValue;
public static int RTTBufferCapacity = 5;
private CircularBuffer<double> rtts = new CircularBuffer<double>(RTTBufferCapacity);
private volatile bool isRunning;
private AutoResetEvent newFrameSignal = new AutoResetEvent(false);
private ConcurrentQueue<HTTPRequest> requestQueue = new ConcurrentQueue<HTTPRequest>();
private List<HTTP2Stream> clientInitiatedStreams = new List<HTTP2Stream>();
private ConcurrentQueue<HTTP2FrameHeaderAndPayload> newFrames = new ConcurrentQueue<HTTP2FrameHeaderAndPayload>();
private List<HTTP2FrameHeaderAndPayload> outgoingFrames = new List<HTTP2FrameHeaderAndPayload>();
private UInt32 remoteWindow;
private DateTime lastInteraction;
private DateTime goAwaySentAt = DateTime.MaxValue;
private bool SentGoAwayFrame { get => this.goAwaySentAt != DateTime.MaxValue; }
private HTTPOverTCPConnection conn;
private TimeSpan MaxGoAwayWaitTime { get { return !this.SentGoAwayFrame ? TimeSpan.MaxValue : TimeSpan.FromMilliseconds(Math.Max(this.Latency * 2.5, 1500)); } }
// https://httpwg.org/specs/rfc7540.html#StreamIdentifiers
// Streams initiated by a client MUST use odd-numbered stream identifiers
// With an initial value of -1, the first client initiated stream's id going to be 1.
private long LastStreamId = -1;
public HTTP2ContentConsumer(HTTPOverTCPConnection conn)
{
this.Context = new LoggingContext(this);
this.Context.Add("Parent", conn.Context);
this.conn = conn;
this.isRunning = true;
this.ConnectionSettings = HTTPManager.PerHostSettings.Get(conn.HostKey).HTTP2ConnectionSettings;
this.settings = new HTTP2SettingsManager(this.Context, this.ConnectionSettings);
Process(this.conn.CurrentRequest);
}
public void Process(HTTPRequest request)
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Process request called", this.Context);
#endif
request.TimeoutSettings.SetProcessing(this.lastInteraction = DateTime.UtcNow);
Interlocked.Increment(ref this._assignedRequest);
this.requestQueue.Enqueue(request);
SignalThread(SignalHandlerTypes.Signal);
}
public void SignalThread(SignalHandlerTypes signalType) => this.newFrameSignal?.Set();
public void RunHandler()
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Processing thread up and running!", this.Context);
#endif
ThreadedRunner.SetThreadName("Best.HTTP2 Process");
string abortWithMessage = string.Empty;
try
{
bool atLeastOneStreamHasAFrameToSend = true;
this.HPACKEncoder = new HPACKEncoder(this.Context, this.settings);
// https://httpwg.org/specs/rfc7540.html#InitialWindowSize
// The connection flow-control window is also 65,535 octets.
this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
// we want to pack as many data as we can in one tcp segment, but setting the buffer's size too high
// we might keep data too long and send them in bursts instead of in a steady stream.
// Keeping it too low might result in a full tcp segment and one with very low payload
// Is it possible that one full tcp segment sized buffer would be the best, or multiple of it.
// It would keep the network busy without any fragments. The ethernet layer has a maximum of 1500 bytes,
// but there's two layers of 20 byte headers each, so as a theoretical maximum it's 1500-20-20 bytes.
// On the other hand, if the buffer is small (1-2), that means that for larger data, we have to do a lot
// of system calls, in that case a larger buffer might be better. Still, if we are not cpu bound,
// a well saturated network might serve us better.
using (WriteOnlyBufferedStream bufferedStream = new WriteOnlyBufferedStream(this.conn.TopStream, 1024 * 1024 /*1500 - 20 - 20*/, this.Context))
{
// The client connection preface starts with a sequence of 24 octets
// Connection preface starts with the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n).
ReadOnlySpan<byte> MAGIC = stackalloc byte[24] { 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a };
bufferedStream.Write(MAGIC);
// This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty.
// The client sends the client connection preface immediately upon receipt of a
// 101 (Switching Protocols) response (indicating a successful upgrade)
// or as the first application data octets of a TLS connection
this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] = this.ConnectionSettings.InitialStreamWindowSize;
this.settings.InitiatedMySettings[HTTP2Settings.MAX_CONCURRENT_STREAMS] = this.ConnectionSettings.MaxConcurrentStreams;
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] = (uint)(this.ConnectionSettings.EnableConnectProtocol ? 1 : 0);
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_PUSH] = 0;
this.settings.SendChanges(this.outgoingFrames);
this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
// The default window size for the whole connection is 65535 bytes,
// but we want to set it to the maximum possible value.
Int64 initialConnectionWindowSize = this.ConnectionSettings.InitialConnectionWindowSize;
// yandex.ru returns with an FLOW_CONTROL_ERROR (3) error when the plugin tries to set the connection window to 2^31 - 1
// and works only with a maximum value of 2^31 - 10Mib (10 * 1024 * 1024).
if (initialConnectionWindowSize == HTTP2ContentConsumer.MaxValueFor31Bits)
initialConnectionWindowSize -= 10 * 1024 * 1024;
if (initialConnectionWindowSize > 65535)
{
Int64 initialConnectionWindowSizeDiff = initialConnectionWindowSize - 65535;
if (initialConnectionWindowSizeDiff > 0)
this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)initialConnectionWindowSizeDiff, this.Context));
}
initialConnectionWindowSize -= 65535;
// local, per-connection window
long localConnectionWindow = initialConnectionWindowSize;
UInt32 updateConnectionWindowAt = (UInt32)(localConnectionWindow / 2);
while (this.isRunning)
{
DateTime now = DateTime.UtcNow;
if (!atLeastOneStreamHasAFrameToSend)
{
// buffered stream will call flush automatically if its internal buffer is full.
// But we have to make it sure that we flush remaining data before we go to sleep.
bufferedStream.Flush();
// Wait until we have to send the next ping, OR a new frame is received on the read thread.
// lastPingSent Now lastPingSent+frequency lastPingSent+Ping timeout
//----|---------------------|---------------|----------------------|----------------------|------------|
// lastInteraction lastInteraction + MaxIdleTime
var sendPingAt = this.lastPingSent + this.ConnectionSettings.PingFrequency;
var timeoutAt = this.waitingForPingAck != 0 ? this.lastPingSent + this.ConnectionSettings.Timeout : DateTime.MaxValue;
// sendPingAt can be in the past if Timeout is larger than PingFrequency
var nextPingInteraction = sendPingAt < timeoutAt && sendPingAt >= now ? sendPingAt : timeoutAt;
var disconnectByIdleAt = this.lastInteraction + this.ConnectionSettings.MaxIdleTime;
var nextDueClientInteractionAt = nextPingInteraction < disconnectByIdleAt ? nextPingInteraction : disconnectByIdleAt;
int wait = (int)(nextDueClientInteractionAt - now).TotalMilliseconds;
wait = (int)Math.Min(wait, this.MaxGoAwayWaitTime.TotalMilliseconds);
TimeSpan nextStreamInteraction = TimeSpan.MaxValue;
for (int i = 0; i < this.clientInitiatedStreams.Count; i++)
{
var streamInteraction = this.clientInitiatedStreams[i].NextInteraction;
if (streamInteraction < nextStreamInteraction)
nextStreamInteraction = streamInteraction;
}
wait = (int)Math.Min(wait, nextStreamInteraction.TotalMilliseconds);
wait = (int)Math.Min(wait, 1000);
if (wait >= 1)
{
//if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
// HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), string.Format("Sleeping for {0:N0}ms", wait), this.Context);
this.newFrameSignal.WaitOne(wait);
now = DateTime.UtcNow;
}
}
// Don't send a new ping until a pong isn't received for the last one
if (now - this.lastPingSent >= this.ConnectionSettings.PingFrequency && Interlocked.CompareExchange(ref this.waitingForPingAck, 1, 0) == 0)
{
this.lastPingSent = now;
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.None, this.Context);
BufferHelper.SetLong(frame.Payload.Data, 0, now.Ticks);
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), $"PING frame created with payload: {frame.Payload.Slice(0, 8)}", this.Context);
#endif
this.outgoingFrames.Add(frame);
}
// Process received frames
HTTP2FrameHeaderAndPayload header;
while (this.newFrames.TryDequeue(out header))
{
if (header.StreamId > 0)
{
switch (header.Type)
{
case HTTP2FrameTypes.DATA:
localConnectionWindow -= header.Payload.Count;
lastDataFrameReceived = now;
break;
}
HTTP2Stream http2Stream = FindStreamById(header.StreamId);
// Add frame to the stream, so it can process it when its Process function is called
if (http2Stream != null)
{
http2Stream.AddFrame(header, this.outgoingFrames);
}
else
{
// Error? It's possible that we closed and removed the stream while the server was in the middle of sending frames
#if ENABLE_LOGGING
if (HTTPManager.Logger.Level == Loglevels.All)
HTTPManager.Logger.Warning(nameof(HTTP2ContentConsumer), $"Can't deliver frame: {header}, because no stream could be found for its Id!", this.Context);
#endif
BufferPool.Release(header.Payload);
}
}
else
{
switch (header.Type)
{
case HTTP2FrameTypes.SETTINGS:
this.settings.Process(header, this.outgoingFrames);
Interlocked.Exchange(ref this._maxAssignedRequests,
(int)Math.Min(this.ConnectionSettings.MaxConcurrentStreams,
this.settings.RemoteSettings[HTTP2Settings.MAX_CONCURRENT_STREAMS]));
/*
PluginEventHelper.EnqueuePluginEvent(
new PluginEventInfo(PluginEvents.HTTP2ConnectProtocol,
new HTTP2ConnectProtocolInfo(this.conn.HostKey,
this.settings.MySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1 && this.settings.RemoteSettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1)));
*/
break;
case HTTP2FrameTypes.PING:
var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);
if ((pingFrame.Flags & HTTP2PingFlags.ACK) != 0)
{
if (Interlocked.CompareExchange(ref this.waitingForPingAck, 0, 1) == 0)
break; // waitingForPingAck was 0 == aren't expecting a ping ack!
// it was an ack, payload must contain what we sent
var ticks = BufferHelper.ReadLong(pingFrame.OpaqueData, 0);
// the difference between the current time and the time when the ping message is sent
TimeSpan diff = TimeSpan.FromTicks(now.Ticks - ticks);
#if ENABLE_LOGGING
if (diff.TotalSeconds > 10 || diff.TotalSeconds < 0)
HTTPManager.Logger.Warning(nameof(HTTP2ContentConsumer), $"Pong received with weird diff: {diff}! Payload: {pingFrame.OpaqueData}", this.Context);
#endif
// add it to the buffer
this.rtts.Add(diff.TotalMilliseconds);
// and calculate the new latency
this.Latency = CalculateLatency();
#if ENABLE_LOGGING
HTTPManager.Logger.Verbose(nameof(HTTP2ContentConsumer), string.Format("Latency: {0:F2}ms, RTT buffer: {1}", this.Latency, this.rtts.ToString()), this.Context);
#endif
}
else if ((pingFrame.Flags & HTTP2PingFlags.ACK) == 0)
{
// https://httpwg.org/specs/rfc7540.html#PING
// if it wasn't an ack for our ping, we have to send one
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.ACK, this.Context);
Array.Copy(pingFrame.OpaqueData.Data, 0, frame.Payload.Data, 0, pingFrame.OpaqueData.Count);
this.outgoingFrames.Insert(0, frame);
}
BufferPool.Release(pingFrame.OpaqueData);
break;
case HTTP2FrameTypes.WINDOW_UPDATE:
var windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(header);
this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
break;
case HTTP2FrameTypes.GOAWAY:
// parse the frame, so we can print out detailed information
HTTP2GoAwayFrame goAwayFrame = HTTP2FrameHelper.ReadGoAwayFrame(header);
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Received GOAWAY frame: " + goAwayFrame.ToString(), this.Context);
#endif
abortWithMessage = string.Format("Server closing the connection! Error code: {0} ({1}) Additonal Debug Data: {2}",
goAwayFrame.Error, goAwayFrame.ErrorCode, goAwayFrame.AdditionalDebugData);
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
this.clientInitiatedStreams[i].Abort(abortWithMessage);
this.clientInitiatedStreams.Clear();
// set the running flag to false, so the thread can exit
this.isRunning = false;
BufferPool.Release(goAwayFrame.AdditionalDebugData);
//this.conn.State = HTTPConnectionStates.Closed;
break;
case HTTP2FrameTypes.ALT_SVC:
//HTTP2AltSVCFrame altSvcFrame = HTTP2FrameHelper.ReadAltSvcFrame(header);
// Implement
//HTTPManager.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(altSvcFrame.Origin, ))
break;
}
if (header.Payload != null)
BufferPool.Release(header.Payload);
}
}
// If no pong received in a (configurable) reasonable time, treat the connection broken
if (this.waitingForPingAck != 0)
{
// Even if we weren't received a ping ack, check for data frames.
// If data is flowing in, we can say that the connection is still healthy.
var lastPingDiff = now - this.lastPingSent;
var lastDataDiff = now - this.lastDataFrameReceived;
if(lastPingDiff >= this.ConnectionSettings.Timeout && lastDataDiff >= this.ConnectionSettings.Timeout)
throw new TimeoutException("Ping ACK isn't received in time!");
}
// pre-test stream count to lock only when truly needed.
if (this.clientInitiatedStreams.Count < _maxAssignedRequests && this.isRunning)
{
// grab requests from queue
HTTPRequest request;
while (this.clientInitiatedStreams.Count < _maxAssignedRequests && this.requestQueue.TryDequeue(out request))
{
HTTP2Stream newStream = null;
if (request.Tag is CustomHTTP2StreamFactory factory)
{
newStream = factory(request, (UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
}
else
{
newStream = new HTTP2Stream((UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
}
newStream.Assign(request);
this.clientInitiatedStreams.Add(newStream);
}
}
// send any settings changes
this.settings.SendChanges(this.outgoingFrames);
atLeastOneStreamHasAFrameToSend = false;
// process other streams
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
{
var stream = this.clientInitiatedStreams[i];
stream.Process(this.outgoingFrames);
// remove closed, empty streams (not enough to check the closed flag, a closed stream still can contain frames to send)
if (stream.State == HTTP2StreamStates.Closed && !stream.HasFrameToSend)
{
this.clientInitiatedStreams.RemoveAt(i--);
stream.Removed();
Interlocked.Decrement(ref this._assignedRequest);
}
atLeastOneStreamHasAFrameToSend |= stream.HasFrameToSend;
this.lastInteraction = now;
}
// If we encounter a data frame that too large for the current remote window, we have to stop
// sending all data frames as we could send smaller data frames before the large ones.
// Room for improvement: An improvement would be here to stop data frame sending per-stream.
bool haltDataSending = false;
if (this.ShutdownType == ShutdownTypes.Running && !this.SentGoAwayFrame && now - this.lastInteraction >= this.ConnectionSettings.MaxIdleTime)
{
this.lastInteraction = now;
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Reached idle time, sending GoAway frame!", this.Context);
#endif
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR, this.Context));
this.goAwaySentAt = now;
}
// https://httpwg.org/specs/rfc7540.html#GOAWAY
// Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote peer can know whether a stream has been partially processed or not.
if (this.ShutdownType == ShutdownTypes.Gentle && !this.SentGoAwayFrame)
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Connection abort requested, sending GoAway frame!", this.Context);
#endif
this.outgoingFrames.Clear();
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR, this.Context));
this.goAwaySentAt = now;
}
if (this.isRunning && this.SentGoAwayFrame && now - goAwaySentAt >= this.MaxGoAwayWaitTime)
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "No GoAway frame received back. Really quitting now!", this.Context);
#endif
this.isRunning = false;
continue;
}
if (localConnectionWindow < updateConnectionWindowAt)
{
UInt32 diff = (UInt32)(initialConnectionWindowSize - localConnectionWindow);
#if ENABLE_LOGGING
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), $"Updating local connection window by {diff:N0} ({initialConnectionWindowSize:N0} - {localConnectionWindow:N0})", this.Context);
#endif
this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, diff, this.Context));
localConnectionWindow = initialConnectionWindowSize;
}
// Go through all the collected frames and send them.
for (int i = 0; i < this.outgoingFrames.Count; ++i)
{
var frame = this.outgoingFrames[i];
#if ENABLE_LOGGING
if (HTTPManager.Logger.IsDiagnostic && frame.Type != HTTP2FrameTypes.DATA /*&& frame.Type != HTTP2FrameTypes.PING*/)
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Sending frame: " + frame.ToString(), this.Context);
#endif
// post process frames
switch (frame.Type)
{
case HTTP2FrameTypes.DATA:
if (haltDataSending)
continue;
// if the tracked remoteWindow is smaller than the frame's payload, we stop sending
// data frames until we receive window-update frames
if (frame.Payload.Count > this.remoteWindow)
{
haltDataSending = true;
#if ENABLE_LOGGING
HTTPManager.Logger.Warning(nameof(HTTP2ContentConsumer), string.Format("Data sending halted for this round. Remote Window: {0:N0}, frame: {1}", this.remoteWindow, frame.ToString()), this.Context);
#endif
continue;
}
break;
}
this.outgoingFrames.RemoveAt(i--);
static void SendHeader(WriteOnlyBufferedStream stream,
HTTP2FrameHeaderAndPayload headerAndPayload)
{
Span<byte> frameData = stackalloc byte[9];
HTTP2FrameHelper.HeaderAsBinary(headerAndPayload, frameData);
stream.Write(frameData);
}
SendHeader(bufferedStream, frame);
if (frame.Payload.Count > 0)
{
bufferedStream.Write(frame.Payload.Data, frame.Payload.Offset, frame.Payload.Count);
if (!frame.DontUseMemPool)
BufferPool.Release(frame.Payload);
}
if (frame.Type == HTTP2FrameTypes.DATA)
this.remoteWindow -= (uint)frame.Payload.Count;
}
bufferedStream.Flush();
} // while (this.isRunning)
bufferedStream.Flush();
}
}
catch (Exception ex)
{
abortWithMessage = ex.ToString();
// Log out the exception if it's a non-expected one.
if (this.ShutdownType == ShutdownTypes.Running && this.isRunning && !this.SentGoAwayFrame && !HTTPManager.IsQuitting)
HTTPManager.Logger.Exception(nameof(HTTP2ContentConsumer), "Sender thread", ex, this.Context);
}
finally
{
this.isRunning = false;
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), $"Sender thread closing - cleaning up remaining requests({this.clientInitiatedStreams.Count})...", this.Context);
#endif
if (string.IsNullOrEmpty(abortWithMessage))
abortWithMessage = "Connection closed unexpectedly";
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
this.clientInitiatedStreams[i].Abort(abortWithMessage);
this.clientInitiatedStreams.Clear();
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Sender thread closing", this.Context);
#endif
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed));
}
}
private void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
{
switch (setting)
{
case HTTP2Settings.INITIAL_WINDOW_SIZE:
this.remoteWindow = newValue - (oldValue - this.remoteWindow);
break;
}
}
public void SetBinding(PeekableContentProviderStream contentProvider) => this.ContentProvider = contentProvider;
public void UnsetBinding() => this.ContentProvider = null;
public void OnContent()
{
try
{
while (this.isRunning && HTTP2FrameHelper.CanReadFullFrame(this.ContentProvider))
{
HTTP2FrameHeaderAndPayload header = HTTP2FrameHelper.ReadHeader(this.ContentProvider, this.Context);
#if ENABLE_LOGGING
if (HTTPManager.Logger.IsDiagnostic /*&& header.Type != HTTP2FrameTypes.DATA /*&& header.Type != HTTP2FrameTypes.PING*/)
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "New frame received: " + header.ToString(), this.Context);
#endif
// Add the new frame to the queue. Processing it on the write thread gives us the advantage that
// we don't have to deal with too much locking.
this.newFrames.Enqueue(header);
}
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(HTTP2ContentConsumer), "", ex, this.Context);
}
finally
{
// ping write thread to process the new frame
this.newFrameSignal?.Set();
}
}
public void OnConnectionClosed()
{
#if ENABLE_LOGGING
HTTPManager.Logger.Verbose(nameof(HTTP2ContentConsumer), $"{nameof(OnConnectionClosed)}({this.isRunning})", this.Context);
#endif
this.isRunning = false;
this.newFrameSignal?.Set();
}
public void OnError(Exception ex)
{
#if ENABLE_LOGGING
HTTPManager.Logger.Exception(nameof(HTTP2ContentConsumer), $"{nameof(OnError)}({this.isRunning}, {ex})", ex, this.Context);
#endif
this.isRunning = false;
this.newFrameSignal?.Set();
}
private double CalculateLatency()
{
if (this.rtts.Count == 0)
return 0;
double sumLatency = 0;
for (int i = 0; i < this.rtts.Count; ++i)
sumLatency += this.rtts[i];
return sumLatency / this.rtts.Count;
}
HTTP2Stream FindStreamById(UInt32 streamId)
{
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
{
var stream = this.clientInitiatedStreams[i];
if (stream.Id == streamId)
return stream;
}
return null;
}
public ShutdownTypes ShutdownType { get; private set; }
public void Shutdown(ShutdownTypes type)
{
this.ShutdownType = type;
switch (this.ShutdownType)
{
case ShutdownTypes.Gentle:
this.newFrameSignal.Set();
break;
case ShutdownTypes.Immediate:
this.conn?.TopStream?.Dispose();
break;
}
}
public void Dispose()
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Dispose", this.Context);
#endif
while (this.newFrames.TryDequeue(out var frame))
BufferPool.Release(frame.Payload);
foreach (var frame in this.outgoingFrames)
BufferPool.Release(frame.Payload);
this.outgoingFrames.Clear();
HTTPRequest request = null;
while (this.requestQueue.TryDequeue(out request))
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), string.Format("Dispose - Request '{0}' IsCancellationRequested: {1}", request.CurrentUri.ToString(), request.IsCancellationRequested.ToString()), this.Context);
#endif
RequestEventHelper.EnqueueRequestEvent(request.IsCancellationRequested ? new RequestEventInfo(request, HTTPRequestStates.Aborted, null) : new RequestEventInfo(request, RequestEvents.Resend));
}
this.newFrameSignal?.Close();
this.newFrameSignal = null;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e555764ed1609a4bb57371203918367
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,430 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.Streams;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.PlatformSupport.Memory;
using System;
using System.Collections.Generic;
using System.IO;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
// https://httpwg.org/specs/rfc7540.html#ErrorCodes
public enum HTTP2ErrorCodes
{
NO_ERROR = 0x00,
PROTOCOL_ERROR = 0x01,
INTERNAL_ERROR = 0x02,
FLOW_CONTROL_ERROR = 0x03,
SETTINGS_TIMEOUT = 0x04,
STREAM_CLOSED = 0x05,
FRAME_SIZE_ERROR = 0x06,
REFUSED_STREAM = 0x07,
CANCEL = 0x08,
COMPRESSION_ERROR = 0x09,
CONNECT_ERROR = 0x0A,
ENHANCE_YOUR_CALM = 0x0B,
INADEQUATE_SECURITY = 0x0C,
HTTP_1_1_REQUIRED = 0x0D
}
public static class HTTP2FrameHelper
{
public static HTTP2ContinuationFrame ReadContinuationFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#CONTINUATION
HTTP2ContinuationFrame frame = new HTTP2ContinuationFrame(header);
frame.HeaderBlockFragment = header.Payload;
header.Payload = BufferSegment.Empty;
return frame;
}
public static HTTP2WindowUpdateFrame ReadWindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
HTTP2WindowUpdateFrame frame = new HTTP2WindowUpdateFrame(header);
frame.ReservedBit = BufferHelper.ReadBit(header.Payload.Data[0], 0);
frame.WindowSizeIncrement = BufferHelper.ReadUInt31(header.Payload, 0);
return frame;
}
public static HTTP2GoAwayFrame ReadGoAwayFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#GOAWAY
// str id error
// | 0, 1, 2, 3 | 4, 5, 6, 7 | ...
HTTP2GoAwayFrame frame = new HTTP2GoAwayFrame(header);
frame.ReservedBit = BufferHelper.ReadBit(header.Payload.Data[0], 0);
frame.LastStreamId = BufferHelper.ReadUInt31(header.Payload, 0);
frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 4);
var additionalDebugDataLength = header.Payload.Count - 8;
if (additionalDebugDataLength > 0)
{
var buff = BufferPool.Get(additionalDebugDataLength, true);
Array.Copy(header.Payload.Data, 8, buff, 0, additionalDebugDataLength);
frame.AdditionalDebugData = buff.AsBuffer(additionalDebugDataLength);
}
return frame;
}
public static HTTP2PingFrame ReadPingFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#PING
HTTP2PingFrame frame = new HTTP2PingFrame(header);
Array.Copy(header.Payload.Data, 0, frame.OpaqueData.Data, 0, frame.OpaqueData.Count);
return frame;
}
public static HTTP2PushPromiseFrame ReadPush_PromiseFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#PUSH_PROMISE
HTTP2PushPromiseFrame frame = new HTTP2PushPromiseFrame(header);
int HeaderBlockFragmentLength = header.Payload.Count - 4; // PromisedStreamId
bool isPadded = (frame.Flags & HTTP2PushPromiseFlags.PADDED) != 0;
if (isPadded)
{
frame.PadLength = header.Payload.Data[0];
HeaderBlockFragmentLength -= 1 + (frame.PadLength ?? 0);
}
frame.ReservedBit = BufferHelper.ReadBit(header.Payload.Data[1], 0);
frame.PromisedStreamId = BufferHelper.ReadUInt31(header.Payload, 1);
var HeaderBlockFragmentIdx = isPadded ? 5 : 4;
frame.HeaderBlockFragment = header.Payload.Slice(HeaderBlockFragmentIdx, HeaderBlockFragmentLength);
header.Payload = BufferSegment.Empty;
return frame;
}
public static HTTP2RSTStreamFrame ReadRST_StreamFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
HTTP2RSTStreamFrame frame = new HTTP2RSTStreamFrame(header);
frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 0);
return frame;
}
public static HTTP2PriorityFrame ReadPriorityFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#PRIORITY
if (header.Payload.Count != 5)
{
//throw FRAME_SIZE_ERROR
}
HTTP2PriorityFrame frame = new HTTP2PriorityFrame(header);
frame.IsExclusive = BufferHelper.ReadBit(header.Payload.Data[0], 0);
frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, 0);
frame.Weight = header.Payload.Data[4];
return frame;
}
public static HTTP2HeadersFrame ReadHeadersFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#HEADERS
HTTP2HeadersFrame frame = new HTTP2HeadersFrame(header);
var HeaderBlockFragmentLength = header.Payload.Count;
bool isPadded = (frame.Flags & HTTP2HeadersFlags.PADDED) != 0;
bool isPriority = (frame.Flags & HTTP2HeadersFlags.PRIORITY) != 0;
int payloadIdx = 0;
if (isPadded)
{
frame.PadLength = header.Payload.Data[payloadIdx++];
int subLength = 1 + (frame.PadLength ?? 0);
if (subLength <= HeaderBlockFragmentLength)
HeaderBlockFragmentLength -= subLength;
//else
// throw PROTOCOL_ERROR;
}
if (isPriority)
{
frame.IsExclusive = BufferHelper.ReadBit(header.Payload.Data[payloadIdx], 0);
frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, payloadIdx);
payloadIdx += 4;
frame.Weight = header.Payload.Data[payloadIdx++];
int subLength = 5;
if (subLength <= HeaderBlockFragmentLength)
HeaderBlockFragmentLength -= subLength;
//else
// throw PROTOCOL_ERROR;
}
var HeaderBlockFragmentIdx = payloadIdx;
frame.HeaderBlockFragment = header.Payload.Slice(HeaderBlockFragmentIdx, HeaderBlockFragmentLength);
header.Payload = BufferSegment.Empty;
return frame;
}
public static HTTP2DataFrame ReadDataFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#DATA
HTTP2DataFrame frame = new HTTP2DataFrame(header);
var DataLength = header.Payload.Count;
bool isPadded = (frame.Flags & HTTP2DataFlags.PADDED) != 0;
if (isPadded)
{
frame.PadLength = header.Payload.Data[0];
int subLength = 1 + (frame.PadLength ?? 0);
if (subLength <= DataLength)
DataLength -= subLength;
//else
// throw PROTOCOL_ERROR;
}
var DataIdx = isPadded ? 1 : 0;
frame.Data = header.Payload.Slice(DataIdx, DataLength);
header.Payload = BufferSegment.Empty;
return frame;
}
public static HTTP2AltSVCFrame ReadAltSvcFrame(HTTP2FrameHeaderAndPayload header)
{
HTTP2AltSVCFrame frame = new HTTP2AltSVCFrame(header);
// Implement
return frame;
}
public static void StreamRead(Stream stream, byte[] buffer, int offset, uint count)
{
if (count == 0)
return;
uint sumRead = 0;
do
{
int readCount = (int)(count - sumRead);
int streamReadCount = stream.Read(buffer, (int)(offset + sumRead), readCount);
if (streamReadCount <= 0 && readCount > 0)
throw new Exception("TCP Stream closed!");
sumRead += (uint)streamReadCount;
} while (sumRead < count);
}
public static void StreamRead(Stream stream, BufferSegment buffer)
{
if (buffer.Count == 0)
return;
uint sumRead = 0;
do
{
int readCount = (int)(buffer.Count - sumRead);
int streamReadCount = stream.Read(buffer.Data, (int)(buffer.Offset + sumRead), readCount);
if (streamReadCount <= 0 && readCount > 0)
throw new Exception("TCP Stream closed!");
sumRead += (uint)streamReadCount;
} while (sumRead < buffer.Count);
}
public static void HeaderAsBinary(HTTP2FrameHeaderAndPayload header, Span<byte> buffer)
{
// https://httpwg.org/specs/rfc7540.html#FrameHeader
BufferHelper.SetUInt24(buffer, 0, (uint)header.Payload.Count);
buffer[3] = (byte)header.Type;
buffer[4] = header.Flags;
BufferHelper.SetUInt31(buffer, 5, header.StreamId);
}
public unsafe static bool CanReadFullFrame(PeekableStream stream)
{
// https://httpwg.org/specs/rfc7540.html#FrameHeader
// A frame without any payload is 9 bytes
if (stream.Length < 9)
return false;
stream.BeginPeek();
// First 3 bytes are the payload length
var rawLength = stackalloc byte[3];
rawLength[0] = (byte)stream.PeekByte();
rawLength[1] = (byte)stream.PeekByte();
rawLength[2] = (byte)stream.PeekByte();
var payloadLength = (UInt32)(rawLength[2] | rawLength[1] << 8 | rawLength[0] << 16);
return stream.Length >= (9 + payloadLength);
}
public static HTTP2FrameHeaderAndPayload ReadHeader(Stream stream, LoggingContext context)
{
byte[] buffer = BufferPool.Get(9, true, context);
using var _ = buffer.AsAutoRelease();
StreamRead(stream, buffer, 0, 9);
HTTP2FrameHeaderAndPayload header = new HTTP2FrameHeaderAndPayload();
var PayloadLength = (int)BufferHelper.ReadUInt24(buffer, 0);
header.Type = (HTTP2FrameTypes)buffer[3];
header.Flags = buffer[4];
header.StreamId = BufferHelper.ReadUInt31(buffer, 5);
header.Payload = BufferPool.Get(PayloadLength, true, context).AsBuffer(PayloadLength);
try
{
StreamRead(stream, header.Payload);
}
catch
{
BufferPool.Release(header.Payload);
throw;
}
return header;
}
public static HTTP2SettingsFrame ReadSettings(HTTP2FrameHeaderAndPayload header)
{
HTTP2SettingsFrame frame = new HTTP2SettingsFrame(header);
if (header.Payload.Count > 0)
{
int kvpCount = (int)(header.Payload.Count / 6);
frame.Settings = new List<KeyValuePair<HTTP2Settings, uint>>(kvpCount);
for (int i = 0; i < kvpCount; ++i)
{
HTTP2Settings key = (HTTP2Settings)BufferHelper.ReadUInt16(header.Payload.Data, i * 6);
UInt32 value = BufferHelper.ReadUInt32(header.Payload, (i * 6) + 2);
frame.Settings.Add(new KeyValuePair<HTTP2Settings, uint>(key, value));
}
}
return frame;
}
public static HTTP2FrameHeaderAndPayload CreateACKSettingsFrame()
{
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.SETTINGS;
frame.Flags = (byte)HTTP2SettingsFlags.ACK;
return frame;
}
public static HTTP2FrameHeaderAndPayload CreateSettingsFrame(List<KeyValuePair<HTTP2Settings, UInt32>> settings, LoggingContext context)
{
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.SETTINGS;
frame.Flags = 0;
var PayloadLength = settings.Count * 6;
frame.Payload = BufferPool.Get(PayloadLength, true, context).AsBuffer(PayloadLength);
for (int i = 0; i < settings.Count; ++i)
{
BufferHelper.SetUInt16(frame.Payload.Data, i * 6, (UInt16)settings[i].Key);
BufferHelper.SetUInt32(frame.Payload.Data, (i * 6) + 2, settings[i].Value);
}
return frame;
}
public static HTTP2FrameHeaderAndPayload CreatePingFrame(HTTP2PingFlags flags, LoggingContext context)
{
// https://httpwg.org/specs/rfc7540.html#PING
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.PING;
frame.Flags = (byte)flags;
frame.StreamId = 0;
frame.Payload = BufferPool.Get(8, true, context).AsBuffer(8);
return frame;
}
public static HTTP2FrameHeaderAndPayload CreateWindowUpdateFrame(UInt32 streamId, UInt32 windowSizeIncrement, LoggingContext context)
{
// https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.WINDOW_UPDATE;
frame.Flags = 0;
frame.StreamId = streamId;
frame.Payload = BufferPool.Get(4, true, context).AsBuffer(4);
BufferHelper.SetBit(0, 0, 0);
BufferHelper.SetUInt31(frame.Payload.Data, 0, windowSizeIncrement);
return frame;
}
public static HTTP2FrameHeaderAndPayload CreateGoAwayFrame(UInt32 lastStreamId, HTTP2ErrorCodes error, LoggingContext context)
{
// https://httpwg.org/specs/rfc7540.html#GOAWAY
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.GOAWAY;
frame.Flags = 0;
frame.StreamId = 0;
frame.Payload = BufferPool.Get(8, true, context).AsBuffer(8);
BufferHelper.SetUInt31(frame.Payload.Data, 0, lastStreamId);
BufferHelper.SetUInt31(frame.Payload.Data, 4, (UInt32)error);
return frame;
}
public static HTTP2FrameHeaderAndPayload CreateRSTFrame(UInt32 streamId, HTTP2ErrorCodes errorCode, LoggingContext context)
{
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.RST_STREAM;
frame.Flags = 0;
frame.StreamId = streamId;
frame.Payload = BufferPool.Get(4, true, context).AsBuffer(4);
BufferHelper.SetUInt32(frame.Payload.Data, 0, (UInt32)errorCode);
return frame;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ed90f479cb36204458007c8c20c99490
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,392 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using System;
using System.Collections.Generic;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.PlatformSupport.Text;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
// https://httpwg.org/specs/rfc7540.html#iana-frames
public enum HTTP2FrameTypes : byte
{
DATA = 0x00,
HEADERS = 0x01,
PRIORITY = 0x02,
RST_STREAM = 0x03,
SETTINGS = 0x04,
PUSH_PROMISE = 0x05,
PING = 0x06,
GOAWAY = 0x07,
WINDOW_UPDATE = 0x08,
CONTINUATION = 0x09,
// https://tools.ietf.org/html/rfc7838#section-4
ALT_SVC = 0x0A
}
[Flags]
public enum HTTP2DataFlags : byte
{
None = 0x00,
END_STREAM = 0x01,
PADDED = 0x08,
}
[Flags]
public enum HTTP2HeadersFlags : byte
{
None = 0x00,
END_STREAM = 0x01,
END_HEADERS = 0x04,
PADDED = 0x08,
PRIORITY = 0x20,
}
[Flags]
public enum HTTP2SettingsFlags : byte
{
None = 0x00,
ACK = 0x01,
}
[Flags]
public enum HTTP2PushPromiseFlags : byte
{
None = 0x00,
END_HEADERS = 0x04,
PADDED = 0x08,
}
[Flags]
public enum HTTP2PingFlags : byte
{
None = 0x00,
ACK = 0x01,
}
[Flags]
public enum HTTP2ContinuationFlags : byte
{
None = 0x00,
END_HEADERS = 0x04,
}
public struct HTTP2FrameHeaderAndPayload
{
public HTTP2FrameTypes Type;
public byte Flags;
public UInt32 StreamId;
//public byte[] Payload;
public BufferSegment Payload;
//public UInt32 PayloadOffset;
//public UInt32 PayloadLength;
public bool DontUseMemPool;
public override string ToString()
{
return $"[HTTP2FrameHeaderAndPayload Type: {Type}, Flags: {Flags.ToBinaryStr()}, StreamId: {StreamId}, DontUseMemPool: {DontUseMemPool}, Payload: {Payload}]";
}
public string PayloadAsHex() => this.Payload.ToString();
}
public struct HTTP2SettingsFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public HTTP2SettingsFlags Flags { get { return (HTTP2SettingsFlags)this.Header.Flags; } }
public List<KeyValuePair<HTTP2Settings, UInt32>> Settings;
public HTTP2SettingsFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.Settings = null;
}
public override string ToString()
{
string settings = null;
if (this.Settings != null)
{
System.Text.StringBuilder sb = StringBuilderPool.Get(this.Settings.Count + 2);
sb.Append("[");
foreach (var kvp in this.Settings)
sb.AppendFormat("[{0}: {1}]", kvp.Key, kvp.Value);
sb.Append("]");
settings = StringBuilderPool.ReleaseAndGrab(sb);
}
return string.Format("[HTTP2SettingsFrame Header: {0}, Flags: {1}, Settings: {2}]", this.Header.ToString(), this.Flags, settings ?? "Empty");
}
}
public struct HTTP2DataFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public HTTP2DataFlags Flags { get { return (HTTP2DataFlags)this.Header.Flags; } }
public byte? PadLength;
public BufferSegment Data;
public HTTP2DataFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.PadLength = null;
this.Data = BufferSegment.Empty;
}
public override string ToString()
{
return string.Format("[HTTP2DataFrame Header: {0}, Flags: {1}, PadLength: {2}, Data: {3}]",
this.Header.ToString(),
this.Flags,
this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
this.Data.ToString());
}
}
public struct HTTP2HeadersFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public HTTP2HeadersFlags Flags { get { return (HTTP2HeadersFlags)this.Header.Flags; } }
public byte? PadLength;
public byte? IsExclusive;
public UInt32? StreamDependency;
public byte? Weight;
public BufferSegment HeaderBlockFragment;
public HTTP2HeadersFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.PadLength = null;
this.IsExclusive = null;
this.StreamDependency = null;
this.Weight = null;
this.HeaderBlockFragment = BufferSegment.Empty;
}
public override string ToString()
{
return string.Format("[HTTP2HeadersFrame Header: {0}, Flags: {1}, PadLength: {2}, IsExclusive: {3}, StreamDependency: {4}, Weight: {5}, HeaderBlockFragmentLength: {6}]",
this.Header.ToString(),
this.Flags,
this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
this.IsExclusive == null ? "Empty" : this.IsExclusive.Value.ToString(),
this.StreamDependency == null ? "Empty" : this.StreamDependency.Value.ToString(),
this.Weight == null ? "Empty" : this.Weight.Value.ToString(),
this.HeaderBlockFragment.ToString());
}
}
public struct HTTP2PriorityFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public byte IsExclusive;
public UInt32 StreamDependency;
public byte Weight;
public HTTP2PriorityFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.IsExclusive = 0;
this.StreamDependency = 0;
this.Weight = 0;
}
public override string ToString()
{
return string.Format("[HTTP2PriorityFrame Header: {0}, IsExclusive: {1}, StreamDependency: {2}, Weight: {3}]",
this.Header.ToString(), this.IsExclusive, this.StreamDependency, this.Weight);
}
}
public struct HTTP2RSTStreamFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public UInt32 ErrorCode;
public HTTP2ErrorCodes Error { get { return (HTTP2ErrorCodes)this.ErrorCode; } }
public HTTP2RSTStreamFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.ErrorCode = 0;
}
public override string ToString()
{
return string.Format("[HTTP2RST_StreamFrame Header: {0}, Error: {1}({2})]", this.Header.ToString(), this.Error, this.ErrorCode);
}
}
public struct HTTP2PushPromiseFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public HTTP2PushPromiseFlags Flags { get { return (HTTP2PushPromiseFlags)this.Header.Flags; } }
public byte? PadLength;
public byte ReservedBit;
public UInt32 PromisedStreamId;
public BufferSegment HeaderBlockFragment;
//public UInt32 HeaderBlockFragmentIdx;
//public byte[] HeaderBlockFragment;
//public UInt32 HeaderBlockFragmentLength;
public HTTP2PushPromiseFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.PadLength = null;
this.ReservedBit = 0;
this.PromisedStreamId = 0;
//this.HeaderBlockFragmentIdx = 0;
//this.HeaderBlockFragment = null;
//this.HeaderBlockFragmentLength = 0;
this.HeaderBlockFragment = BufferSegment.Empty;
}
public override string ToString()
{
return string.Format("[HTTP2Push_PromiseFrame Header: {0}, Flags: {1}, PadLength: {2}, ReservedBit: {3}, PromisedStreamId: {4}, HeaderBlockFragmentLength: {5}]",
this.Header.ToString(),
this.Flags,
this.PadLength == null ? "Empty" : this.PadLength.Value.ToString(),
this.ReservedBit,
this.PromisedStreamId,
this.HeaderBlockFragment);
}
}
public struct HTTP2PingFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public HTTP2PingFlags Flags { get { return (HTTP2PingFlags)this.Header.Flags; } }
//public readonly byte[] OpaqueData;
//public readonly byte OpaqueDataLength;
public readonly BufferSegment OpaqueData;
public HTTP2PingFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.OpaqueData = BufferPool.Get(8, true).AsBuffer(8);
}
public override string ToString()
{
return string.Format("[HTTP2PingFrame Header: {0}, Flags: {1}, OpaqueData: {2}]",
this.Header.ToString(),
this.Flags,
this.OpaqueData);
}
}
public struct HTTP2GoAwayFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public HTTP2ErrorCodes Error { get { return (HTTP2ErrorCodes)this.ErrorCode; } }
public byte ReservedBit;
public UInt32 LastStreamId;
public UInt32 ErrorCode;
public BufferSegment AdditionalDebugData;
//public byte[] AdditionalDebugData;
//public UInt32 AdditionalDebugDataLength;
public HTTP2GoAwayFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.ReservedBit = 0;
this.LastStreamId = 0;
this.ErrorCode = 0;
this.AdditionalDebugData = BufferSegment.Empty;
}
public override string ToString()
{
return string.Format("[HTTP2GoAwayFrame Header: {0}, ReservedBit: {1}, LastStreamId: {2}, Error: {3}({4}), AdditionalDebugData: {5}]",
this.Header.ToString(),
this.ReservedBit,
this.LastStreamId,
this.Error,
this.ErrorCode,
this.AdditionalDebugData.ToString());
}
}
public struct HTTP2WindowUpdateFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public byte ReservedBit;
public UInt32 WindowSizeIncrement;
public HTTP2WindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.ReservedBit = 0;
this.WindowSizeIncrement = 0;
}
public override string ToString()
{
return string.Format("[HTTP2WindowUpdateFrame Header: {0}, ReservedBit: {1}, WindowSizeIncrement: {2}]",
this.Header.ToString(), this.ReservedBit, this.WindowSizeIncrement);
}
}
public struct HTTP2ContinuationFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public HTTP2ContinuationFlags Flags { get { return (HTTP2ContinuationFlags)this.Header.Flags; } }
public BufferSegment HeaderBlockFragment;
public HTTP2ContinuationFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.HeaderBlockFragment = BufferSegment.Empty;
}
public override string ToString()
{
return string.Format("[HTTP2ContinuationFrame Header: {0}, Flags: {1}, HeaderBlockFragment: {2}]",
this.Header.ToString(),
this.Flags,
this.HeaderBlockFragment);
}
}
/// <summary>
/// https://tools.ietf.org/html/rfc7838#section-4
/// </summary>
public struct HTTP2AltSVCFrame
{
public readonly HTTP2FrameHeaderAndPayload Header;
public string Origin;
public string AltSvcFieldValue;
public HTTP2AltSVCFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.Origin = null;
this.AltSvcFieldValue = null;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5b1b3f5f21e2324b9a6513e8d458167
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,160 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using System;
using System.Collections.Generic;
using Best.HTTP.Response;
using Best.HTTP.Response.Decompression;
using Best.HTTP.Shared;
using Best.HTTP.Shared.PlatformSupport.Memory;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
public sealed class HTTP2Response : HTTPResponse
{
// For progress report
public long ExpectedContentLength { get; private set; }
private string contentEncoding = null;
bool isPrepared;
private IDecompressor _decompressor;
public HTTP2Response(HTTPRequest request, bool isFromCache)
: base(request, isFromCache)
{
this.HTTPVersion = new Version(2, 0);
}
internal void AddHeaders(List<KeyValuePair<string, string>> headers)
{
this.ExpectedContentLength = -1;
Dictionary<string, List<string>> newHeaders = this.Request.DownloadSettings.OnHeadersReceived != null ? new Dictionary<string, List<string>>() : null;
for (int i = 0; i < headers.Count; ++i)
{
KeyValuePair<string, string> header = headers[i];
if (header.Key.Equals(":status", StringComparison.Ordinal))
{
base.StatusCode = int.Parse(header.Value);
base.Message = string.Empty;
}
else
{
if (string.IsNullOrEmpty(this.contentEncoding) && header.Key.Equals("content-encoding", StringComparison.OrdinalIgnoreCase))
{
this.contentEncoding = header.Value;
}
else if (base.Request.DownloadSettings.OnDownloadProgress != null && header.Key.Equals("content-length", StringComparison.OrdinalIgnoreCase))
{
long contentLength;
if (long.TryParse(header.Value, out contentLength))
this.ExpectedContentLength = contentLength;
else
HTTPManager.Logger.Information("HTTP2Response", string.Format("AddHeaders - Can't parse Content-Length as an int: '{0}'", header.Value), this.Context);
}
base.AddHeader(header.Key, header.Value);
}
if (newHeaders != null)
{
List<string> values;
if (!newHeaders.TryGetValue(header.Key, out values))
newHeaders.Add(header.Key, values = new List<string>(1));
values.Add(header.Value);
}
}
if (this.ExpectedContentLength == -1 && base.Request.DownloadSettings.OnDownloadProgress != null)
HTTPManager.Logger.Information("HTTP2Response", "AddHeaders - No Content-Length header found!", this.Context);
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.Request, newHeaders));
}
internal void Prepare(IDownloadContentBufferAvailable bufferAvailable)
{
if (!this.isPrepared)
{
this.isPrepared = true;
CreateDownloadStream(bufferAvailable);
base.BeginReceiveContent();
}
}
internal void ProcessData(BufferSegment payload)
{
// https://github.com/Benedicht/BestHTTP-Issues/issues/183
// If _decompressor is still null, remove the content encoding value and serve the content as-is.
if (!string.IsNullOrEmpty(this.contentEncoding) && this._decompressor == null)
{
if ((this._decompressor = DecompressorFactory.GetDecompressor(this.contentEncoding, this.Context)) == null)
this.contentEncoding = null;
}
if (!string.IsNullOrEmpty(this.contentEncoding))
{
BufferSegment result = BufferSegment.Empty;
bool release;
try
{
(result, release) = this._decompressor.Decompress(payload, false, true, this.Context);
}
catch
{
BufferPool.Release(payload);
throw;
}
if (release)
BufferPool.Release(payload);
base.FeedDownloadedContentChunk(result);
}
else
base.FeedDownloadedContentChunk(payload);
}
internal void FinishProcessData()
{
if (this._decompressor != null)
{
var (decompressed, _) = this._decompressor.Decompress(BufferSegment.Empty, true, true, this.Context);
if (decompressed != BufferSegment.Empty)
base.FeedDownloadedContentChunk(decompressed);
this._decompressor.Dispose();
this._decompressor = null;
}
base.FinishedContentReceiving();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
if (this._decompressor != null)
{
// In some cases, the request is aborted and the decompressor left in an incomplete state.
// Closing it might cause an exception that we don't care about.
try
{
this._decompressor.Dispose();
}
catch
{ }
this._decompressor = null;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fe46645d97549c146a8c38f1297a75ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,331 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using System;
using System.Collections.Generic;
using Best.HTTP.Shared;
using Best.HTTP.Shared.Logger;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
/// <summary>
/// <see href="https://httpwg.org/specs/rfc7540.html#iana-settings">Settings Registry</see>
/// </summary>
public enum HTTP2Settings : ushort
{
/// <summary>
/// Allows the sender to inform the remote endpoint of the maximum size of the
/// header compression table used to decode header blocks, in octets.
/// The encoder can select any size equal to or less than this value
/// by using signaling specific to the header compression format inside a header block (see [COMPRESSION]).
/// The initial value is 4,096 octets.
/// </summary>
HEADER_TABLE_SIZE = 0x01,
/// <summary>
/// This setting can be used to disable server push (Section 8.2).
/// An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0.
/// An endpoint that has both set this parameter to 0 and had it acknowledged MUST treat the receipt of a
/// PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
///
/// The initial value is 1, which indicates that server push is permitted.
/// Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
/// </summary>
ENABLE_PUSH = 0x02,
/// <summary>
/// Indicates the maximum number of concurrent streams that the sender will allow. This limit is directional:
/// it applies to the number of streams that the sender permits the receiver to create.
/// Initially, there is no limit to this value. It is recommended that this value be no smaller than 100,
/// so as to not unnecessarily limit parallelism.
///
/// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints.
/// A zero value does prevent the creation of new streams;
/// however, this can also happen for any limit that is exhausted with active streams.
/// Servers SHOULD only set a zero value for short durations; if a server does not wish to accept requests,
/// closing the connection is more appropriate.
/// </summary>
MAX_CONCURRENT_STREAMS = 0x03,
/// <summary>
/// Indicates the sender's initial window size (in octets) for stream-level flow control.
/// The initial value is 2^16-1 (65,535) octets.
///
/// This setting affects the window size of all streams (see Section 6.9.2).
///
/// Values above the maximum flow-control window size of 2^31-1 MUST be treated as a connection error
/// (Section 5.4.1) of type FLOW_CONTROL_ERROR.
/// </summary>
INITIAL_WINDOW_SIZE = 0x04,
/// <summary>
/// Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
///
/// The initial value is 2^14 (16,384) octets.
/// The value advertised by an endpoint MUST be between this initial value and the maximum allowed frame size
/// (2^24-1 or 16,777,215 octets), inclusive.
/// Values outside this range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
/// </summary>
MAX_FRAME_SIZE = 0x05,
/// <summary>
/// This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets.
/// The value is based on the uncompressed size of header fields,
/// including the length of the name and value in octets plus an overhead of 32 octets for each header field.
///
/// For any given request, a lower limit than what is advertised MAY be enforced. The initial value of this setting is unlimited.
/// </summary>
MAX_HEADER_LIST_SIZE = 0x06,
RESERVED = 0x07,
/// <summary>
/// https://tools.ietf.org/html/rfc8441
/// Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1, a client MAY use the Extended CONNECT as defined in this document when creating new streams.
/// Receipt of this parameter by a server does not have any impact.
///
/// A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with the value of 0 after previously sending a value of 1.
/// </summary>
ENABLE_CONNECT_PROTOCOL = 0x08,
/// <summary>
/// Allow endpoints to omit or ignore HTTP/2 priority signals.
/// <see href="https://www.rfc-editor.org/rfc/rfc9218.html">Extensible Prioritization Scheme for HTTP</see>
/// </summary>
NO_RFC7540_PRIORITIES = 0x09,
}
/// <summary>
/// Represents a registry for HTTP/2 settings.
/// </summary>
public sealed class HTTP2SettingsRegistry
{
/// <summary>
/// Gets a value indicating whether the registry is read-only.
/// </summary>
public bool IsReadOnly { get; private set; }
/// <summary>
/// Event triggered when a setting changes.
/// </summary>
public Action<HTTP2SettingsRegistry, HTTP2Settings, UInt32, UInt32> OnSettingChangedEvent;
private UInt32[] values;
private bool[] changeFlags;
/// <summary>
/// Indexer to get or set values based on an <see cref="HTTP2Settings"/> key.
/// </summary>
/// <param name="setting">The setting key.</param>
/// <returns>The value associated with the given setting key.</returns>
public UInt32 this[HTTP2Settings setting]
{
get { return this.values[(ushort)setting]; }
set
{
if (this.IsReadOnly)
throw new NotSupportedException("It's a read-only one!");
ushort idx = (ushort)setting;
// https://httpwg.org/specs/rfc7540.html#SettingValues
// An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST ignore that setting.
if (idx == 0 || idx >= this.values.Length)
return;
UInt32 oldValue = this.values[idx];
if (oldValue != value)
{
this.values[idx] = value;
this.changeFlags[idx] = true;
IsChanged = true;
if (this.OnSettingChangedEvent != null)
this.OnSettingChangedEvent(this, setting, oldValue, value);
}
}
}
/// <summary>
/// Gets a value indicating whether any setting has changed.
/// </summary>
public bool IsChanged { get; private set; }
private HTTP2SettingsManager _parent;
/// <summary>
/// Initializes a new instance of the HTTP2SettingsRegistry class.
/// </summary>
/// <param name="parent">The parent <see cref="HTTP2SettingsManager"/>.</param>
/// <param name="readOnly">Whether this registry is read-only.</param>
/// <param name="treatItAsAlreadyChanged">Whether to treat the registry as if a setting has already changed.</param>
public HTTP2SettingsRegistry(HTTP2SettingsManager parent, bool readOnly, bool treatItAsAlreadyChanged)
{
this._parent = parent;
this.values = new UInt32[HTTP2SettingsManager.KnownSettingsCount];
this.IsReadOnly = readOnly;
if (!this.IsReadOnly)
this.changeFlags = new bool[HTTP2SettingsManager.KnownSettingsCount];
// Set default values (https://httpwg.org/specs/rfc7540.html#iana-settings)
this.values[(UInt16)HTTP2Settings.HEADER_TABLE_SIZE] = 4096;
this.values[(UInt16)HTTP2Settings.ENABLE_PUSH] = 1;
this.values[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = 128;
this.values[(UInt16)HTTP2Settings.INITIAL_WINDOW_SIZE] = 65535;
this.values[(UInt16)HTTP2Settings.MAX_FRAME_SIZE] = 16384;
this.values[(UInt16)HTTP2Settings.MAX_HEADER_LIST_SIZE] = UInt32.MaxValue; // infinite
if (this.IsChanged = treatItAsAlreadyChanged)
{
this.changeFlags[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = true;
}
}
/// <summary>
/// Merges the specified settings into the current registry.
/// </summary>
/// <param name="settings">The settings to merge.</param>
public void Merge(List<KeyValuePair<HTTP2Settings, UInt32>> settings)
{
if (settings == null)
return;
for (int i = 0; i < settings.Count; ++i)
{
HTTP2Settings setting = settings[i].Key;
UInt16 key = (UInt16)setting;
UInt32 value = settings[i].Value;
if (key >= this.values.Length)
Array.Resize<uint>(ref this.values, (int)key + 1);
UInt32 oldValue = this.values[key];
this.values[key] = value;
if (oldValue != value && this.OnSettingChangedEvent != null)
this.OnSettingChangedEvent(this, setting, oldValue, value);
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Information("HTTP2SettingsRegistry", string.Format("Merge {0}({1}) = {2}", setting, key, value), this._parent.Context);
}
}
/// <summary>
/// Merges settings from another HTTP2SettingsRegistry into the current registry.
/// </summary>
/// <param name="from">The registry to merge settings from.</param>
public void Merge(HTTP2SettingsRegistry from)
{
if (this.values != null)
this.values = new uint[from.values.Length];
for (int i = 0; i < this.values.Length; ++i)
this.values[i] = from.values[i];
}
/// <summary>
/// Creates a new HTTP/2 frame based on the current registry settings.
/// </summary>
/// <param name="context">The logging context.</param>
/// <returns>A new HTTP/2 frame.</returns>
internal HTTP2FrameHeaderAndPayload CreateFrame(LoggingContext context)
{
List<KeyValuePair<HTTP2Settings, UInt32>> keyValuePairs = new List<KeyValuePair<HTTP2Settings, uint>>(HTTP2SettingsManager.KnownSettingsCount);
for (int i = 1; i < HTTP2SettingsManager.KnownSettingsCount; ++i)
if (this.changeFlags[i])
{
keyValuePairs.Add(new KeyValuePair<HTTP2Settings, uint>((HTTP2Settings)i, this[(HTTP2Settings)i]));
this.changeFlags[i] = false;
}
this.IsChanged = false;
return HTTP2FrameHelper.CreateSettingsFrame(keyValuePairs, context);
}
}
/// <summary>
/// Class to manager local and remote HTTP/2 settings.
/// </summary>
public sealed class HTTP2SettingsManager
{
public static readonly int KnownSettingsCount = Enum.GetNames(typeof(HTTP2Settings)).Length + 1;
/// <summary>
/// This is the ACKd or default settings that we sent to the server.
/// </summary>
public HTTP2SettingsRegistry MySettings { get; private set; }
/// <summary>
/// This is the setting that can be changed. It will be sent to the server ASAP, and when ACKd, it will be copied
/// to MySettings.
/// </summary>
public HTTP2SettingsRegistry InitiatedMySettings { get; private set; }
/// <summary>
/// Settings of the remote peer
/// </summary>
public HTTP2SettingsRegistry RemoteSettings { get; private set; }
public DateTime SettingsChangesSentAt { get; private set; }
public LoggingContext Context { get; private set; }
private HTTP2ConnectionSettings _connectionSettings;
public HTTP2SettingsManager(LoggingContext context, HTTP2ConnectionSettings connectionSettings)
{
this.Context = context;
this._connectionSettings = connectionSettings;
this.MySettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
this.InitiatedMySettings = new HTTP2SettingsRegistry(this, readOnly: false, treatItAsAlreadyChanged: true);
this.RemoteSettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
this.SettingsChangesSentAt = DateTime.MinValue;
}
internal void Process(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
if (frame.Type != HTTP2FrameTypes.SETTINGS)
return;
HTTP2SettingsFrame settingsFrame = HTTP2FrameHelper.ReadSettings(frame);
if (HTTPManager.Logger.Level <= Loglevels.Information)
HTTPManager.Logger.Information("HTTP2SettingsManager", "Processing Settings frame: " + settingsFrame.ToString(), this.Context);
if ((settingsFrame.Flags & HTTP2SettingsFlags.ACK) == HTTP2SettingsFlags.ACK)
{
this.MySettings.Merge(this.InitiatedMySettings);
this.SettingsChangesSentAt = DateTime.MinValue;
}
else
{
this.RemoteSettings.Merge(settingsFrame.Settings);
outgoingFrames.Add(HTTP2FrameHelper.CreateACKSettingsFrame());
}
}
internal void SendChanges(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
if (this.SettingsChangesSentAt != DateTime.MinValue && DateTime.UtcNow - this.SettingsChangesSentAt >= this._connectionSettings.Timeout)
{
HTTPManager.Logger.Error("HTTP2SettingsManager", "No ACK received for settings frame!", this.Context);
this.SettingsChangesSentAt = DateTime.MinValue;
}
// Upon receiving a SETTINGS frame with the ACK flag set, the sender of the altered parameters can rely on the setting having been applied.
if (!this.InitiatedMySettings.IsChanged)
return;
outgoingFrames.Add(this.InitiatedMySettings.CreateFrame(this.Context));
this.SettingsChangesSentAt = DateTime.UtcNow;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9723be24bbd74214dbff4fc66a5b24bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,663 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
#define ENABLE_LOGGING
using System;
using System.Collections.Generic;
using Best.HTTP.Request.Upload;
using Best.HTTP.Request.Timings;
using Best.HTTP.Response;
using Best.HTTP.Shared;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.PlatformSupport.Memory;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
// https://httpwg.org/specs/rfc7540.html#StreamStates
//
// Idle
// |
// V
// Open
// Receive END_STREAM / | \ Send END_STREAM
// v |R V
// Half Closed Remote |S Half Closed Locale
// \ |T /
// Send END_STREAM | RST_STREAM \ | / Receive END_STREAM | RST_STREAM
// Receive RST_STREAM \ | / Send RST_STREAM
// V
// Closed
//
// IDLE -> send headers -> OPEN -> send data -> HALF CLOSED - LOCAL -> receive headers -> receive Data -> CLOSED
// | ^ | ^
// +-------------------------------------+ +-----------------------------+
// END_STREAM flag present? END_STREAM flag present?
//
public enum HTTP2StreamStates
{
Idle,
//ReservedLocale,
//ReservedRemote,
Open,
HalfClosedLocal,
HalfClosedRemote,
Closed
}
/// <summary>
/// Implements an HTTP/2 logical stream.
/// </summary>
public class HTTP2Stream : IDownloadContentBufferAvailable
{
public UInt32 Id { get; private set; }
public HTTP2StreamStates State {
get { return this._state; }
protected set {
var oldState = this._state;
this._state = value;
#if ENABLE_LOGGING
if (oldState != this._state && HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] State changed from {1} to {2}", this.Id, oldState, this._state), this.Context);
#endif
}
}
private HTTP2StreamStates _state;
/// <summary>
/// This flag is checked by the connection to decide whether to do a new processing-frame sending round before sleeping until new data arrives
/// </summary>
public virtual bool HasFrameToSend
{
get
{
// Don't let the connection sleep until
return this.outgoing.Count > 0 || // we already booked at least one frame in advance
(this.State == HTTP2StreamStates.Open && this.remoteWindow > 0 && this.lastReadCount > 0); // we are in the middle of sending request data
}
}
/// <summary>
/// Next interaction scheduled by the stream relative to *now*. Its default is TimeSpan.MaxValue == no interaction.
/// </summary>
public virtual TimeSpan NextInteraction { get; } = TimeSpan.MaxValue;
public HTTPRequest AssignedRequest { get; protected set; }
public LoggingContext Context { get; protected set; }
public HTTP2ContentConsumer ParentHandler { get; private set; }
protected uint downloaded;
protected HTTP2SettingsManager settings;
protected HPACKEncoder encoder;
// Outgoing frames. The stream will send one frame per Process call, but because one step might be able to
// generate more than one frames, we use a list.
protected Queue<HTTP2FrameHeaderAndPayload> outgoing = new Queue<HTTP2FrameHeaderAndPayload>();
protected Queue<HTTP2FrameHeaderAndPayload> incomingFrames = new Queue<HTTP2FrameHeaderAndPayload>();
protected FramesAsStreamView headerView;
protected Int64 localWindow;
protected Int64 remoteWindow;
protected uint windowUpdateThreshold;
protected UInt32 assignDataLength;
protected long sentData;
protected long uploadLength;
protected bool isRSTFrameSent;
protected bool isEndSTRReceived;
protected HTTP2Response response;
protected int lastReadCount;
/// <summary>
/// Constructor to create a client stream.
/// </summary>
public HTTP2Stream(UInt32 id, HTTP2ContentConsumer parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder)
{
this.Id = id;
this.ParentHandler = parentHandler;
this.settings = registry;
this.encoder = hpackEncoder;
this.Context = new LoggingContext(this);
this.Context.Add("id", id);
this.Context.Add("Parent", parentHandler.Context);
this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
// Room for improvement: If INITIAL_WINDOW_SIZE is small (what we can consider a 'small' value?), threshold must be higher
this.windowUpdateThreshold = (uint)(this.remoteWindow / 2);
}
public virtual void Assign(HTTPRequest request)
{
this.Context.Add("Request", request.Context);
request.Timing.StartNext(TimingEventNames.Request_Sent);
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Request assigned to stream. Remote Window: {1:N0}. Uri: {2}", this.Id, this.remoteWindow, request.CurrentUri.ToString()), this.Context);
#endif
this.AssignedRequest = request;
this.downloaded = 0;
}
public void Process(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
if (this.AssignedRequest.IsCancellationRequested && !this.isRSTFrameSent)
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", $"[{this.Id}] Process({this.State}) - IsCancellationRequested", this.Context);
#endif
// These two are already set in HTTPRequest's Abort().
//this.AssignedRequest.Response = null;
//this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
this.outgoing.Clear();
if (this.State != HTTP2StreamStates.Idle)
this.outgoing.Enqueue(HTTP2FrameHelper.CreateRSTFrame(this.Id, HTTP2ErrorCodes.CANCEL, this.Context));
// We can close the stream if already received headers, or not even sent one
if (this.State == HTTP2StreamStates.HalfClosedRemote || this.State == HTTP2StreamStates.HalfClosedLocal || this.State == HTTP2StreamStates.Idle)
this.State = HTTP2StreamStates.Closed;
this.isRSTFrameSent = true;
}
// 1.) Go through incoming frames
ProcessIncomingFrames(outgoingFrames);
// 2.) Create outgoing frames based on the stream's state and the request processing state.
ProcessState(outgoingFrames);
// 3.) Send one frame per Process call
if (this.outgoing.Count > 0)
{
HTTP2FrameHeaderAndPayload frame = this.outgoing.Dequeue();
outgoingFrames.Add(frame);
// If END_Stream in header or data frame is present => half closed local
if ((frame.Type == HTTP2FrameTypes.HEADERS && (frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0) ||
(frame.Type == HTTP2FrameTypes.DATA && (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0))
{
this.State = HTTP2StreamStates.HalfClosedLocal;
}
}
}
public void AddFrame(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
// Room for improvement: error check for forbidden frames (like settings) and stream state
this.incomingFrames.Enqueue(frame);
ProcessIncomingFrames(outgoingFrames);
}
public void Abort(string msg)
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", $"[{this.Id}] Abort(\"{msg}\", {this.State}, {this.AssignedRequest.State})", this.Context);
#endif
if (this.State != HTTP2StreamStates.Closed)
{
// TODO: Remove AssignedRequest.State checks. If the main thread has delays processing queued up state change requests,
// the request's State can contain old information!
if (this.AssignedRequest.State != HTTPRequestStates.Processing)
{
// do nothing, its state is already set.
}
else if (this.AssignedRequest.IsCancellationRequested)
{
// These two are already set in HTTPRequest's Abort().
//this.AssignedRequest.Response = null;
//this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
this.State = HTTP2StreamStates.Closed;
}
else if (this.AssignedRequest.RetrySettings.Retries >= this.AssignedRequest.RetrySettings.MaxRetries || HTTPManager.IsQuitting)
{
this.AssignedRequest.Timing.StartNext(TimingEventNames.Queued);
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, HTTPRequestStates.Error, new Exception(msg)));
this.State = HTTP2StreamStates.Closed;
}
else
{
this.AssignedRequest.RetrySettings.Retries++;
this.AssignedRequest.Response?.Dispose();
this.AssignedRequest.Response = null;
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.Resend));
}
}
this.Removed();
}
protected void ProcessIncomingFrames(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
while (this.incomingFrames.Count > 0)
{
HTTP2FrameHeaderAndPayload frame = this.incomingFrames.Dequeue();
if ((this.isRSTFrameSent || this.AssignedRequest.IsCancellationRequested) && frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
{
BufferPool.Release(frame.Payload);
continue;
}
#if ENABLE_LOGGING
if (/*HTTPManager.Logger.Level == Logger.Loglevels.All && */frame.Type != HTTP2FrameTypes.DATA && frame.Type != HTTP2FrameTypes.WINDOW_UPDATE)
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Process - processing frame: {1}", this.Id, frame.ToString()), this.Context);
#endif
switch (frame.Type)
{
case HTTP2FrameTypes.HEADERS:
case HTTP2FrameTypes.CONTINUATION:
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open && this.State != HTTP2StreamStates.Idle)
{
// ERROR!
continue;
}
// payload will be released by the view
frame.DontUseMemPool = true;
if (this.headerView == null)
{
this.AssignedRequest.Timing.StartNext(TimingEventNames.Headers);
this.headerView = new FramesAsStreamView(new HeaderFrameView());
}
this.headerView.AddFrame(frame);
// END_STREAM may arrive sooner than an END_HEADERS, so we have to store that we already received it
if ((frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0)
this.isEndSTRReceived = true;
if ((frame.Flags & (byte)HTTP2HeadersFlags.END_HEADERS) != 0)
{
List<KeyValuePair<string, string>> headers = new List<KeyValuePair<string, string>>();
try
{
this.encoder.Decode(this, this.headerView, headers);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("HTTP2Stream", string.Format("[{0}] ProcessIncomingFrames - Header Frames: {1}, Encoder: {2}", this.Id, this.headerView.ToString(), this.encoder.ToString()), ex, this.Context);
}
this.headerView.Close();
this.headerView = null;
this.AssignedRequest.Timing.StartNext(TimingEventNames.Response_Received);
if (this.isRSTFrameSent)
{
this.State = HTTP2StreamStates.Closed;
break;
}
if (this.response == null)
this.AssignedRequest.Response = this.response = new HTTP2Response(this.AssignedRequest, false);
this.response.AddHeaders(headers);
if (this.isEndSTRReceived)
{
// If there's any trailing header, no data frame has an END_STREAM flag
this.response.FinishProcessData();
FinishRequest();
if (this.State == HTTP2StreamStates.HalfClosedLocal)
this.State = HTTP2StreamStates.Closed;
else
this.State = HTTP2StreamStates.HalfClosedRemote;
}
}
break;
case HTTP2FrameTypes.DATA:
ProcessIncomingDATAFrame(ref frame);
break;
case HTTP2FrameTypes.WINDOW_UPDATE:
HTTP2WindowUpdateFrame windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(frame);
#if ENABLE_LOGGING
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Received Window Update: {1:N0}, new remoteWindow: {2:N0}, initial remote window: {3:N0}, total data sent: {4:N0}", this.Id, windowUpdateFrame.WindowSizeIncrement, this.remoteWindow + windowUpdateFrame.WindowSizeIncrement, this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE], this.sentData), this.Context);
#endif
this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
break;
case HTTP2FrameTypes.RST_STREAM:
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
// It's possible to receive an RST_STREAM on a closed stream. In this case, we have to ignore it.
if (this.State == HTTP2StreamStates.Closed)
break;
var rstStreamFrame = HTTP2FrameHelper.ReadRST_StreamFrame(frame);
//HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] RST Stream frame ({1}) received in state {2}!", this.Id, rstStreamFrame, this.State), this.Context);
Abort(string.Format("RST_STREAM frame received! Error code: {0}({1})", rstStreamFrame.Error.ToString(), rstStreamFrame.ErrorCode));
break;
default:
HTTPManager.Logger.Warning("HTTP2Stream", string.Format("[{0}] Unexpected frame ({1}, Payload: {2}) in state {3}!", this.Id, frame, frame.PayloadAsHex(), this.State), this.Context);
break;
}
if (!frame.DontUseMemPool)
BufferPool.Release(frame.Payload);
}
}
void IDownloadContentBufferAvailable.BufferAvailable(DownloadContentStream stream)
{
// Signal the http2 thread, window update will be sent out in ProcessOpenState.
this.ParentHandler.SignalThread(SignalHandlerTypes.Signal);
}
protected virtual void ProcessIncomingDATAFrame(ref HTTP2FrameHeaderAndPayload frame)
{
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open)
{
// ERROR!
return;
}
HTTP2DataFrame dataFrame = HTTP2FrameHelper.ReadDataFrame(frame);
this.downloaded += (uint)dataFrame.Data.Count;
this.response.Prepare(this);
this.response.ProcessData(dataFrame.Data);
frame.DontUseMemPool = true;
// Because of padding, frame.Payload.Count can be larger than dataFrame.Data.Count!
// "The entire DATA frame payload is included in flow control, including the Pad Length and Padding fields if present."
this.localWindow -= frame.Payload.Count;
this.isEndSTRReceived = (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0;
if (this.isEndSTRReceived)
{
this.response.FinishProcessData();
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded), this.Context);
#endif
FinishRequest();
if (this.State == HTTP2StreamStates.HalfClosedLocal)
this.State = HTTP2StreamStates.Closed;
else
this.State = HTTP2StreamStates.HalfClosedRemote;
}
else if (this.AssignedRequest.DownloadSettings.OnDownloadProgress != null)
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest,
RequestEvents.DownloadProgress,
downloaded,
this.response.ExpectedContentLength));
}
protected void ProcessState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
switch (this.State)
{
case HTTP2StreamStates.Idle:
// hpack encode the request's headers
this.encoder.Encode(this, this.AssignedRequest, this.outgoing, this.Id);
// HTTP/2 uses DATA frames to carry message payloads.
// The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
if (this.AssignedRequest.UploadSettings.UploadStream == null)
{
this.State = HTTP2StreamStates.HalfClosedLocal;
//this.AssignedRequest.Timing.Finish(TimingEventNames.Request_Sent);
this.AssignedRequest.Timing.StartNext(TimingEventNames.Waiting_TTFB);
}
else
{
this.State = HTTP2StreamStates.Open;
this.lastReadCount = 1;
if (this.AssignedRequest.UploadSettings.UploadStream is UploadStreamBase upStream)
upStream.BeforeSendBody(this.AssignedRequest, this.ParentHandler);
if (this.AssignedRequest.UploadSettings != null && this.AssignedRequest.UploadSettings.UploadStream != null)
this.uploadLength = this.AssignedRequest.UploadSettings.UploadStream.Length;
}
// Change the initial window size to the request's DownloadSettings.ContentStreamMaxBuffered and send it to the server.
// After this initial setup the sending out window_update frames faces two problems:
// 1.) The local window should be bound to the Down-stream's MaxBuffered (ContentStreamMaxBuffered) and how its current length.
// 2.) Even while the its bound to the stream's current values, when the download finishes, we still have to update the global window.
// So, there's two options to follow:
// 1.) Update the local window based on the stream's usage
// 2.a) Send global window_update for every DATA frame processed/received
UInt32 initiatedInitialWindowSize = this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
this.localWindow = initiatedInitialWindowSize;
// Maximize max buffered to HTTP/2's limit
if (this.AssignedRequest.DownloadSettings.ContentStreamMaxBuffered > HTTP2ContentConsumer.MaxValueFor31Bits)
this.AssignedRequest.DownloadSettings.ContentStreamMaxBuffered = HTTP2ContentConsumer.MaxValueFor31Bits;
long localWindowDiff = this.AssignedRequest.DownloadSettings.ContentStreamMaxBuffered - this.localWindow;
if (localWindowDiff > 0)
{
this.localWindow += localWindowDiff;
this.outgoing.Enqueue(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, (UInt32)localWindowDiff, this.Context));
}
break;
case HTTP2StreamStates.Open:
ProcessOpenState(outgoingFrames);
//HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] New DATA frame created! remoteWindow: {1:N0}", this.Id, this.remoteWindow), this.Context);
break;
case HTTP2StreamStates.HalfClosedLocal:
if (this.response?.DownStream != null)
{
var windowIncrement = this.response.DownStream.MaxBuffered - this.localWindow - this.response.DownStream.Length;
if (windowIncrement > 0)
{
this.localWindow += windowIncrement;
outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, (UInt32)windowIncrement, this.Context));
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", $"[{this.Id}] Sending window inc. update: {windowIncrement:N0}, {this.localWindow:N0}/{this.response.DownStream.MaxBuffered:N0}", this.Context);
#endif
}
}
break;
case HTTP2StreamStates.HalfClosedRemote:
break;
case HTTP2StreamStates.Closed:
break;
}
}
protected virtual void ProcessOpenState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
// remote Window can be negative! See https://httpwg.org/specs/rfc7540.html#InitialWindowSize
if (this.remoteWindow <= 0)
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Skipping data sending as remote Window is {1}!", this.Id, this.remoteWindow), this.Context);
#endif
return;
}
// This step will send one frame per ProcessOpenState call.
Int64 maxFrameSize = Math.Min(this.AssignedRequest.UploadSettings.UploadChunkSize, Math.Min(this.remoteWindow, this.settings.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE]));
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.DATA;
frame.StreamId = this.Id;
frame.Payload = BufferPool.Get(maxFrameSize, true)
.AsBuffer((int)maxFrameSize);
// Expect a readCount of zero if it's end of the stream. But, to enable non-blocking scenario to wait for data, going to treat a negative value as no data.
this.lastReadCount = this.AssignedRequest.UploadSettings.UploadStream.Read(frame.Payload.Data, 0, (int)Math.Min(maxFrameSize, int.MaxValue));
if (this.lastReadCount <= 0)
{
BufferPool.Release(frame.Payload);
frame.Payload = BufferSegment.Empty;
if (this.lastReadCount < 0)
return;
}
else
frame.Payload = frame.Payload.Slice(0, this.lastReadCount);
frame.DontUseMemPool = false;
if (this.lastReadCount <= 0)
{
this.AssignedRequest.UploadSettings.Dispose();
frame.Flags = (byte)(HTTP2DataFlags.END_STREAM);
this.State = HTTP2StreamStates.HalfClosedLocal;
this.AssignedRequest.Timing.StartNext(TimingEventNames.Waiting_TTFB);
}
this.outgoing.Enqueue(frame);
this.remoteWindow -= frame.Payload.Count;
this.sentData += (uint)frame.Payload.Count;
if (this.AssignedRequest.UploadSettings.OnUploadProgress != null)
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.UploadProgress, this.sentData, uploadLength));
}
protected void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
{
switch (setting)
{
case HTTP2Settings.INITIAL_WINDOW_SIZE:
// https://httpwg.org/specs/rfc7540.html#InitialWindowSize
// "Prior to receiving a SETTINGS frame that sets a value for SETTINGS_INITIAL_WINDOW_SIZE,
// an endpoint can only use the default initial window size when sending flow-controlled frames."
// "In addition to changing the flow-control window for streams that are not yet active,
// a SETTINGS frame can alter the initial flow-control window size for streams with active flow-control windows
// (that is, streams in the "open" or "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE changes,
// a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value."
// So, if we created a stream before the remote peer's initial settings frame is received, we
// will adjust the window size. For example: initial window size by default is 65535, if we later
// receive a change to 1048576 (1 MB) we will increase the current remoteWindow by (1 048 576 - 65 535 =) 983 041
// But because initial window size in a setting frame can be smaller then the default 65535 bytes,
// the difference can be negative:
// "A change to SETTINGS_INITIAL_WINDOW_SIZE can cause the available space in a flow-control window to become negative.
// A sender MUST track the negative flow-control window and MUST NOT send new flow-controlled frames
// until it receives WINDOW_UPDATE frames that cause the flow-control window to become positive.
// For example, if the client sends 60 KB immediately on connection establishment
// and the server sets the initial window size to be 16 KB, the client will recalculate
// the available flow - control window to be - 44 KB on receipt of the SETTINGS frame.
// The client retains a negative flow-control window until WINDOW_UPDATE frames restore the
// window to being positive, after which the client can resume sending."
this.remoteWindow += newValue - oldValue;
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Remote Setting's Initial Window Updated from {1:N0} to {2:N0}, diff: {3:N0}, new remoteWindow: {4:N0}, total data sent: {5:N0}", this.Id, oldValue, newValue, newValue - oldValue, this.remoteWindow, this.sentData), this.Context);
#endif
break;
}
}
protected void FinishRequest()
{
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", $"FinishRequest({this.Id})", this.Context);
#endif
try
{
this.AssignedRequest.Timing.StartNext(TimingEventNames.Queued);
bool resendRequest;
HTTPConnectionStates proposedConnectionStates; // ignored
KeepAliveHeader keepAliveHeader = null; // ignored
ConnectionHelper.HandleResponse(this.AssignedRequest, out resendRequest, out proposedConnectionStates, ref keepAliveHeader, this.Context);
if (resendRequest && !this.AssignedRequest.IsCancellationRequested)
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.Resend));
else
{
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, HTTPRequestStates.Finished, null));
}
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("HTTP2Stream", "FinishRequest", ex, this.Context);
}
}
public void Removed()
{
this.AssignedRequest.UploadSettings.Dispose();
// After receiving a RST_STREAM on a stream, the receiver MUST NOT send additional frames for that stream, with the exception of PRIORITY.
this.outgoing.Clear();
// https://github.com/Benedicht/BestHTTP-Issues/issues/77
// Unsubscribe from OnSettingChangedEvent to remove reference to this instance.
this.settings.RemoteSettings.OnSettingChangedEvent -= OnRemoteSettingChanged;
this.headerView?.Close();
#if ENABLE_LOGGING
HTTPManager.Logger.Information("HTTP2Stream", "Stream removed: " + this.Id.ToString(), this.Context);
#endif
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2eadc0f2891358d4c9754796420a8e15
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,201 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using System;
using System.Collections.Generic;
using Best.HTTP.Shared.PlatformSupport.Text;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
sealed class HeaderTable
{
// https://http2.github.io/http2-spec/compression.html#static.table.definition
// Valid indexes starts with 1, so there's an empty entry.
static string[] StaticTableValues = new string[] { string.Empty, string.Empty, "GET", "POST", "/", "/index.html", "http", "https", "200", "204", "206", "304", "400", "404", "500", string.Empty, "gzip, deflate" };
// https://http2.github.io/http2-spec/compression.html#static.table.definition
// Valid indexes starts with 1, so there's an empty entry.
static string[] StaticTable = new string[62]
{
string.Empty,
":authority",
":method", // GET
":method", // POST
":path", // /
":path", // index.html
":scheme", // http
":scheme", // https
":status", // 200
":status", // 204
":status", // 206
":status", // 304
":status", // 400
":status", // 404
":status", // 500
"accept-charset",
"accept-encoding", // gzip, deflate
"accept-language",
"accept-ranges",
"accept",
"access-control-allow-origin",
"age",
"allow",
"authorization",
"cache-control",
"content-disposition",
"content-encoding",
"content-language",
"content-length",
"content-location",
"content-range",
"content-type",
"cookie",
"date",
"etag",
"expect",
"expires",
"from",
"host",
"if-match",
"if-modified-since",
"if-none-match",
"if-range",
"if-unmodified-since",
"last-modified",
"link",
"location",
"max-forwards",
"proxy-authenticate",
"proxy-authorization",
"range",
"referer",
"refresh",
"retry-after",
"server",
"set-cookie",
"strict-transport-security",
"transfer-encoding",
"user-agent",
"vary",
"via",
"www-authenticate",
};
public UInt32 DynamicTableSize { get; private set; }
public UInt32 MaxDynamicTableSize {
get { return this._maxDynamicTableSize; }
set
{
this._maxDynamicTableSize = value;
EvictEntries(0);
}
}
private UInt32 _maxDynamicTableSize;
private List<KeyValuePair<string, string>> DynamicTable = new List<KeyValuePair<string, string>>();
private HTTP2SettingsRegistry settingsRegistry;
public HeaderTable(HTTP2SettingsRegistry registry)
{
this.settingsRegistry = registry;
this.MaxDynamicTableSize = this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE];
}
public KeyValuePair<UInt32, UInt32> GetIndex(string key, string value)
{
for (int i = 0; i < DynamicTable.Count; ++i)
{
var kvp = DynamicTable[i];
// Exact match for both key and value
if (kvp.Key.Equals(key, StringComparison.OrdinalIgnoreCase) && kvp.Value.Equals(value, StringComparison.OrdinalIgnoreCase))
return new KeyValuePair<UInt32, UInt32>((UInt32)(StaticTable.Length + i), (UInt32)(StaticTable.Length + i));
}
KeyValuePair<UInt32, UInt32> bestMatch = new KeyValuePair<UInt32, UInt32>(0, 0);
for (int i = 0; i < StaticTable.Length; ++i)
{
if (StaticTable[i].Equals(key, StringComparison.OrdinalIgnoreCase))
{
if (i < StaticTableValues.Length && !string.IsNullOrEmpty(StaticTableValues[i]) && StaticTableValues[i].Equals(value, StringComparison.OrdinalIgnoreCase))
return new KeyValuePair<UInt32, UInt32>((UInt32)i, (UInt32)i);
else
bestMatch = new KeyValuePair<UInt32, UInt32>((UInt32)i, 0);
}
}
return bestMatch;
}
public string GetKey(UInt32 index)
{
if (index < StaticTable.Length)
return StaticTable[index];
return this.DynamicTable[(int)(index - StaticTable.Length)].Key;
}
public KeyValuePair<string, string> GetHeader(UInt32 index)
{
if (index < StaticTable.Length)
return new KeyValuePair<string, string>(StaticTable[index],
index < StaticTableValues.Length ? StaticTableValues[index] : null);
return this.DynamicTable[(int)(index - StaticTable.Length)];
}
public void Add(KeyValuePair<string, string> header)
{
// https://http2.github.io/http2-spec/compression.html#calculating.table.size
// The size of an entry is the sum of its name's length in octets (as defined in Section 5.2),
// its value's length in octets, and 32.
UInt32 newHeaderSize = CalculateEntrySize(header);
EvictEntries(newHeaderSize);
// If the size of the new entry is less than or equal to the maximum size, that entry is added to the table.
// It is not an error to attempt to add an entry that is larger than the maximum size;
// an attempt to add an entry larger than the maximum size causes the table to be
// emptied of all existing entries and results in an empty table.
if (this.DynamicTableSize + newHeaderSize <= this.MaxDynamicTableSize)
{
this.DynamicTable.Insert(0, header);
this.DynamicTableSize += (UInt32)newHeaderSize;
}
}
private UInt32 CalculateEntrySize(KeyValuePair<string, string> entry)
{
return 32 + (UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Key) +
(UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Value);
}
private void EvictEntries(uint newHeaderSize)
{
// https://http2.github.io/http2-spec/compression.html#entry.addition
// Before a new entry is added to the dynamic table, entries are evicted from the end of the dynamic
// table until the size of the dynamic table is less than or equal to (maximum size - new entry size) or until the table is empty.
while (this.DynamicTableSize + newHeaderSize > this.MaxDynamicTableSize && this.DynamicTable.Count > 0)
{
KeyValuePair<string, string> entry = this.DynamicTable[this.DynamicTable.Count - 1];
this.DynamicTable.RemoveAt(this.DynamicTable.Count - 1);
this.DynamicTableSize -= CalculateEntrySize(entry);
}
}
public override string ToString()
{
System.Text.StringBuilder sb = StringBuilderPool.Get(this.DynamicTable.Count + 3);
sb.Append("[HeaderTable ");
sb.AppendFormat("DynamicTable count: {0}, DynamicTableSize: {1}, MaxDynamicTableSize: {2}, ", this.DynamicTable.Count, this.DynamicTableSize, this.MaxDynamicTableSize);
foreach(var kvp in this.DynamicTable)
sb.AppendFormat("\"{0}\": \"{1}\", ", kvp.Key, kvp.Value);
sb.Append("]");
return StringBuilderPool.ReleaseAndGrab(sb);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a0cd78904e5b8e4095111b9a2d439e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,933 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
using System;
namespace Best.HTTP.Hosts.Connections.HTTP2
{
/// <summary>
/// A pre-generated table entry in a Huffman-Tree.
/// </summary>
public readonly struct HuffmanTableEntry
{
public readonly UInt32 Code;
public readonly byte Bits;
public HuffmanTableEntry(UInt32 code, byte bits)
{
this.Code = code;
this.Bits = bits;
}
/// <summary>
/// It must return 0 or 1 at bit index. Indexing will be relative to the Bits representing the current code. Idx grows from left to right. Idx must be between [1..Bits].
/// </summary>
public byte GetBitAtIdx(byte idx)
{
return (byte)((this.Code >> (this.Bits - idx)) & 1);
}
public override string ToString()
{
return string.Format("[TableEntry Code: 0x{0:X}, Bits: {1}]", this.Code, this.Bits);
}
}
public readonly struct HuffmanTreeNode
{
public readonly UInt16 Value;
public readonly UInt16 NextZeroIdx;
public readonly UInt16 NextOneIdx;
public HuffmanTreeNode(UInt16 value, UInt16 nextZeroIdx, UInt16 nextOneIdx)
{
this.Value = value;
this.NextZeroIdx = nextZeroIdx;
this.NextOneIdx = nextOneIdx;
}
public override string ToString()
{
return string.Format("[TreeNode Value: {0}, NextZeroIdx: {1}, NextOneIdx: {2}]",
this.Value, this.NextZeroIdx, this.NextOneIdx);
}
}
[Best.HTTP.Shared.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstruction]
static class HuffmanEncoder
{
public const UInt16 EOS = 256;
static readonly HuffmanTableEntry[] StaticTable = new HuffmanTableEntry[257]
{
new HuffmanTableEntry( 0x1ff8 , 13 ),
new HuffmanTableEntry( 0x7fffd8 , 23 ),
new HuffmanTableEntry( 0xfffffe2, 28 ),
new HuffmanTableEntry( 0xfffffe3, 28 ),
new HuffmanTableEntry( 0xfffffe4, 28 ),
new HuffmanTableEntry( 0xfffffe5, 28 ),
new HuffmanTableEntry( 0xfffffe6, 28 ),
new HuffmanTableEntry( 0xfffffe7, 28 ),
new HuffmanTableEntry( 0xfffffe8, 28 ),
new HuffmanTableEntry( 0xffffea, 24 ),
new HuffmanTableEntry( 0x3ffffffc , 30 ),
new HuffmanTableEntry( 0xfffffe9 , 28 ),
new HuffmanTableEntry( 0xfffffea , 28 ),
new HuffmanTableEntry( 0x3ffffffd , 30 ),
new HuffmanTableEntry( 0xfffffeb , 28 ),
new HuffmanTableEntry( 0xfffffec , 28 ),
new HuffmanTableEntry( 0xfffffed , 28 ),
new HuffmanTableEntry( 0xfffffee , 28 ),
new HuffmanTableEntry( 0xfffffef , 28 ),
new HuffmanTableEntry( 0xffffff0 , 28 ),
new HuffmanTableEntry( 0xffffff1 , 28 ),
new HuffmanTableEntry( 0xffffff2 , 28 ),
new HuffmanTableEntry( 0x3ffffffe, 30 ),
new HuffmanTableEntry( 0xffffff3 , 28 ),
new HuffmanTableEntry( 0xffffff4 , 28 ),
new HuffmanTableEntry( 0xffffff5 , 28 ),
new HuffmanTableEntry( 0xffffff6 , 28 ),
new HuffmanTableEntry( 0xffffff7 , 28 ),
new HuffmanTableEntry( 0xffffff8 , 28 ),
new HuffmanTableEntry( 0xffffff9 , 28 ),
new HuffmanTableEntry( 0xffffffa , 28 ),
new HuffmanTableEntry( 0xffffffb , 28 ),
new HuffmanTableEntry( 0x14 , 6 ),
new HuffmanTableEntry( 0x3f8, 10 ),
new HuffmanTableEntry( 0x3f9, 10 ),
new HuffmanTableEntry( 0xffa, 12 ),
new HuffmanTableEntry( 0x1ff9 , 13 ),
new HuffmanTableEntry( 0x15 , 6 ),
new HuffmanTableEntry( 0xf8 , 8 ),
new HuffmanTableEntry( 0x7fa, 11 ),
new HuffmanTableEntry( 0x3fa, 10 ),
new HuffmanTableEntry( 0x3fb, 10 ),
new HuffmanTableEntry( 0xf9 , 8 ),
new HuffmanTableEntry( 0x7fb, 11 ),
new HuffmanTableEntry( 0xfa , 8 ),
new HuffmanTableEntry( 0x16 , 6 ),
new HuffmanTableEntry( 0x17 , 6 ),
new HuffmanTableEntry( 0x18 , 6 ),
new HuffmanTableEntry( 0x0 , 5 ),
new HuffmanTableEntry( 0x1 , 5 ),
new HuffmanTableEntry( 0x2 , 5 ),
new HuffmanTableEntry( 0x19 , 6 ),
new HuffmanTableEntry( 0x1a , 6 ),
new HuffmanTableEntry( 0x1b , 6 ),
new HuffmanTableEntry( 0x1c , 6 ),
new HuffmanTableEntry( 0x1d , 6 ),
new HuffmanTableEntry( 0x1e , 6 ),
new HuffmanTableEntry( 0x1f , 6 ),
new HuffmanTableEntry( 0x5c , 7 ),
new HuffmanTableEntry( 0xfb , 8 ),
new HuffmanTableEntry( 0x7ffc , 15 ),
new HuffmanTableEntry( 0x20 , 6 ),
new HuffmanTableEntry( 0xffb, 12 ),
new HuffmanTableEntry( 0x3fc, 10 ),
new HuffmanTableEntry( 0x1ffa , 13 ),
new HuffmanTableEntry( 0x21, 6 ),
new HuffmanTableEntry( 0x5d, 7 ),
new HuffmanTableEntry( 0x5e, 7 ),
new HuffmanTableEntry( 0x5f, 7 ),
new HuffmanTableEntry( 0x60, 7 ),
new HuffmanTableEntry( 0x61, 7 ),
new HuffmanTableEntry( 0x62, 7 ),
new HuffmanTableEntry( 0x63, 7 ),
new HuffmanTableEntry( 0x64, 7 ),
new HuffmanTableEntry( 0x65, 7 ),
new HuffmanTableEntry( 0x66, 7 ),
new HuffmanTableEntry( 0x67, 7 ),
new HuffmanTableEntry( 0x68, 7 ),
new HuffmanTableEntry( 0x69, 7 ),
new HuffmanTableEntry( 0x6a, 7 ),
new HuffmanTableEntry( 0x6b, 7 ),
new HuffmanTableEntry( 0x6c, 7 ),
new HuffmanTableEntry( 0x6d, 7 ),
new HuffmanTableEntry( 0x6e, 7 ),
new HuffmanTableEntry( 0x6f, 7 ),
new HuffmanTableEntry( 0x70, 7 ),
new HuffmanTableEntry( 0x71, 7 ),
new HuffmanTableEntry( 0x72, 7 ),
new HuffmanTableEntry( 0xfc, 8 ),
new HuffmanTableEntry( 0x73, 7 ),
new HuffmanTableEntry( 0xfd, 8 ),
new HuffmanTableEntry( 0x1ffb, 13 ),
new HuffmanTableEntry( 0x7fff0, 19 ),
new HuffmanTableEntry( 0x1ffc, 13 ),
new HuffmanTableEntry( 0x3ffc, 14 ),
new HuffmanTableEntry( 0x22, 6 ),
new HuffmanTableEntry( 0x7ffd, 15 ),
new HuffmanTableEntry( 0x3, 5 ),
new HuffmanTableEntry( 0x23, 6 ),
new HuffmanTableEntry( 0x4, 5 ),
new HuffmanTableEntry( 0x24, 6 ),
new HuffmanTableEntry( 0x5, 5 ),
new HuffmanTableEntry( 0x25, 6 ),
new HuffmanTableEntry( 0x26, 6 ),
new HuffmanTableEntry( 0x27, 6 ),
new HuffmanTableEntry( 0x6 , 5 ),
new HuffmanTableEntry( 0x74, 7 ),
new HuffmanTableEntry( 0x75, 7 ),
new HuffmanTableEntry( 0x28, 6 ),
new HuffmanTableEntry( 0x29, 6 ),
new HuffmanTableEntry( 0x2a, 6 ),
new HuffmanTableEntry( 0x7 , 5 ),
new HuffmanTableEntry( 0x2b, 6 ),
new HuffmanTableEntry( 0x76, 7 ),
new HuffmanTableEntry( 0x2c, 6 ),
new HuffmanTableEntry( 0x8 , 5 ),
new HuffmanTableEntry( 0x9 , 5 ),
new HuffmanTableEntry( 0x2d, 6 ),
new HuffmanTableEntry( 0x77, 7 ),
new HuffmanTableEntry( 0x78, 7 ),
new HuffmanTableEntry( 0x79, 7 ),
new HuffmanTableEntry( 0x7a, 7 ),
new HuffmanTableEntry( 0x7b, 7 ),
new HuffmanTableEntry( 0x7ffe, 15 ),
new HuffmanTableEntry( 0x7fc, 11 ),
new HuffmanTableEntry( 0x3ffd, 14 ),
new HuffmanTableEntry( 0x1ffd, 13 ),
new HuffmanTableEntry( 0xffffffc, 28 ),
new HuffmanTableEntry( 0xfffe6 , 20 ),
new HuffmanTableEntry( 0x3fffd2, 22 ),
new HuffmanTableEntry( 0xfffe7 , 20 ),
new HuffmanTableEntry( 0xfffe8 , 20 ),
new HuffmanTableEntry( 0x3fffd3, 22 ),
new HuffmanTableEntry( 0x3fffd4, 22 ),
new HuffmanTableEntry( 0x3fffd5, 22 ),
new HuffmanTableEntry( 0x7fffd9, 23 ),
new HuffmanTableEntry( 0x3fffd6, 22 ),
new HuffmanTableEntry( 0x7fffda, 23 ),
new HuffmanTableEntry( 0x7fffdb, 23 ),
new HuffmanTableEntry( 0x7fffdc, 23 ),
new HuffmanTableEntry( 0x7fffdd, 23 ),
new HuffmanTableEntry( 0x7fffde, 23 ),
new HuffmanTableEntry( 0xffffeb, 24 ),
new HuffmanTableEntry( 0x7fffdf, 23 ),
new HuffmanTableEntry( 0xffffec, 24 ),
new HuffmanTableEntry( 0xffffed, 24 ),
new HuffmanTableEntry( 0x3fffd7, 22 ),
new HuffmanTableEntry( 0x7fffe0, 23 ),
new HuffmanTableEntry( 0xffffee, 24 ),
new HuffmanTableEntry( 0x7fffe1, 23 ),
new HuffmanTableEntry( 0x7fffe2, 23 ),
new HuffmanTableEntry( 0x7fffe3, 23 ),
new HuffmanTableEntry( 0x7fffe4, 23 ),
new HuffmanTableEntry( 0x1fffdc, 21 ),
new HuffmanTableEntry( 0x3fffd8, 22 ),
new HuffmanTableEntry( 0x7fffe5, 23 ),
new HuffmanTableEntry( 0x3fffd9, 22 ),
new HuffmanTableEntry( 0x7fffe6, 23 ),
new HuffmanTableEntry( 0x7fffe7, 23 ),
new HuffmanTableEntry( 0xffffef, 24 ),
new HuffmanTableEntry( 0x3fffda, 22 ),
new HuffmanTableEntry( 0x1fffdd, 21 ),
new HuffmanTableEntry( 0xfffe9 , 20 ),
new HuffmanTableEntry( 0x3fffdb, 22 ),
new HuffmanTableEntry( 0x3fffdc, 22 ),
new HuffmanTableEntry( 0x7fffe8, 23 ),
new HuffmanTableEntry( 0x7fffe9, 23 ),
new HuffmanTableEntry( 0x1fffde, 21 ),
new HuffmanTableEntry( 0x7fffea, 23 ),
new HuffmanTableEntry( 0x3fffdd, 22 ),
new HuffmanTableEntry( 0x3fffde, 22 ),
new HuffmanTableEntry( 0xfffff0, 24 ),
new HuffmanTableEntry( 0x1fffdf, 21 ),
new HuffmanTableEntry( 0x3fffdf, 22 ),
new HuffmanTableEntry( 0x7fffeb, 23 ),
new HuffmanTableEntry( 0x7fffec, 23 ),
new HuffmanTableEntry( 0x1fffe0, 21 ),
new HuffmanTableEntry( 0x1fffe1, 21 ),
new HuffmanTableEntry( 0x3fffe0, 22 ),
new HuffmanTableEntry( 0x1fffe2, 21 ),
new HuffmanTableEntry( 0x7fffed, 23 ),
new HuffmanTableEntry( 0x3fffe1, 22 ),
new HuffmanTableEntry( 0x7fffee, 23 ),
new HuffmanTableEntry( 0x7fffef, 23 ),
new HuffmanTableEntry( 0xfffea , 20 ),
new HuffmanTableEntry( 0x3fffe2, 22 ),
new HuffmanTableEntry( 0x3fffe3, 22 ),
new HuffmanTableEntry( 0x3fffe4, 22 ),
new HuffmanTableEntry( 0x7ffff0, 23 ),
new HuffmanTableEntry( 0x3fffe5, 22 ),
new HuffmanTableEntry( 0x3fffe6, 22 ),
new HuffmanTableEntry( 0x7ffff1, 23 ),
new HuffmanTableEntry( 0x3ffffe0, 26 ),
new HuffmanTableEntry( 0x3ffffe1, 26 ),
new HuffmanTableEntry( 0xfffeb , 20 ),
new HuffmanTableEntry( 0x7fff1 , 19 ),
new HuffmanTableEntry( 0x3fffe7, 22 ),
new HuffmanTableEntry( 0x7ffff2, 23 ),
new HuffmanTableEntry( 0x3fffe8, 22 ),
new HuffmanTableEntry( 0x1ffffec, 25 ),
new HuffmanTableEntry( 0x3ffffe2, 26 ),
new HuffmanTableEntry( 0x3ffffe3, 26 ),
new HuffmanTableEntry( 0x3ffffe4, 26 ),
new HuffmanTableEntry( 0x7ffffde, 27 ),
new HuffmanTableEntry( 0x7ffffdf, 27 ),
new HuffmanTableEntry( 0x3ffffe5, 26 ),
new HuffmanTableEntry( 0xfffff1 , 24 ),
new HuffmanTableEntry( 0x1ffffed, 25 ),
new HuffmanTableEntry( 0x7fff2 , 19 ),
new HuffmanTableEntry( 0x1fffe3 , 21 ),
new HuffmanTableEntry( 0x3ffffe6, 26 ),
new HuffmanTableEntry( 0x7ffffe0, 27 ),
new HuffmanTableEntry( 0x7ffffe1, 27 ),
new HuffmanTableEntry( 0x3ffffe7, 26 ),
new HuffmanTableEntry( 0x7ffffe2, 27 ),
new HuffmanTableEntry( 0xfffff2 , 24 ),
new HuffmanTableEntry( 0x1fffe4 , 21 ),
new HuffmanTableEntry( 0x1fffe5 , 21 ),
new HuffmanTableEntry( 0x3ffffe8, 26 ),
new HuffmanTableEntry( 0x3ffffe9, 26 ),
new HuffmanTableEntry( 0xffffffd, 28 ),
new HuffmanTableEntry( 0x7ffffe3, 27 ),
new HuffmanTableEntry( 0x7ffffe4, 27 ),
new HuffmanTableEntry( 0x7ffffe5, 27 ),
new HuffmanTableEntry( 0xfffec , 20 ),
new HuffmanTableEntry( 0xfffff3, 24 ),
new HuffmanTableEntry( 0xfffed , 20 ),
new HuffmanTableEntry( 0x1fffe6, 21 ),
new HuffmanTableEntry( 0x3fffe9, 22 ),
new HuffmanTableEntry( 0x1fffe7, 21 ),
new HuffmanTableEntry( 0x1fffe8, 21 ),
new HuffmanTableEntry( 0x7ffff3, 23 ),
new HuffmanTableEntry( 0x3fffea, 22 ),
new HuffmanTableEntry( 0x3fffeb, 22 ),
new HuffmanTableEntry( 0x1ffffee, 25 ),
new HuffmanTableEntry( 0x1ffffef, 25 ),
new HuffmanTableEntry( 0xfffff4 , 24 ),
new HuffmanTableEntry( 0xfffff5 , 24 ),
new HuffmanTableEntry( 0x3ffffea, 26 ),
new HuffmanTableEntry( 0x7ffff4 , 23 ),
new HuffmanTableEntry( 0x3ffffeb, 26 ),
new HuffmanTableEntry( 0x7ffffe6, 27 ),
new HuffmanTableEntry( 0x3ffffec, 26 ),
new HuffmanTableEntry( 0x3ffffed, 26 ),
new HuffmanTableEntry( 0x7ffffe7, 27 ),
new HuffmanTableEntry( 0x7ffffe8, 27 ),
new HuffmanTableEntry( 0x7ffffe9, 27 ),
new HuffmanTableEntry( 0x7ffffea, 27 ),
new HuffmanTableEntry( 0x7ffffeb, 27 ),
new HuffmanTableEntry( 0xffffffe, 28 ),
new HuffmanTableEntry( 0x7ffffec, 27 ),
new HuffmanTableEntry( 0x7ffffed, 27 ),
new HuffmanTableEntry( 0x7ffffee, 27 ),
new HuffmanTableEntry( 0x7ffffef, 27 ),
new HuffmanTableEntry( 0x7fffff0, 27 ),
new HuffmanTableEntry( 0x3ffffee, 26 ),
new HuffmanTableEntry( 0x3fffffff, 30 )
};
//static List<TreeNode> entries = new List<TreeNode>();
static readonly HuffmanTreeNode[] HuffmanTree = new HuffmanTreeNode[]
{
new HuffmanTreeNode ( 0, 98, 1 ),
new HuffmanTreeNode ( 0, 151, 2 ),
new HuffmanTreeNode ( 0, 173, 3 ),
new HuffmanTreeNode ( 0, 204, 4 ),
new HuffmanTreeNode ( 0, 263, 5 ),
new HuffmanTreeNode ( 0, 113, 6 ),
new HuffmanTreeNode ( 0, 211, 7 ),
new HuffmanTreeNode ( 0, 104, 8 ),
new HuffmanTreeNode ( 0, 116, 9 ),
new HuffmanTreeNode ( 0, 108, 10 ),
new HuffmanTreeNode ( 0, 11, 14 ),
new HuffmanTreeNode ( 0, 12, 166 ),
new HuffmanTreeNode ( 0, 13, 111 ),
new HuffmanTreeNode ( 0, 0, 0 ),
new HuffmanTreeNode ( 0, 220, 15 ),
new HuffmanTreeNode ( 0, 222, 16 ),
new HuffmanTreeNode ( 0, 158, 17 ),
new HuffmanTreeNode ( 0, 270, 18 ),
new HuffmanTreeNode ( 0, 216, 19 ),
new HuffmanTreeNode ( 0, 279, 20 ),
new HuffmanTreeNode ( 0, 21, 27 ),
new HuffmanTreeNode ( 0, 377, 22 ),
new HuffmanTreeNode ( 0, 414, 23 ),
new HuffmanTreeNode ( 0, 24, 301 ),
new HuffmanTreeNode ( 0, 25, 298 ),
new HuffmanTreeNode ( 0, 26, 295 ),
new HuffmanTreeNode ( 1, 0, 0 ),
new HuffmanTreeNode ( 0, 314, 28 ),
new HuffmanTreeNode ( 0, 50, 29 ),
new HuffmanTreeNode ( 0, 362, 30 ),
new HuffmanTreeNode ( 0, 403, 31 ),
new HuffmanTreeNode ( 0, 440, 32 ),
new HuffmanTreeNode ( 0, 33, 55 ),
new HuffmanTreeNode ( 0, 34, 46 ),
new HuffmanTreeNode ( 0, 35, 39 ),
new HuffmanTreeNode ( 0, 510, 36 ),
new HuffmanTreeNode ( 0, 37, 38 ),
new HuffmanTreeNode ( 2, 0, 0 ),
new HuffmanTreeNode ( 3, 0, 0 ),
new HuffmanTreeNode ( 0, 40, 43 ),
new HuffmanTreeNode ( 0, 41, 42 ),
new HuffmanTreeNode ( 4, 0, 0 ),
new HuffmanTreeNode ( 5, 0, 0 ),
new HuffmanTreeNode ( 0, 44, 45 ),
new HuffmanTreeNode ( 6, 0, 0 ),
new HuffmanTreeNode ( 7, 0, 0 ),
new HuffmanTreeNode ( 0, 47, 67 ),
new HuffmanTreeNode ( 0, 48, 63 ),
new HuffmanTreeNode ( 0, 49, 62 ),
new HuffmanTreeNode ( 8, 0, 0 ),
new HuffmanTreeNode ( 0, 396, 51 ),
new HuffmanTreeNode ( 0, 52, 309 ),
new HuffmanTreeNode ( 0, 486, 53 ),
new HuffmanTreeNode ( 0, 54, 307 ),
new HuffmanTreeNode ( 9, 0, 0 ),
new HuffmanTreeNode ( 0, 74, 56 ),
new HuffmanTreeNode ( 0, 91, 57 ),
new HuffmanTreeNode ( 0, 274, 58 ),
new HuffmanTreeNode ( 0, 502, 59 ),
new HuffmanTreeNode ( 0, 60, 81 ),
new HuffmanTreeNode ( 0, 61, 65 ),
new HuffmanTreeNode ( 10, 0, 0 ),
new HuffmanTreeNode ( 11, 0, 0 ),
new HuffmanTreeNode ( 0, 64, 66 ),
new HuffmanTreeNode ( 12, 0, 0 ),
new HuffmanTreeNode ( 13, 0, 0 ),
new HuffmanTreeNode ( 14, 0, 0 ),
new HuffmanTreeNode ( 0, 68, 71 ),
new HuffmanTreeNode ( 0, 69, 70 ),
new HuffmanTreeNode ( 15, 0, 0 ),
new HuffmanTreeNode ( 16, 0, 0 ),
new HuffmanTreeNode ( 0, 72, 73 ),
new HuffmanTreeNode ( 17, 0, 0 ),
new HuffmanTreeNode ( 18, 0, 0 ),
new HuffmanTreeNode ( 0, 75, 84 ),
new HuffmanTreeNode ( 0, 76, 79 ),
new HuffmanTreeNode ( 0, 77, 78 ),
new HuffmanTreeNode ( 19, 0, 0 ),
new HuffmanTreeNode ( 20, 0, 0 ),
new HuffmanTreeNode ( 0, 80, 83 ),
new HuffmanTreeNode ( 21, 0, 0 ),
new HuffmanTreeNode ( 0, 82, 512 ),
new HuffmanTreeNode ( 22, 0, 0 ),
new HuffmanTreeNode ( 23, 0, 0 ),
new HuffmanTreeNode ( 0, 85, 88 ),
new HuffmanTreeNode ( 0, 86, 87 ),
new HuffmanTreeNode ( 24, 0, 0 ),
new HuffmanTreeNode ( 25, 0, 0 ),
new HuffmanTreeNode ( 0, 89, 90 ),
new HuffmanTreeNode ( 26, 0, 0 ),
new HuffmanTreeNode ( 27, 0, 0 ),
new HuffmanTreeNode ( 0, 92, 95 ),
new HuffmanTreeNode ( 0, 93, 94 ),
new HuffmanTreeNode ( 28, 0, 0 ),
new HuffmanTreeNode ( 29, 0, 0 ),
new HuffmanTreeNode ( 0, 96, 97 ),
new HuffmanTreeNode ( 30, 0, 0 ),
new HuffmanTreeNode ( 31, 0, 0 ),
new HuffmanTreeNode ( 0, 133, 99 ),
new HuffmanTreeNode ( 0, 100, 129 ),
new HuffmanTreeNode ( 0, 258, 101 ),
new HuffmanTreeNode ( 0, 102, 126 ),
new HuffmanTreeNode ( 0, 103, 112 ),
new HuffmanTreeNode ( 32, 0, 0 ),
new HuffmanTreeNode ( 0, 105, 119 ),
new HuffmanTreeNode ( 0, 106, 107 ),
new HuffmanTreeNode ( 33, 0, 0 ),
new HuffmanTreeNode ( 34, 0, 0 ),
new HuffmanTreeNode ( 0, 271, 109 ),
new HuffmanTreeNode ( 0, 110, 164 ),
new HuffmanTreeNode ( 35, 0, 0 ),
new HuffmanTreeNode ( 36, 0, 0 ),
new HuffmanTreeNode ( 37, 0, 0 ),
new HuffmanTreeNode ( 0, 114, 124 ),
new HuffmanTreeNode ( 0, 115, 122 ),
new HuffmanTreeNode ( 38, 0, 0 ),
new HuffmanTreeNode ( 0, 165, 117 ),
new HuffmanTreeNode ( 0, 118, 123 ),
new HuffmanTreeNode ( 39, 0, 0 ),
new HuffmanTreeNode ( 0, 120, 121 ),
new HuffmanTreeNode ( 40, 0, 0 ),
new HuffmanTreeNode ( 41, 0, 0 ),
new HuffmanTreeNode ( 42, 0, 0 ),
new HuffmanTreeNode ( 43, 0, 0 ),
new HuffmanTreeNode ( 0, 125, 157 ),
new HuffmanTreeNode ( 44, 0, 0 ),
new HuffmanTreeNode ( 0, 127, 128 ),
new HuffmanTreeNode ( 45, 0, 0 ),
new HuffmanTreeNode ( 46, 0, 0 ),
new HuffmanTreeNode ( 0, 130, 144 ),
new HuffmanTreeNode ( 0, 131, 141 ),
new HuffmanTreeNode ( 0, 132, 140 ),
new HuffmanTreeNode ( 47, 0, 0 ),
new HuffmanTreeNode ( 0, 134, 229 ),
new HuffmanTreeNode ( 0, 135, 138 ),
new HuffmanTreeNode ( 0, 136, 137 ),
new HuffmanTreeNode ( 48, 0, 0 ),
new HuffmanTreeNode ( 49, 0, 0 ),
new HuffmanTreeNode ( 0, 139, 227 ),
new HuffmanTreeNode ( 50, 0, 0 ),
new HuffmanTreeNode ( 51, 0, 0 ),
new HuffmanTreeNode ( 0, 142, 143 ),
new HuffmanTreeNode ( 52, 0, 0 ),
new HuffmanTreeNode ( 53, 0, 0 ),
new HuffmanTreeNode ( 0, 145, 148 ),
new HuffmanTreeNode ( 0, 146, 147 ),
new HuffmanTreeNode ( 54, 0, 0 ),
new HuffmanTreeNode ( 55, 0, 0 ),
new HuffmanTreeNode ( 0, 149, 150 ),
new HuffmanTreeNode ( 56, 0, 0 ),
new HuffmanTreeNode ( 57, 0, 0 ),
new HuffmanTreeNode ( 0, 160, 152 ),
new HuffmanTreeNode ( 0, 246, 153 ),
new HuffmanTreeNode ( 0, 256, 154 ),
new HuffmanTreeNode ( 0, 155, 170 ),
new HuffmanTreeNode ( 0, 156, 169 ),
new HuffmanTreeNode ( 58, 0, 0 ),
new HuffmanTreeNode ( 59, 0, 0 ),
new HuffmanTreeNode ( 0, 159, 226 ),
new HuffmanTreeNode ( 60, 0, 0 ),
new HuffmanTreeNode ( 0, 161, 232 ),
new HuffmanTreeNode ( 0, 162, 224 ),
new HuffmanTreeNode ( 0, 163, 168 ),
new HuffmanTreeNode ( 61, 0, 0 ),
new HuffmanTreeNode ( 62, 0, 0 ),
new HuffmanTreeNode ( 63, 0, 0 ),
new HuffmanTreeNode ( 0, 167, 215 ),
new HuffmanTreeNode ( 64, 0, 0 ),
new HuffmanTreeNode ( 65, 0, 0 ),
new HuffmanTreeNode ( 66, 0, 0 ),
new HuffmanTreeNode ( 0, 171, 172 ),
new HuffmanTreeNode ( 67, 0, 0 ),
new HuffmanTreeNode ( 68, 0, 0 ),
new HuffmanTreeNode ( 0, 174, 189 ),
new HuffmanTreeNode ( 0, 175, 182 ),
new HuffmanTreeNode ( 0, 176, 179 ),
new HuffmanTreeNode ( 0, 177, 178 ),
new HuffmanTreeNode ( 69, 0, 0 ),
new HuffmanTreeNode ( 70, 0, 0 ),
new HuffmanTreeNode ( 0, 180, 181 ),
new HuffmanTreeNode ( 71, 0, 0 ),
new HuffmanTreeNode ( 72, 0, 0 ),
new HuffmanTreeNode ( 0, 183, 186 ),
new HuffmanTreeNode ( 0, 184, 185 ),
new HuffmanTreeNode ( 73, 0, 0 ),
new HuffmanTreeNode ( 74, 0, 0 ),
new HuffmanTreeNode ( 0, 187, 188 ),
new HuffmanTreeNode ( 75, 0, 0 ),
new HuffmanTreeNode ( 76, 0, 0 ),
new HuffmanTreeNode ( 0, 190, 197 ),
new HuffmanTreeNode ( 0, 191, 194 ),
new HuffmanTreeNode ( 0, 192, 193 ),
new HuffmanTreeNode ( 77, 0, 0 ),
new HuffmanTreeNode ( 78, 0, 0 ),
new HuffmanTreeNode ( 0, 195, 196 ),
new HuffmanTreeNode ( 79, 0, 0 ),
new HuffmanTreeNode ( 80, 0, 0 ),
new HuffmanTreeNode ( 0, 198, 201 ),
new HuffmanTreeNode ( 0, 199, 200 ),
new HuffmanTreeNode ( 81, 0, 0 ),
new HuffmanTreeNode ( 82, 0, 0 ),
new HuffmanTreeNode ( 0, 202, 203 ),
new HuffmanTreeNode ( 83, 0, 0 ),
new HuffmanTreeNode ( 84, 0, 0 ),
new HuffmanTreeNode ( 0, 205, 242 ),
new HuffmanTreeNode ( 0, 206, 209 ),
new HuffmanTreeNode ( 0, 207, 208 ),
new HuffmanTreeNode ( 85, 0, 0 ),
new HuffmanTreeNode ( 86, 0, 0 ),
new HuffmanTreeNode ( 0, 210, 213 ),
new HuffmanTreeNode ( 87, 0, 0 ),
new HuffmanTreeNode ( 0, 212, 214 ),
new HuffmanTreeNode ( 88, 0, 0 ),
new HuffmanTreeNode ( 89, 0, 0 ),
new HuffmanTreeNode ( 90, 0, 0 ),
new HuffmanTreeNode ( 91, 0, 0 ),
new HuffmanTreeNode ( 0, 217, 286 ),
new HuffmanTreeNode ( 0, 218, 276 ),
new HuffmanTreeNode ( 0, 219, 410 ),
new HuffmanTreeNode ( 92, 0, 0 ),
new HuffmanTreeNode ( 0, 221, 273 ),
new HuffmanTreeNode ( 93, 0, 0 ),
new HuffmanTreeNode ( 0, 223, 272 ),
new HuffmanTreeNode ( 94, 0, 0 ),
new HuffmanTreeNode ( 0, 225, 228 ),
new HuffmanTreeNode ( 95, 0, 0 ),
new HuffmanTreeNode ( 96, 0, 0 ),
new HuffmanTreeNode ( 97, 0, 0 ),
new HuffmanTreeNode ( 98, 0, 0 ),
new HuffmanTreeNode ( 0, 230, 240 ),
new HuffmanTreeNode ( 0, 231, 235 ),
new HuffmanTreeNode ( 99, 0, 0 ),
new HuffmanTreeNode ( 0, 233, 237 ),
new HuffmanTreeNode ( 0, 234, 236 ),
new HuffmanTreeNode ( 100, 0, 0 ),
new HuffmanTreeNode ( 101, 0, 0 ),
new HuffmanTreeNode ( 102, 0, 0 ),
new HuffmanTreeNode ( 0, 238, 239 ),
new HuffmanTreeNode ( 103, 0, 0 ),
new HuffmanTreeNode ( 104, 0, 0 ),
new HuffmanTreeNode ( 0, 241, 252 ),
new HuffmanTreeNode ( 105, 0, 0 ),
new HuffmanTreeNode ( 0, 243, 254 ),
new HuffmanTreeNode ( 0, 244, 245 ),
new HuffmanTreeNode ( 106, 0, 0 ),
new HuffmanTreeNode ( 107, 0, 0 ),
new HuffmanTreeNode ( 0, 247, 250 ),
new HuffmanTreeNode ( 0, 248, 249 ),
new HuffmanTreeNode ( 108, 0, 0 ),
new HuffmanTreeNode ( 109, 0, 0 ),
new HuffmanTreeNode ( 0, 251, 253 ),
new HuffmanTreeNode ( 110, 0, 0 ),
new HuffmanTreeNode ( 111, 0, 0 ),
new HuffmanTreeNode ( 112, 0, 0 ),
new HuffmanTreeNode ( 0, 255, 262 ),
new HuffmanTreeNode ( 113, 0, 0 ),
new HuffmanTreeNode ( 0, 257, 261 ),
new HuffmanTreeNode ( 114, 0, 0 ),
new HuffmanTreeNode ( 0, 259, 260 ),
new HuffmanTreeNode ( 115, 0, 0 ),
new HuffmanTreeNode ( 116, 0, 0 ),
new HuffmanTreeNode ( 117, 0, 0 ),
new HuffmanTreeNode ( 118, 0, 0 ),
new HuffmanTreeNode ( 0, 264, 267 ),
new HuffmanTreeNode ( 0, 265, 266 ),
new HuffmanTreeNode ( 119, 0, 0 ),
new HuffmanTreeNode ( 120, 0, 0 ),
new HuffmanTreeNode ( 0, 268, 269 ),
new HuffmanTreeNode ( 121, 0, 0 ),
new HuffmanTreeNode ( 122, 0, 0 ),
new HuffmanTreeNode ( 123, 0, 0 ),
new HuffmanTreeNode ( 124, 0, 0 ),
new HuffmanTreeNode ( 125, 0, 0 ),
new HuffmanTreeNode ( 126, 0, 0 ),
new HuffmanTreeNode ( 0, 275, 459 ),
new HuffmanTreeNode ( 127, 0, 0 ),
new HuffmanTreeNode ( 0, 436, 277 ),
new HuffmanTreeNode ( 0, 278, 285 ),
new HuffmanTreeNode ( 128, 0, 0 ),
new HuffmanTreeNode ( 0, 372, 280 ),
new HuffmanTreeNode ( 0, 281, 332 ),
new HuffmanTreeNode ( 0, 282, 291 ),
new HuffmanTreeNode ( 0, 473, 283 ),
new HuffmanTreeNode ( 0, 284, 290 ),
new HuffmanTreeNode ( 129, 0, 0 ),
new HuffmanTreeNode ( 130, 0, 0 ),
new HuffmanTreeNode ( 0, 287, 328 ),
new HuffmanTreeNode ( 0, 288, 388 ),
new HuffmanTreeNode ( 0, 289, 345 ),
new HuffmanTreeNode ( 131, 0, 0 ),
new HuffmanTreeNode ( 132, 0, 0 ),
new HuffmanTreeNode ( 0, 292, 296 ),
new HuffmanTreeNode ( 0, 293, 294 ),
new HuffmanTreeNode ( 133, 0, 0 ),
new HuffmanTreeNode ( 134, 0, 0 ),
new HuffmanTreeNode ( 135, 0, 0 ),
new HuffmanTreeNode ( 0, 297, 313 ),
new HuffmanTreeNode ( 136, 0, 0 ),
new HuffmanTreeNode ( 0, 299, 300 ),
new HuffmanTreeNode ( 137, 0, 0 ),
new HuffmanTreeNode ( 138, 0, 0 ),
new HuffmanTreeNode ( 0, 302, 305 ),
new HuffmanTreeNode ( 0, 303, 304 ),
new HuffmanTreeNode ( 139, 0, 0 ),
new HuffmanTreeNode ( 140, 0, 0 ),
new HuffmanTreeNode ( 0, 306, 308 ),
new HuffmanTreeNode ( 141, 0, 0 ),
new HuffmanTreeNode ( 142, 0, 0 ),
new HuffmanTreeNode ( 143, 0, 0 ),
new HuffmanTreeNode ( 0, 310, 319 ),
new HuffmanTreeNode ( 0, 311, 312 ),
new HuffmanTreeNode ( 144, 0, 0 ),
new HuffmanTreeNode ( 145, 0, 0 ),
new HuffmanTreeNode ( 146, 0, 0 ),
new HuffmanTreeNode ( 0, 315, 350 ),
new HuffmanTreeNode ( 0, 316, 325 ),
new HuffmanTreeNode ( 0, 317, 322 ),
new HuffmanTreeNode ( 0, 318, 321 ),
new HuffmanTreeNode ( 147, 0, 0 ),
new HuffmanTreeNode ( 0, 320, 341 ),
new HuffmanTreeNode ( 148, 0, 0 ),
new HuffmanTreeNode ( 149, 0, 0 ),
new HuffmanTreeNode ( 0, 323, 324 ),
new HuffmanTreeNode ( 150, 0, 0 ),
new HuffmanTreeNode ( 151, 0, 0 ),
new HuffmanTreeNode ( 0, 326, 338 ),
new HuffmanTreeNode ( 0, 327, 336 ),
new HuffmanTreeNode ( 152, 0, 0 ),
new HuffmanTreeNode ( 0, 465, 329 ),
new HuffmanTreeNode ( 0, 330, 355 ),
new HuffmanTreeNode ( 0, 331, 344 ),
new HuffmanTreeNode ( 153, 0, 0 ),
new HuffmanTreeNode ( 0, 333, 347 ),
new HuffmanTreeNode ( 0, 334, 342 ),
new HuffmanTreeNode ( 0, 335, 337 ),
new HuffmanTreeNode ( 154, 0, 0 ),
new HuffmanTreeNode ( 155, 0, 0 ),
new HuffmanTreeNode ( 156, 0, 0 ),
new HuffmanTreeNode ( 0, 339, 340 ),
new HuffmanTreeNode ( 157, 0, 0 ),
new HuffmanTreeNode ( 158, 0, 0 ),
new HuffmanTreeNode ( 159, 0, 0 ),
new HuffmanTreeNode ( 0, 343, 346 ),
new HuffmanTreeNode ( 160, 0, 0 ),
new HuffmanTreeNode ( 161, 0, 0 ),
new HuffmanTreeNode ( 162, 0, 0 ),
new HuffmanTreeNode ( 163, 0, 0 ),
new HuffmanTreeNode ( 0, 348, 360 ),
new HuffmanTreeNode ( 0, 349, 359 ),
new HuffmanTreeNode ( 164, 0, 0 ),
new HuffmanTreeNode ( 0, 351, 369 ),
new HuffmanTreeNode ( 0, 352, 357 ),
new HuffmanTreeNode ( 0, 353, 354 ),
new HuffmanTreeNode ( 165, 0, 0 ),
new HuffmanTreeNode ( 166, 0, 0 ),
new HuffmanTreeNode ( 0, 356, 366 ),
new HuffmanTreeNode ( 167, 0, 0 ),
new HuffmanTreeNode ( 0, 358, 368 ),
new HuffmanTreeNode ( 168, 0, 0 ),
new HuffmanTreeNode ( 169, 0, 0 ),
new HuffmanTreeNode ( 0, 361, 367 ),
new HuffmanTreeNode ( 170, 0, 0 ),
new HuffmanTreeNode ( 0, 363, 417 ),
new HuffmanTreeNode ( 0, 364, 449 ),
new HuffmanTreeNode ( 0, 365, 434 ),
new HuffmanTreeNode ( 171, 0, 0 ),
new HuffmanTreeNode ( 172, 0, 0 ),
new HuffmanTreeNode ( 173, 0, 0 ),
new HuffmanTreeNode ( 174, 0, 0 ),
new HuffmanTreeNode ( 0, 370, 385 ),
new HuffmanTreeNode ( 0, 371, 383 ),
new HuffmanTreeNode ( 175, 0, 0 ),
new HuffmanTreeNode ( 0, 373, 451 ),
new HuffmanTreeNode ( 0, 374, 381 ),
new HuffmanTreeNode ( 0, 375, 376 ),
new HuffmanTreeNode ( 176, 0, 0 ),
new HuffmanTreeNode ( 177, 0, 0 ),
new HuffmanTreeNode ( 0, 378, 393 ),
new HuffmanTreeNode ( 0, 379, 390 ),
new HuffmanTreeNode ( 0, 380, 384 ),
new HuffmanTreeNode ( 178, 0, 0 ),
new HuffmanTreeNode ( 0, 382, 437 ),
new HuffmanTreeNode ( 179, 0, 0 ),
new HuffmanTreeNode ( 180, 0, 0 ),
new HuffmanTreeNode ( 181, 0, 0 ),
new HuffmanTreeNode ( 0, 386, 387 ),
new HuffmanTreeNode ( 182, 0, 0 ),
new HuffmanTreeNode ( 183, 0, 0 ),
new HuffmanTreeNode ( 0, 389, 409 ),
new HuffmanTreeNode ( 184, 0, 0 ),
new HuffmanTreeNode ( 0, 391, 392 ),
new HuffmanTreeNode ( 185, 0, 0 ),
new HuffmanTreeNode ( 186, 0, 0 ),
new HuffmanTreeNode ( 0, 394, 400 ),
new HuffmanTreeNode ( 0, 395, 399 ),
new HuffmanTreeNode ( 187, 0, 0 ),
new HuffmanTreeNode ( 0, 397, 412 ),
new HuffmanTreeNode ( 0, 398, 402 ),
new HuffmanTreeNode ( 188, 0, 0 ),
new HuffmanTreeNode ( 189, 0, 0 ),
new HuffmanTreeNode ( 0, 401, 411 ),
new HuffmanTreeNode ( 190, 0, 0 ),
new HuffmanTreeNode ( 191, 0, 0 ),
new HuffmanTreeNode ( 0, 404, 427 ),
new HuffmanTreeNode ( 0, 405, 424 ),
new HuffmanTreeNode ( 0, 406, 421 ),
new HuffmanTreeNode ( 0, 407, 408 ),
new HuffmanTreeNode ( 192, 0, 0 ),
new HuffmanTreeNode ( 193, 0, 0 ),
new HuffmanTreeNode ( 194, 0, 0 ),
new HuffmanTreeNode ( 195, 0, 0 ),
new HuffmanTreeNode ( 196, 0, 0 ),
new HuffmanTreeNode ( 0, 413, 474 ),
new HuffmanTreeNode ( 197, 0, 0 ),
new HuffmanTreeNode ( 0, 415, 475 ),
new HuffmanTreeNode ( 0, 416, 471 ),
new HuffmanTreeNode ( 198, 0, 0 ),
new HuffmanTreeNode ( 0, 481, 418 ),
new HuffmanTreeNode ( 0, 419, 478 ),
new HuffmanTreeNode ( 0, 420, 435 ),
new HuffmanTreeNode ( 199, 0, 0 ),
new HuffmanTreeNode ( 0, 422, 423 ),
new HuffmanTreeNode ( 200, 0, 0 ),
new HuffmanTreeNode ( 201, 0, 0 ),
new HuffmanTreeNode ( 0, 425, 438 ),
new HuffmanTreeNode ( 0, 426, 433 ),
new HuffmanTreeNode ( 202, 0, 0 ),
new HuffmanTreeNode ( 0, 455, 428 ),
new HuffmanTreeNode ( 0, 490, 429 ),
new HuffmanTreeNode ( 0, 511, 430 ),
new HuffmanTreeNode ( 0, 431, 432 ),
new HuffmanTreeNode ( 203, 0, 0 ),
new HuffmanTreeNode ( 204, 0, 0 ),
new HuffmanTreeNode ( 205, 0, 0 ),
new HuffmanTreeNode ( 206, 0, 0 ),
new HuffmanTreeNode ( 207, 0, 0 ),
new HuffmanTreeNode ( 208, 0, 0 ),
new HuffmanTreeNode ( 209, 0, 0 ),
new HuffmanTreeNode ( 0, 439, 446 ),
new HuffmanTreeNode ( 210, 0, 0 ),
new HuffmanTreeNode ( 0, 441, 494 ),
new HuffmanTreeNode ( 0, 442, 461 ),
new HuffmanTreeNode ( 0, 443, 447 ),
new HuffmanTreeNode ( 0, 444, 445 ),
new HuffmanTreeNode ( 211, 0, 0 ),
new HuffmanTreeNode ( 212, 0, 0 ),
new HuffmanTreeNode ( 213, 0, 0 ),
new HuffmanTreeNode ( 0, 448, 460 ),
new HuffmanTreeNode ( 214, 0, 0 ),
new HuffmanTreeNode ( 0, 450, 467 ),
new HuffmanTreeNode ( 215, 0, 0 ),
new HuffmanTreeNode ( 0, 452, 469 ),
new HuffmanTreeNode ( 0, 453, 454 ),
new HuffmanTreeNode ( 216, 0, 0 ),
new HuffmanTreeNode ( 217, 0, 0 ),
new HuffmanTreeNode ( 0, 456, 484 ),
new HuffmanTreeNode ( 0, 457, 458 ),
new HuffmanTreeNode ( 218, 0, 0 ),
new HuffmanTreeNode ( 219, 0, 0 ),
new HuffmanTreeNode ( 220, 0, 0 ),
new HuffmanTreeNode ( 221, 0, 0 ),
new HuffmanTreeNode ( 0, 462, 488 ),
new HuffmanTreeNode ( 0, 463, 464 ),
new HuffmanTreeNode ( 222, 0, 0 ),
new HuffmanTreeNode ( 223, 0, 0 ),
new HuffmanTreeNode ( 0, 466, 468 ),
new HuffmanTreeNode ( 224, 0, 0 ),
new HuffmanTreeNode ( 225, 0, 0 ),
new HuffmanTreeNode ( 226, 0, 0 ),
new HuffmanTreeNode ( 0, 470, 472 ),
new HuffmanTreeNode ( 227, 0, 0 ),
new HuffmanTreeNode ( 228, 0, 0 ),
new HuffmanTreeNode ( 229, 0, 0 ),
new HuffmanTreeNode ( 230, 0, 0 ),
new HuffmanTreeNode ( 231, 0, 0 ),
new HuffmanTreeNode ( 0, 476, 477 ),
new HuffmanTreeNode ( 232, 0, 0 ),
new HuffmanTreeNode ( 233, 0, 0 ),
new HuffmanTreeNode ( 0, 479, 480 ),
new HuffmanTreeNode ( 234, 0, 0 ),
new HuffmanTreeNode ( 235, 0, 0 ),
new HuffmanTreeNode ( 0, 482, 483 ),
new HuffmanTreeNode ( 236, 0, 0 ),
new HuffmanTreeNode ( 237, 0, 0 ),
new HuffmanTreeNode ( 0, 485, 487 ),
new HuffmanTreeNode ( 238, 0, 0 ),
new HuffmanTreeNode ( 239, 0, 0 ),
new HuffmanTreeNode ( 240, 0, 0 ),
new HuffmanTreeNode ( 0, 489, 493 ),
new HuffmanTreeNode ( 241, 0, 0 ),
new HuffmanTreeNode ( 0, 491, 492 ),
new HuffmanTreeNode ( 242, 0, 0 ),
new HuffmanTreeNode ( 243, 0, 0 ),
new HuffmanTreeNode ( 244, 0, 0 ),
new HuffmanTreeNode ( 0, 495, 503 ),
new HuffmanTreeNode ( 0, 496, 499 ),
new HuffmanTreeNode ( 0, 497, 498 ),
new HuffmanTreeNode ( 245, 0, 0 ),
new HuffmanTreeNode ( 246, 0, 0 ),
new HuffmanTreeNode ( 0, 500, 501 ),
new HuffmanTreeNode ( 247, 0, 0 ),
new HuffmanTreeNode ( 248, 0, 0 ),
new HuffmanTreeNode ( 249, 0, 0 ),
new HuffmanTreeNode ( 0, 504, 507 ),
new HuffmanTreeNode ( 0, 505, 506 ),
new HuffmanTreeNode ( 250, 0, 0 ),
new HuffmanTreeNode ( 251, 0, 0 ),
new HuffmanTreeNode ( 0, 508, 509 ),
new HuffmanTreeNode ( 252, 0, 0 ),
new HuffmanTreeNode ( 253, 0, 0 ),
new HuffmanTreeNode ( 254, 0, 0 ),
new HuffmanTreeNode ( 255, 0, 0 ),
new HuffmanTreeNode ( 256, 0, 0 )
};
//static HuffmanEncoder()
//{
// BuildTree();
//}
//
//private static void BuildTree()
//{
// // Add root
// entries.Add(new TreeNode());
//
// for (int i = 0; i < StaticTable.Length; ++i)
// {
// var tableEntry = StaticTable[i];
// var currentNode = entries[0];
// int currentNodeIdx = 0;
//
// for (byte bitIdx = 1; bitIdx <= tableEntry.Bits; bitIdx++)
// {
// byte bit = tableEntry.GetBitAtIdx(bitIdx);
//
// switch(bit)
// {
// case 0:
// if (currentNode.NextZeroIdx == 0)
// {
// currentNode.NextZeroIdx = (UInt16)entries.Count;
// entries[currentNodeIdx] = currentNode;
// entries.Add(new TreeNode());
// }
//
// currentNodeIdx = currentNode.NextZeroIdx;
// currentNode = entries[currentNodeIdx];
// break;
//
// case 1:
// if (currentNode.NextOneIdx == 0)
// {
// currentNode.NextOneIdx = (UInt16)entries.Count;
// entries[currentNodeIdx] = currentNode;
// entries.Add(new TreeNode());
// }
//
// currentNodeIdx = currentNode.NextOneIdx;
// currentNode = entries[currentNodeIdx];
// break;
//
// default:
// HTTPManager.Logger.Information("HuffmanEncoder", "BuildTree - GetBitAtIdx returned with an unsupported value: " + bit);
// break;
// }
// }
//
// entries[currentNodeIdx] = new TreeNode { Value = (UInt16)i };
//
// //HTTPManager.Logger.Information("HuffmanEncoder", string.Format("BuildTree - {0} - Entry({1}) added to idx: {2}", i, entries[currentNodeIdx], currentNodeIdx));
// }
//
// //HTTPManager.Logger.Information("HuffmanEncoder", "BuildTree - entries: " + entries.Count);
// //for (int i = 0; i < entries.Count; ++i)
// // HTTPManager.Logger.Information("HuffmanEncoder", string.Format("{0} - Entry : {1}", i, entries[i]));
// System.Text.StringBuilder sb = new System.Text.StringBuilder();
// for (int i = 0; i < entries.Count; ++i)
// {
// sb.AppendFormat("new TreeNode {{ Value = {0}, NextZeroIdx = {1}, NextOneIdx = {2} }},\n", entries[i].Value, entries[i].NextZeroIdx, entries[i].NextOneIdx);
// }
// UnityEngine.Debug.Log(sb.ToString());
//}
public static HuffmanTreeNode GetRoot()
{
return HuffmanTree[0];
}
public static HuffmanTreeNode GetNext(HuffmanTreeNode current, byte bit)
{
switch(bit)
{
case 0:
return HuffmanTree[current.NextZeroIdx];
case 1:
return HuffmanTree[current.NextOneIdx];
}
throw new Exception("HuffmanEncoder - GetNext - unsupported bit: " + bit);
}
public static HuffmanTableEntry GetEntryForCodePoint(UInt16 codePoint)
{
return StaticTable[codePoint];
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: babdeee42980ac84fb76ef946256b076
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: