提交Unity 联机Pro

This commit is contained in:
PC-20230316NUNE\Administrator
2024-08-17 14:27:18 +08:00
parent f00193b000
commit 894100ae37
7448 changed files with 854473 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using System;
namespace BestHTTP.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 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 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);
}
/// <summary>
/// bitIdx: 01234567
/// </summary>
public static byte SetBit(byte value, byte bitIdx, bool bitValue)
{
return SetBit(value, bitIdx, Convert.ToByte(bitValue));
}
/// <summary>
/// bitIdx: 01234567
/// </summary>
public static byte SetBit(byte value, byte bitIdx, byte bitValue)
{
//byte mask = (byte)(0x80 >> bitIdx);
return (byte)((value ^ (value & (0x80 >> bitIdx))) | bitValue << (7 - bitIdx));
}
/// <summary>
/// bitIdx: 01234567
/// </summary>
public static byte ReadBit(byte value, byte bitIdx)
{
byte mask = (byte)(0x80 >> bitIdx);
return (byte)((value & mask) >> (7 - bitIdx));
}
/// <summary>
/// bitIdx: 01234567
/// </summary>
public static byte ReadValue(byte value, byte fromBit, byte toBit)
{
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 ReadUInt32(byte[] buffer, int offset)
{
return (UInt32)(buffer[offset + 3] |
buffer[offset + 2] << 8 |
buffer[offset + 1] << 16 |
buffer[offset + 0] << 24
);
}
public static long ReadLong(byte[] buffer, int offset)
{
return (long)buffer[offset + 7] |
(long)buffer[offset + 6] << 8 |
(long)buffer[offset + 5] << 16 |
(long)buffer[offset + 4] << 24 |
(long)buffer[offset + 3] << 32 |
(long)buffer[offset + 2] << 40 |
(long)buffer[offset + 1] << 48 |
(long)buffer[offset + 0] << 56;
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 53d7db68937661d4eb9c0fe989b9f46c
timeCreated: 1571210041
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,234 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using BestHTTP.PlatformSupport.Memory;
using System;
using System.Collections.Generic;
using System.IO;
namespace BestHTTP.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 UInt32 dataOffset;
protected UInt32 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 += (UInt32)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 = PlatformSupport.Text.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 PlatformSupport.Text.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).HeaderBlockFragmentLength;
case HTTP2FrameTypes.CONTINUATION:
return frame.PayloadLength;
}
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;
switch (frame.Type)
{
case HTTP2FrameTypes.HEADERS:
var header = HTTP2FrameHelper.ReadHeadersFrame(frame);
this.dataOffset = header.HeaderBlockFragmentIdx;
this.maxOffset = this.dataOffset + header.HeaderBlockFragmentLength;
break;
case HTTP2FrameTypes.CONTINUATION:
this.dataOffset = 0;
this.maxOffset = frame.PayloadLength;
break;
}
return true;
}
}
public sealed class DataFrameView : CommonFrameView
{
public override void AddFrame(HTTP2FrameHeaderAndPayload frame)
{
if (frame.Type != HTTP2FrameTypes.DATA)
throw new ArgumentException("HeaderFrameView - Unexpected frame type: " + frame.Type);
this.frames.Add(frame);
this.Length += CalculateDataLengthForFrame(frame);
}
protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame)
{
return HTTP2FrameHelper.ReadDataFrame(frame).DataLength;
}
protected override bool AdvanceFrame()
{
if (this.currentFrameIdx >= this.frames.Count - 1)
return false;
this.currentFrameIdx++;
HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx];
HTTP2DataFrame dataFrame = HTTP2FrameHelper.ReadDataFrame(frame);
this.data = frame.Payload;
this.dataOffset = dataFrame.DataIdx;
this.maxOffset = dataFrame.DataIdx + dataFrame.DataLength;
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,12 @@
fileFormatVersion: 2
guid: 591ec412e5c6f2a42b591c9c02921d33
timeCreated: 1571210041
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6b9891b070f01ec458dd74bd4e91cf75
timeCreated: 1571210041
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,402 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
using System;
using System.Collections.Generic;
using System.IO;
namespace BestHTTP.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 = null;
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[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[0], 0);
frame.LastStreamId = BufferHelper.ReadUInt31(header.Payload, 0);
frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 4);
frame.AdditionalDebugDataLength = header.PayloadLength - 8;
if (frame.AdditionalDebugDataLength > 0)
{
frame.AdditionalDebugData = BufferPool.Get(frame.AdditionalDebugDataLength, true);
Array.Copy(header.Payload, 8, frame.AdditionalDebugData, 0, frame.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, 0, frame.OpaqueData, 0, frame.OpaqueDataLength);
return frame;
}
public static HTTP2PushPromiseFrame ReadPush_PromiseFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#PUSH_PROMISE
HTTP2PushPromiseFrame frame = new HTTP2PushPromiseFrame(header);
frame.HeaderBlockFragmentLength = header.PayloadLength - 4; // PromisedStreamId
bool isPadded = (frame.Flags & HTTP2PushPromiseFlags.PADDED) != 0;
if (isPadded)
{
frame.PadLength = header.Payload[0];
frame.HeaderBlockFragmentLength -= (uint)(1 + (frame.PadLength ?? 0));
}
frame.ReservedBit = BufferHelper.ReadBit(header.Payload[1], 0);
frame.PromisedStreamId = BufferHelper.ReadUInt31(header.Payload, 1);
frame.HeaderBlockFragmentIdx = (UInt32)(isPadded ? 5 : 4);
frame.HeaderBlockFragment = header.Payload;
header.Payload = null;
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.PayloadLength != 5)
{
//throw FRAME_SIZE_ERROR
}
HTTP2PriorityFrame frame = new HTTP2PriorityFrame(header);
frame.IsExclusive = BufferHelper.ReadBit(header.Payload[0], 0);
frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, 0);
frame.Weight = header.Payload[4];
return frame;
}
public static HTTP2HeadersFrame ReadHeadersFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#HEADERS
HTTP2HeadersFrame frame = new HTTP2HeadersFrame(header);
frame.HeaderBlockFragmentLength = header.PayloadLength;
bool isPadded = (frame.Flags & HTTP2HeadersFlags.PADDED) != 0;
bool isPriority = (frame.Flags & HTTP2HeadersFlags.PRIORITY) != 0;
int payloadIdx = 0;
if (isPadded)
{
frame.PadLength = header.Payload[payloadIdx++];
uint subLength = (uint)(1 + (frame.PadLength ?? 0));
if (subLength <= frame.HeaderBlockFragmentLength)
frame.HeaderBlockFragmentLength -= subLength;
//else
// throw PROTOCOL_ERROR;
}
if (isPriority)
{
frame.IsExclusive = BufferHelper.ReadBit(header.Payload[payloadIdx], 0);
frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, payloadIdx);
payloadIdx += 4;
frame.Weight = header.Payload[payloadIdx++];
uint subLength = 5;
if (subLength <= frame.HeaderBlockFragmentLength)
frame.HeaderBlockFragmentLength -= subLength;
//else
// throw PROTOCOL_ERROR;
}
frame.HeaderBlockFragmentIdx = (UInt32)payloadIdx;
frame.HeaderBlockFragment = header.Payload;
return frame;
}
public static HTTP2DataFrame ReadDataFrame(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#DATA
HTTP2DataFrame frame = new HTTP2DataFrame(header);
frame.DataLength = header.PayloadLength;
bool isPadded = (frame.Flags & HTTP2DataFlags.PADDED) != 0;
if (isPadded)
{
frame.PadLength = header.Payload[0];
uint subLength = (uint)(1 + (frame.PadLength ?? 0));
if (subLength <= frame.DataLength)
frame.DataLength -= subLength;
//else
// throw PROTOCOL_ERROR;
}
frame.DataIdx = (UInt32)(isPadded ? 1 : 0);
frame.Data = header.Payload;
header.Payload = null;
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 PooledBuffer HeaderAsBinary(HTTP2FrameHeaderAndPayload header)
{
// https://httpwg.org/specs/rfc7540.html#FrameHeader
var buffer = BufferPool.Get(9, true);
BufferHelper.SetUInt24(buffer, 0, header.PayloadLength);
buffer[3] = (byte)header.Type;
buffer[4] = header.Flags;
BufferHelper.SetUInt31(buffer, 5, header.StreamId);
return new PooledBuffer { Data = buffer, Length = 9 };
}
public static HTTP2FrameHeaderAndPayload ReadHeader(Stream stream)
{
byte[] buffer = BufferPool.Get(9, true);
try
{
StreamRead(stream, buffer, 0, 9);
}
catch
{
BufferPool.Release(buffer);
throw;
}
HTTP2FrameHeaderAndPayload header = new HTTP2FrameHeaderAndPayload();
header.PayloadLength = BufferHelper.ReadUInt24(buffer, 0);
header.Type = (HTTP2FrameTypes)buffer[3];
header.Flags = buffer[4];
header.StreamId = BufferHelper.ReadUInt31(buffer, 5);
BufferPool.Release(buffer);
header.Payload = BufferPool.Get(header.PayloadLength, true);
try
{
StreamRead(stream, header.Payload, 0, header.PayloadLength);
}
catch
{
BufferPool.Release(header.Payload);
throw;
}
return header;
}
public static HTTP2SettingsFrame ReadSettings(HTTP2FrameHeaderAndPayload header)
{
HTTP2SettingsFrame frame = new HTTP2SettingsFrame(header);
if (header.PayloadLength > 0)
{
int kvpCount = (int)(header.PayloadLength / 6);
frame.Settings = new List<KeyValuePair<HTTP2Settings, uint>>(kvpCount);
for (int i = 0; i < kvpCount; ++i)
{
HTTP2Settings key = (HTTP2Settings)BufferHelper.ReadUInt16(header.Payload, 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)
{
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.SETTINGS;
frame.Flags = 0;
frame.PayloadLength = (UInt32)settings.Count * 6;
frame.Payload = BufferPool.Get(frame.PayloadLength, true);
for (int i = 0; i < settings.Count; ++i)
{
BufferHelper.SetUInt16(frame.Payload, i * 6, (UInt16)settings[i].Key);
BufferHelper.SetUInt32(frame.Payload, (i * 6) + 2, settings[i].Value);
}
return frame;
}
public static HTTP2FrameHeaderAndPayload CreatePingFrame(HTTP2PingFlags flags = HTTP2PingFlags.None)
{
// 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);
frame.PayloadLength = 8;
return frame;
}
public static HTTP2FrameHeaderAndPayload CreateWindowUpdateFrame(UInt32 streamId, UInt32 windowSizeIncrement)
{
// 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);
frame.PayloadLength = 4;
BufferHelper.SetBit(0, 0, 0);
BufferHelper.SetUInt31(frame.Payload, 0, windowSizeIncrement);
return frame;
}
public static HTTP2FrameHeaderAndPayload CreateGoAwayFrame(UInt32 lastStreamId, HTTP2ErrorCodes error)
{
// 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);
frame.PayloadLength = 8;
BufferHelper.SetUInt31(frame.Payload, 0, lastStreamId);
BufferHelper.SetUInt31(frame.Payload, 4, (UInt32)error);
return frame;
}
public static HTTP2FrameHeaderAndPayload CreateRSTFrame(UInt32 streamId, HTTP2ErrorCodes errorCode)
{
// 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);
frame.PayloadLength = 4;
BufferHelper.SetUInt32(frame.Payload, 0, (UInt32)errorCode);
return frame;
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8a3c905b246be424b8afe85c7e0a8526
timeCreated: 1571210041
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,406 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
using System;
using System.Collections.Generic;
namespace BestHTTP.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 UInt32 PayloadLength;
public HTTP2FrameTypes Type;
public byte Flags;
public UInt32 StreamId;
public byte[] Payload;
public UInt32 PayloadOffset;
public bool DontUseMemPool;
public override string ToString()
{
return string.Format("[HTTP2FrameHeaderAndPayload Length: {0}, Type: {1}, Flags: {2}, StreamId: {3}, PayloadOffset: {4}, DontUseMemPool: {5}, Payload: {6}]",
this.PayloadLength, this.Type, this.Flags.ToBinaryStr(), this.StreamId, this.PayloadOffset, this.DontUseMemPool,
this.Payload == null ? BufferSegment.Empty : new BufferSegment(this.Payload, (int)this.PayloadOffset, (int)this.PayloadLength));
}
public string PayloadAsHex()
{
System.Text.StringBuilder sb = PlatformSupport.Text.StringBuilderPool.Get((int)this.PayloadLength + 2);
sb.Append("[");
if (this.Payload != null && this.PayloadLength > 0)
{
uint idx = this.PayloadOffset;
sb.Append(this.Payload[idx++]);
for (int i = 1; i < this.PayloadLength; i++)
sb.AppendFormat(", {0:X2}", this.Payload[idx++]);
}
sb.Append("]");
return PlatformSupport.Text.StringBuilderPool.ReleaseAndGrab(sb);
}
}
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 = PlatformSupport.Text.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 = PlatformSupport.Text.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 UInt32 DataIdx;
public byte[] Data;
public uint DataLength;
public HTTP2DataFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.PadLength = null;
this.DataIdx = 0;
this.Data = null;
this.DataLength = 0;
}
public override string ToString()
{
return string.Format("[HTTP2DataFrame Header: {0}, Flags: {1}, PadLength: {2}, DataLength: {3}]",
this.Header.ToString(),
this.Flags,
this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
this.DataLength);
}
}
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 UInt32 HeaderBlockFragmentIdx;
public byte[] HeaderBlockFragment;
public UInt32 HeaderBlockFragmentLength;
public HTTP2HeadersFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.PadLength = null;
this.IsExclusive = null;
this.StreamDependency = null;
this.Weight = null;
this.HeaderBlockFragmentIdx = 0;
this.HeaderBlockFragment = null;
this.HeaderBlockFragmentLength = 0;
}
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.HeaderBlockFragmentLength);
}
}
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 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;
}
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.HeaderBlockFragmentLength);
}
}
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 HTTP2PingFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.OpaqueData = BufferPool.Get(8, true);
this.OpaqueDataLength = 8;
}
public override string ToString()
{
return string.Format("[HTTP2PingFrame Header: {0}, Flags: {1}, OpaqueData: {2}]",
this.Header.ToString(),
this.Flags,
SecureProtocol.Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(this.OpaqueData, 0, this.OpaqueDataLength));
}
}
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 byte[] AdditionalDebugData;
public UInt32 AdditionalDebugDataLength;
public HTTP2GoAwayFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.ReservedBit = 0;
this.LastStreamId = 0;
this.ErrorCode = 0;
this.AdditionalDebugData = null;
this.AdditionalDebugDataLength = 0;
}
public override string ToString()
{
return string.Format("[HTTP2GoAwayFrame Header: {0}, ReservedBit: {1}, LastStreamId: {2}, Error: {3}({4}), AdditionalDebugData({5}): {6}]",
this.Header.ToString(),
this.ReservedBit,
this.LastStreamId,
this.Error,
this.ErrorCode,
this.AdditionalDebugDataLength,
this.AdditionalDebugData == null ? "Empty" : SecureProtocol.Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(this.AdditionalDebugData, 0, (int)this.AdditionalDebugDataLength));
}
}
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 byte[] HeaderBlockFragment;
public UInt32 HeaderBlockFragmentLength { get { return this.Header.PayloadLength; } }
public HTTP2ContinuationFrame(HTTP2FrameHeaderAndPayload header)
{
this.Header = header;
this.HeaderBlockFragment = null;
}
public override string ToString()
{
return string.Format("[HTTP2ContinuationFrame Header: {0}, Flags: {1}, HeaderBlockFragmentLength: {2}]",
this.Header.ToString(),
this.Flags,
this.HeaderBlockFragmentLength);
}
}
/// <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,12 @@
fileFormatVersion: 2
guid: 06f965acc306c134093ce2a816832155
timeCreated: 1571210040
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,671 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using System;
using System.Collections.Generic;
using System.Threading;
using System.Collections.Concurrent;
using BestHTTP.Extensions;
using BestHTTP.Core;
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.Logger;
using BestHTTP.PlatformSupport.Threading;
namespace BestHTTP.Connections.HTTP2
{
public sealed class HTTP2Handler : IHTTPRequestHandler
{
public bool HasCustomRequestProcessor { get { return true; } }
public KeepAliveHeader KeepAlive { get { return null; } }
public bool CanProcessMultiple { get { return this.goAwaySentAt == DateTime.MaxValue && this.isRunning; } }
// Connection preface starts with the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n).
private static readonly byte[] MAGIC = new 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 };
public const UInt32 MaxValueFor31Bits = 0xFFFFFFFF >> 1;
public double Latency { get; private set; }
public HTTP2SettingsManager settings;
public HPACKEncoder HPACKEncoder;
public LoggingContext Context { get; private set; }
private DateTime lastPingSent = DateTime.MinValue;
private TimeSpan pingFrequency = TimeSpan.MaxValue; // going to be overridden in RunHandler
private int waitingForPingAck = 0;
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 HTTPConnection conn;
private int threadExitCount;
private TimeSpan MaxGoAwayWaitTime { get { return this.goAwaySentAt == DateTime.MaxValue ? 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 HTTP2Handler(HTTPConnection conn)
{
this.Context = new LoggingContext(this);
this.conn = conn;
this.isRunning = true;
this.settings = new HTTP2SettingsManager(this);
Process(this.conn.CurrentRequest);
}
public void Process(HTTPRequest request)
{
HTTPManager.Logger.Information("HTTP2Handler", "Process request called", this.Context, request.Context);
request.QueuedAt = DateTime.MinValue;
request.ProcessingStarted = this.lastInteraction = DateTime.UtcNow;
this.requestQueue.Enqueue(request);
// Wee might added the request to a dead queue, signaling would be pointless.
// When the ConnectionEventHelper processes the Close state-change event
// requests in the queue going to be resent. (We should avoid resending the request just right now,
// as it might still select this connection/handler resulting in a infinite loop.)
if (Volatile.Read(ref this.threadExitCount) == 0)
this.newFrameSignal.Set();
}
public void SignalRunnerThread()
{
this.newFrameSignal?.Set();
}
public void RunHandler()
{
HTTPManager.Logger.Information("HTTP2Handler", "Processing thread up and running!", this.Context);
ThreadedRunner.SetThreadName("BestHTTP.HTTP2 Process");
PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ReadThread);
try
{
bool atLeastOneStreamHasAFrameToSend = true;
this.HPACKEncoder = new HPACKEncoder(this, 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.connector.Stream, 1024 * 1024 /*1500 - 20 - 20*/))
{
// The client connection preface starts with a sequence of 24 octets
bufferedStream.Write(MAGIC, 0, MAGIC.Length);
// 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
// Set streams' initial window size to its maximum.
this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] = HTTPManager.HTTP2Settings.InitialStreamWindowSize;
this.settings.InitiatedMySettings[HTTP2Settings.MAX_CONCURRENT_STREAMS] = HTTPManager.HTTP2Settings.MaxConcurrentStreams;
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] = (uint)(HTTPManager.HTTP2Settings.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 = HTTPManager.HTTP2Settings.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 == HTTP2Handler.MaxValueFor31Bits)
initialConnectionWindowSize -= 10 * 1024 * 1024;
Int64 diff = initialConnectionWindowSize - 65535;
if (diff > 0)
this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)diff));
this.pingFrequency = HTTPManager.HTTP2Settings.PingFrequency;
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.pingFrequency;
var timeoutAt = this.waitingForPingAck != 0 ? this.lastPingSent + HTTPManager.HTTP2Settings.Timeout : DateTime.MaxValue;
var nextPingInteraction = sendPingAt < timeoutAt ? sendPingAt : timeoutAt;
var disconnectByIdleAt = this.lastInteraction + HTTPManager.HTTP2Settings.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);
if (wait >= 1)
{
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
HTTPManager.Logger.Information("HTTP2Handler", 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.pingFrequency && Interlocked.CompareExchange(ref this.waitingForPingAck, 1, 0) == 0)
{
this.lastPingSent = now;
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.None);
BufferHelper.SetLong(frame.Payload, 0, now.Ticks);
this.outgoingFrames.Add(frame);
}
// If no pong received in a (configurable) reasonable time, treat the connection broken
if (this.waitingForPingAck != 0 && now - this.lastPingSent >= HTTPManager.HTTP2Settings.Timeout)
throw new TimeoutException("Ping ACK isn't received in time!");
// Process received frames
HTTP2FrameHeaderAndPayload header;
while (this.newFrames.TryDequeue(out header))
{
if (header.StreamId > 0)
{
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 (HTTPManager.Logger.Level == Loglevels.All)
HTTPManager.Logger.Warning("HTTP2Handler", string.Format("No stream found for id: {0}! Can't deliver frame: {1}", header.StreamId, header), this.Context, http2Stream.Context);
}
}
else
{
switch (header.Type)
{
case HTTP2FrameTypes.SETTINGS:
this.settings.Process(header, this.outgoingFrames);
PluginEventHelper.EnqueuePluginEvent(
new PluginEventInfo(PluginEvents.HTTP2ConnectProtocol,
new HTTP2ConnectProtocolInfo(this.conn.LastProcessedUri.Host,
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);
// https://httpwg.org/specs/rfc7540.html#PING
// if it wasn't an ack for our ping, we have to send one
if ((pingFrame.Flags & HTTP2PingFlags.ACK) == 0)
{
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.ACK);
Array.Copy(pingFrame.OpaqueData, 0, frame.Payload, 0, pingFrame.OpaqueDataLength);
this.outgoingFrames.Add(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);
HTTPManager.Logger.Information("HTTP2Handler", "Received GOAWAY frame: " + goAwayFrame.ToString(), this.Context);
string msg = string.Format("Server closing the connection! Error code: {0} ({1}) Additonal Debug Data: {2}", goAwayFrame.Error, goAwayFrame.ErrorCode, new BufferSegment(goAwayFrame.AdditionalDebugData, 0, (int)goAwayFrame.AdditionalDebugDataLength));
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
this.clientInitiatedStreams[i].Abort(msg);
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);
}
}
UInt32 maxConcurrentStreams = Math.Min(HTTPManager.HTTP2Settings.MaxConcurrentStreams, this.settings.RemoteSettings[HTTP2Settings.MAX_CONCURRENT_STREAMS]);
// pre-test stream count to lock only when truly needed.
if (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.isRunning)
{
// grab requests from queue
HTTPRequest request;
while (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.requestQueue.TryDequeue(out request))
{
HTTP2Stream newStream = null;
#if !BESTHTTP_DISABLE_WEBSOCKET
if (request.Tag is WebSocket.OverHTTP2)
{
newStream = new HTTP2WebSocketStream((UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
}
else
#endif
{
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
// Room for improvement Streams should be processed by their priority!
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();
}
atLeastOneStreamHasAFrameToSend |= stream.HasFrameToSend;
this.lastInteraction = DateTime.UtcNow;
}
// 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 && now - this.lastInteraction >= HTTPManager.HTTP2Settings.MaxIdleTime)
{
this.lastInteraction = DateTime.UtcNow;
HTTPManager.Logger.Information("HTTP2Handler", "Reached idle time, sending GoAway frame!", this.Context);
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
this.goAwaySentAt = DateTime.UtcNow;
}
// 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)
{
HTTPManager.Logger.Information("HTTP2Handler", "Connection abort requested, sending GoAway frame!", this.Context);
this.outgoingFrames.Clear();
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
this.goAwaySentAt = DateTime.UtcNow;
}
if (this.isRunning && now - goAwaySentAt >= this.MaxGoAwayWaitTime)
{
HTTPManager.Logger.Information("HTTP2Handler", "No GoAway frame received back. Really quitting now!", this.Context);
this.isRunning = false;
//conn.State = HTTPConnectionStates.Closed;
}
uint streamWindowUpdates = 0;
// Go through all the collected frames and send them.
for (int i = 0; i < this.outgoingFrames.Count; ++i)
{
var frame = this.outgoingFrames[i];
if (HTTPManager.Logger.Level <= Logger.Loglevels.All && frame.Type != HTTP2FrameTypes.DATA /*&& frame.Type != HTTP2FrameTypes.PING*/)
HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString(), this.Context);
// 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.PayloadLength > this.remoteWindow)
{
haltDataSending = true;
HTTPManager.Logger.Warning("HTTP2Handler", string.Format("Data sending halted for this round. Remote Window: {0:N0}, frame: {1}", this.remoteWindow, frame.ToString()), this.Context);
continue;
}
break;
case HTTP2FrameTypes.WINDOW_UPDATE:
if (frame.StreamId > 0)
streamWindowUpdates += BufferHelper.ReadUInt31(frame.Payload, 0);
break;
}
this.outgoingFrames.RemoveAt(i--);
using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
bufferedStream.Write(buffer.Data, 0, buffer.Length);
if (frame.PayloadLength > 0)
{
bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
if (!frame.DontUseMemPool)
BufferPool.Release(frame.Payload);
}
if (frame.Type == HTTP2FrameTypes.DATA)
this.remoteWindow -= frame.PayloadLength;
}
if (streamWindowUpdates > 0)
{
var frame = HTTP2FrameHelper.CreateWindowUpdateFrame(0, streamWindowUpdates);
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString(), this.Context);
using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
bufferedStream.Write(buffer.Data, 0, buffer.Length);
bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
if (!frame.DontUseMemPool)
BufferPool.Release(frame.Payload);
}
} // while (this.isRunning)
bufferedStream.Flush();
}
}
catch (Exception ex)
{
// Log out the exception if it's a non-expected one.
if (this.ShutdownType == ShutdownTypes.Running && this.goAwaySentAt == DateTime.MaxValue && !HTTPManager.IsQuitting)
HTTPManager.Logger.Exception("HTTP2Handler", "Sender thread", ex, this.Context);
}
finally
{
TryToCleanup();
HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing - cleaning up remaining request...", this.Context);
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
this.clientInitiatedStreams[i].Abort("Connection closed unexpectedly");
this.clientInitiatedStreams.Clear();
HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing", this.Context);
}
try
{
if (this.conn != null && this.conn.connector != null)
{
// Works in the new runtime
if (this.conn.connector.TopmostStream != null)
using (this.conn.connector.TopmostStream) { }
// Works in the old runtime
if (this.conn.connector.Stream != null)
using (this.conn.connector.Stream) { }
}
}
catch
{ }
}
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;
}
}
private void ReadThread()
{
try
{
ThreadedRunner.SetThreadName("BestHTTP.HTTP2 Read");
HTTPManager.Logger.Information("HTTP2Handler", "Reader thread up and running!", this.Context);
while (this.isRunning)
{
HTTP2FrameHeaderAndPayload header = HTTP2FrameHelper.ReadHeader(this.conn.connector.Stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information && header.Type != HTTP2FrameTypes.DATA /*&& header.Type != HTTP2FrameTypes.PING*/)
HTTPManager.Logger.Information("HTTP2Handler", "New frame received: " + header.ToString(), this.Context);
// 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);
// ping write thread to process the new frame
this.newFrameSignal.Set();
switch (header.Type)
{
// Handle pongs on the read thread, so no additional latency is added to the rtt calculation.
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(DateTime.UtcNow.Ticks - ticks);
// add it to the buffer
this.rtts.Add(diff.TotalMilliseconds);
// and calculate the new latency
this.Latency = CalculateLatency();
HTTPManager.Logger.Verbose("HTTP2Handler", string.Format("Latency: {0:F2}ms, RTT buffer: {1}", this.Latency, this.rtts.ToString()), this.Context);
}
BufferPool.Release(pingFrame.OpaqueData);
break;
case HTTP2FrameTypes.GOAWAY:
// Just exit from this thread. The processing thread will handle the frame too.
// Risking a double release here if the processing thread also consumed the goaway frame
//if (Volatile.Read(ref this.threadExitCount) > 0)
// BufferPool.Release(header.Payload);
return;
}
}
}
catch //(Exception ex)
{
//HTTPManager.Logger.Exception("HTTP2Handler", "", ex, this.Context);
//this.isRunning = false;
}
finally
{
TryToCleanup();
HTTPManager.Logger.Information("HTTP2Handler", "Reader thread closing", this.Context);
}
}
private void TryToCleanup()
{
this.isRunning = false;
// First thread closing notifies the ConnectionEventHelper
int counter = Interlocked.Increment(ref this.threadExitCount);
switch(counter)
{
case 1:
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed));
break;
// Last thread closes the AutoResetEvent
case 2:
if (this.newFrameSignal != null)
this.newFrameSignal.Close();
this.newFrameSignal = null;
while (this.newFrames.TryDequeue(out var frame))
BufferPool.Release(frame.Payload);
break;
default:
HTTPManager.Logger.Warning("HTTP2Handler", String.Format("TryToCleanup - counter is {0}!", counter));
break;
}
}
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.connector.Stream.Dispose();
break;
}
}
public void Dispose()
{
HTTPRequest request = null;
while (this.requestQueue.TryDequeue(out request))
{
HTTPManager.Logger.Information("HTTP2Handler", string.Format("Dispose - Request '{0}' IsCancellationRequested: {1}", request.CurrentUri.ToString(), request.IsCancellationRequested.ToString()), this.Context);
if (request.IsCancellationRequested)
{
request.Response = null;
request.State = HTTPRequestStates.Aborted;
}
else
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
}
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 21ffa29fa36f0aa48bcf898a72ab5ecf
timeCreated: 1571210040
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,77 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using System;
namespace BestHTTP.Connections.HTTP2
{
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;
}
public sealed class HTTP2PluginSettings
{
/// <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 10 Mb (10 * 1024 * 1024).
/// </summary>
public UInt32 InitialStreamWindowSize = 10 * 1024 * 1024; // 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 = HTTP2Handler.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(30);
/// <summary>
/// Timeout to receive a ping acknowledgement from the server. If no ack reveived in this time the connection will be treated as broken.
/// </summary>
public TimeSpan Timeout = TimeSpan.FromSeconds(10);
/// <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();
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 218daa3af21407f40ac4304f80a79b59
timeCreated: 1571210040
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,156 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using BestHTTP.Core;
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
using System;
using System.Collections.Generic;
using System.IO;
namespace BestHTTP.Connections.HTTP2
{
public sealed class HTTP2Response : HTTPResponse
{
// For progress report
public long ExpectedContentLength { get; private set; }
public bool HasContentEncoding { get => !string.IsNullOrEmpty(this.contentEncoding); }
private string contentEncoding = null;
bool isPrepared;
private Decompression.IDecompressor decompressor;
public HTTP2Response(HTTPRequest request, bool isFromCache)
: base(request, isFromCache)
{
this.VersionMajor = 2;
this.VersionMinor = 0;
}
internal void AddHeaders(List<KeyValuePair<string, string>> headers)
{
this.ExpectedContentLength = -1;
Dictionary<string, List<string>> newHeaders = this.baseRequest.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 (!this.HasContentEncoding && header.Key.Equals("content-encoding", StringComparison.OrdinalIgnoreCase))
{
this.contentEncoding = header.Value;
}
else if (base.baseRequest.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.baseRequest.Context, 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.baseRequest.OnDownloadProgress != null)
HTTPManager.Logger.Information("HTTP2Response", "AddHeaders - No Content-Length header found!", this.baseRequest.Context, this.Context);
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, newHeaders));
}
internal void AddData(Stream stream)
{
if (this.HasContentEncoding)
{
Stream decoderStream = Decompression.DecompressorFactory.GetDecoderStream(stream, this.contentEncoding);
if (decoderStream == null)
{
base.Data = new byte[stream.Length];
stream.Read(base.Data, 0, (int)stream.Length);
}
else
{
using (var ms = new BufferPoolMemoryStream((int)stream.Length))
{
var buf = BufferPool.Get(HTTPResponse.MinReadBufferSize, true);
int byteCount = 0;
while ((byteCount = decoderStream.Read(buf, 0, buf.Length)) > 0)
ms.Write(buf, 0, byteCount);
BufferPool.Release(buf);
base.Data = ms.ToArray();
}
decoderStream.Dispose();
}
}
else
{
base.Data = new byte[stream.Length];
stream.Read(base.Data, 0, (int)stream.Length);
}
}
internal void ProcessData(byte[] payload, int payloadLength)
{
if (!this.isPrepared)
{
this.isPrepared = true;
base.BeginReceiveStreamFragments();
}
if (this.HasContentEncoding)
{
if (this.decompressor == null)
this.decompressor = Decompression.DecompressorFactory.GetDecompressor(this.contentEncoding, this.Context);
var result = this.decompressor.Decompress(payload, 0, payloadLength, true, true);
base.FeedStreamFragment(result.Data, 0, result.Length);
}
else
base.FeedStreamFragment(payload, 0, payloadLength);
}
internal void FinishProcessData()
{
base.FlushRemainingFragmentBuffer();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
if (this.decompressor != null)
{
this.decompressor.Dispose();
this.decompressor = null;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b8916e7811100a643b66249f17136fcc
timeCreated: 1571210041
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,277 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using System;
using System.Collections.Generic;
namespace BestHTTP.Connections.HTTP2
{
// https://httpwg.org/specs/rfc7540.html#iana-settings
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
}
public sealed class HTTP2SettingsRegistry
{
public bool IsReadOnly { get; private set; }
public Action<HTTP2SettingsRegistry, HTTP2Settings, UInt32, UInt32> OnSettingChangedEvent;
private UInt32[] values;
private bool[] changeFlags;
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);
}
}
}
public bool IsChanged { get; private set; }
private HTTP2SettingsManager _parent;
public HTTP2SettingsRegistry(HTTP2SettingsManager parent, bool readOnly, bool treatItAsAlreadyChanged)
{
this._parent = parent;
this.values = new UInt32[HTTP2SettingsManager.SettingsCount];
this.IsReadOnly = readOnly;
if (!this.IsReadOnly)
this.changeFlags = new bool[HTTP2SettingsManager.SettingsCount];
// 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;
}
}
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 > 0 && key <= HTTP2SettingsManager.SettingsCount)
{
UInt32 oldValue = this.values[key];
this.values[key] = value;
if (oldValue != value && this.OnSettingChangedEvent != null)
this.OnSettingChangedEvent(this, setting, oldValue, value);
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
HTTPManager.Logger.Information("HTTP2SettingsRegistry", string.Format("Merge {0}({1}) = {2}", setting, key, value), this._parent.Parent.Context);
}
}
}
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];
}
internal HTTP2FrameHeaderAndPayload CreateFrame()
{
List<KeyValuePair<HTTP2Settings, UInt32>> keyValuePairs = new List<KeyValuePair<HTTP2Settings, uint>>(HTTP2SettingsManager.SettingsCount);
for (int i = 1; i < HTTP2SettingsManager.SettingsCount; ++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);
}
}
public sealed class HTTP2SettingsManager
{
public static readonly int SettingsCount = 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 HTTP2Handler Parent { get; private set; }
public HTTP2SettingsManager(HTTP2Handler parentHandler)
{
this.Parent = parentHandler;
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 <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HTTP2SettingsManager", "Processing Settings frame: " + settingsFrame.ToString(), this.Parent.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 >= TimeSpan.FromSeconds(10))
{
HTTPManager.Logger.Error("HTTP2SettingsManager", "No ACK received for settings frame!", this.Parent.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.SettingsChangesSentAt = DateTime.UtcNow;
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 38a4e3fed00ab4c4181c1a7ff3dcb858
timeCreated: 1571210041
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,657 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using System;
using System.Collections.Generic;
using BestHTTP.Core;
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.Logger;
#if !BESTHTTP_DISABLE_CACHING
using BestHTTP.Caching;
#endif
using BestHTTP.Timings;
namespace BestHTTP.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
}
public class HTTP2Stream
{
public UInt32 Id { get; private set; }
public HTTP2StreamStates State {
get { return this._state; }
protected set {
var oldState = this._state;
this._state = value;
if (oldState != this._state)
{
//this.lastStateChangedAt = DateTime.Now;
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] State changed from {1} to {2}", this.Id, oldState, this._state), this.Context, this.AssignedRequest.Context, this.parent.Context);
}
}
}
private HTTP2StreamStates _state;
//protected DateTime lastStateChangedAt;
//protected TimeSpan TimeSpentInCurrentState { get { return DateTime.Now - this.lastStateChangedAt; } }
/// <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; }
protected bool isStreamedDownload;
protected uint downloaded;
protected HTTPRequest.UploadStreamInfo uploadStreamInfo;
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 FramesAsStreamView dataView;
protected UInt32 localWindow;
protected Int64 remoteWindow;
protected uint windowUpdateThreshold;
protected UInt32 sentData;
protected bool isRSTFrameSent;
protected bool isEndSTRReceived;
protected HTTP2Response response;
protected HTTP2Handler parent;
protected int lastReadCount;
/// <summary>
/// Constructor to create a client stream.
/// </summary>
public HTTP2Stream(UInt32 id, HTTP2Handler parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder)
{
this.Id = id;
this.parent = parentHandler;
this.settings = registry;
this.encoder = hpackEncoder;
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);
this.Context = new LoggingContext(this);
this.Context.Add("id", id);
}
public virtual void Assign(HTTPRequest request)
{
if (request.IsRedirected)
request.Timing.Add(TimingEventNames.Queued_For_Redirection);
else
request.Timing.Add(TimingEventNames.Queued);
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, request.Context, this.parent.Context);
this.AssignedRequest = request;
this.isStreamedDownload = request.UseStreaming && request.OnStreamingData != null;
this.downloaded = 0;
}
public void Process(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
if (this.AssignedRequest.IsCancellationRequested && !this.isRSTFrameSent)
{
// 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));
// We can close the stream if already received headers, or not even sent one
if (this.State == HTTP2StreamStates.HalfClosedRemote || 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 (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.Retries >= this.AssignedRequest.MaxRetries)
{
this.AssignedRequest.Response = null;
this.AssignedRequest.Exception = new Exception(msg);
this.AssignedRequest.State = HTTPRequestStates.Error;
this.State = HTTP2StreamStates.Closed;
}
else
{
this.AssignedRequest.Retries++;
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.Resend));
}
this.Removed();
}
protected void ProcessIncomingFrames(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
UInt32 windowUpdate = 0;
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 (/*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, this.AssignedRequest.Context, this.parent.Context);
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.Add(TimingEventNames.Waiting_TTFB);
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.AssignedRequest.Context, this.parent.Context);
}
this.headerView.Close();
this.headerView = null;
this.AssignedRequest.Timing.Add(TimingEventNames.Headers);
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
if (this.isStreamedDownload)
this.response.FinishProcessData();
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTP2Stream, FramesAsStreamView>(FinishRequest, this, this.dataView);
this.dataView = null;
if (this.State == HTTP2StreamStates.HalfClosedLocal)
this.State = HTTP2StreamStates.Closed;
else
this.State = HTTP2StreamStates.HalfClosedRemote;
}
}
break;
case HTTP2FrameTypes.DATA:
ProcessIncomingDATAFrame(ref frame, ref windowUpdate);
break;
case HTTP2FrameTypes.WINDOW_UPDATE:
HTTP2WindowUpdateFrame windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(frame);
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
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, this.AssignedRequest.Context, this.parent.Context);
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, this.AssignedRequest.Context, this.parent.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, this.AssignedRequest.Context, this.parent.Context);
break;
}
if (!frame.DontUseMemPool)
BufferPool.Release(frame.Payload);
}
// 2023.07.08: Even if State is Closed, we should send back a window update.
// Not because of this stream, but for the global window update.
if (windowUpdate > 0 /*&& this.State != HTTP2StreamStates.Closed*/)
{
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Sending window update: {1:N0}, current window: {2:N0}, initial window size: {3:N0}", this.Id, windowUpdate, this.localWindow, this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE]), this.Context, this.AssignedRequest.Context, this.parent.Context);
this.localWindow += windowUpdate;
outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, windowUpdate));
}
}
protected virtual void ProcessIncomingDATAFrame(ref HTTP2FrameHeaderAndPayload frame, ref uint windowUpdate)
{
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open)
{
// ERROR!
return;
}
this.downloaded += frame.PayloadLength;
if (this.isStreamedDownload && frame.Payload != null && frame.PayloadLength > 0)
this.response.ProcessData(frame.Payload, (int)frame.PayloadLength);
// frame's buffer will be released by the frames view
frame.DontUseMemPool = !this.isStreamedDownload;
if (this.dataView == null && !this.isStreamedDownload)
this.dataView = new FramesAsStreamView(new DataFrameView());
if (!this.isStreamedDownload)
this.dataView.AddFrame(frame);
// Track received data, and if necessary(local window getting too low), send a window update frame
if (this.localWindow < frame.PayloadLength)
{
HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] Frame's PayloadLength ({1:N0}) is larger then local window ({2:N0}). Frame: {3}", this.Id, frame.PayloadLength, this.localWindow, frame), this.Context, this.AssignedRequest.Context, this.parent.Context);
}
else
this.localWindow -= frame.PayloadLength;
if ((frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0)
this.isEndSTRReceived = true;
// Window update logic.
// 1.) We could use a logic to only send window update(s) after a threshold is reached.
// When the initial window size is high enough to contain the whole or most of the result,
// sending back two window updates (connection and stream) after every data frame is pointless.
// 2.) On the other hand, window updates are cheap and works even when initial window size is low.
// (
if (this.isEndSTRReceived || this.localWindow <= this.windowUpdateThreshold)
windowUpdate += this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] - this.localWindow - windowUpdate;
if (this.isEndSTRReceived)
{
if (this.isStreamedDownload)
this.response.FinishProcessData();
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded), this.Context, this.AssignedRequest.Context, this.parent.Context);
// create a short living thread to process the downloaded data:
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTP2Stream, FramesAsStreamView>(FinishRequest, this, this.dataView);
this.dataView = null;
if (this.State == HTTP2StreamStates.HalfClosedLocal)
this.State = HTTP2StreamStates.Closed;
else
this.State = HTTP2StreamStates.HalfClosedRemote;
}
else if (this.AssignedRequest.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:
UInt32 initiatedInitialWindowSize = this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
this.localWindow = initiatedInitialWindowSize;
// window update with a zero increment would be an error (https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE)
//if (HTTP2Connection.MaxValueFor31Bits > initiatedInitialWindowSize)
// this.outgoing.Enqueue(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, HTTP2Connection.MaxValueFor31Bits - initiatedInitialWindowSize));
//this.localWindow = HTTP2Connection.MaxValueFor31Bits;
#if !BESTHTTP_DISABLE_CACHING
// Setup cache control headers before we send out the request
if (!this.AssignedRequest.DisableCache)
HTTPCacheService.SetHeaders(this.AssignedRequest);
#endif
// 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.
this.uploadStreamInfo = this.AssignedRequest.GetUpStream();
//this.State = HTTP2StreamStates.Open;
if (this.uploadStreamInfo.Stream == null)
{
this.State = HTTP2StreamStates.HalfClosedLocal;
this.AssignedRequest.Timing.Add(TimingEventNames.Request_Sent);
}
else
{
this.State = HTTP2StreamStates.Open;
this.lastReadCount = 1;
}
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, this.AssignedRequest.Context, this.parent.Context);
break;
case HTTP2StreamStates.HalfClosedLocal:
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)
{
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Skipping data sending as remote Window is {1}!", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
return;
}
// This step will send one frame per OpenState call.
Int64 maxFrameSize = Math.Min(HTTPRequest.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);
// 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.uploadStreamInfo.Stream.Read(frame.Payload, 0, (int)Math.Min(maxFrameSize, int.MaxValue));
if (this.lastReadCount <= 0)
{
BufferPool.Release(frame.Payload);
frame.Payload = null;
frame.PayloadLength = 0;
if (this.lastReadCount < 0)
return;
}
else
frame.PayloadLength = (UInt32)this.lastReadCount;
frame.PayloadOffset = 0;
frame.DontUseMemPool = false;
if (this.lastReadCount <= 0)
{
this.uploadStreamInfo.Stream.Dispose();
this.uploadStreamInfo = new HTTPRequest.UploadStreamInfo();
frame.Flags = (byte)(HTTP2DataFlags.END_STREAM);
this.State = HTTP2StreamStates.HalfClosedLocal;
this.AssignedRequest.Timing.Add(TimingEventNames.Request_Sent);
}
this.outgoing.Enqueue(frame);
this.remoteWindow -= frame.PayloadLength;
this.sentData += frame.PayloadLength;
if (this.AssignedRequest.OnUploadProgress != null)
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.UploadProgress, this.sentData, this.uploadStreamInfo.Length));
}
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;
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, this.AssignedRequest.Context, this.parent.Context);
break;
}
}
protected static void FinishRequest(HTTP2Stream stream, FramesAsStreamView dataStream)
{
try
{
if (dataStream != null)
{
try
{
stream.response.AddData(dataStream);
}
finally
{
dataStream.Close();
}
}
stream.AssignedRequest.Timing.Add(TimingEventNames.Response_Received);
bool resendRequest;
HTTPConnectionStates proposedConnectionStates; // ignored
KeepAliveHeader keepAliveHeader = null; // ignored
ConnectionHelper.HandleResponse("HTTP2Stream", stream.AssignedRequest, out resendRequest, out proposedConnectionStates, ref keepAliveHeader, stream.Context, stream.AssignedRequest.Context);
if (resendRequest && !stream.AssignedRequest.IsCancellationRequested)
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(stream.AssignedRequest, RequestEvents.Resend));
else if (stream.AssignedRequest.State == HTTPRequestStates.Processing && !stream.AssignedRequest.IsCancellationRequested)
stream.AssignedRequest.State = HTTPRequestStates.Finished;
else
{
// Already set in HTTPRequest's Abort().
//if (stream.AssignedRequest.State == HTTPRequestStates.Processing && stream.AssignedRequest.IsCancellationRequested)
// stream.AssignedRequest.State = stream.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
}
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("HTTP2Stream", "FinishRequest", ex, stream.AssignedRequest.Context);
}
}
public void Removed()
{
if (this.uploadStreamInfo.Stream != null)
{
this.uploadStreamInfo.Stream.Dispose();
this.uploadStreamInfo = new HTTPRequest.UploadStreamInfo();
}
// 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;
HTTPManager.Logger.Information("HTTP2Stream", "Stream removed: " + this.Id.ToString(), this.Context, this.AssignedRequest.Context, this.parent.Context);
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: cc308fd8e0bd56844b6a3511b489602c
timeCreated: 1571210042
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,185 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2 && !BESTHTTP_DISABLE_WEBSOCKET
using System;
using System.Collections.Generic;
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.WebSocket;
namespace BestHTTP.Connections.HTTP2
{
public sealed class HTTP2WebSocketStream : HTTP2Stream
{
public override 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 &&
(this.overHTTP2.frames.Count > 0 || this.chunkQueue.Count > 0)); // we are in the middle of sending request data
}
}
public override TimeSpan NextInteraction => this.overHTTP2.GetNextInteraction();
private OverHTTP2 overHTTP2;
// local list of websocket header-data pairs
private List<KeyValuePair<BufferSegment, BufferSegment>> chunkQueue = new List<KeyValuePair<BufferSegment, BufferSegment>>();
public HTTP2WebSocketStream(uint id, HTTP2Handler parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder) : base(id, parentHandler, registry, hpackEncoder)
{
}
public override void Assign(HTTPRequest request)
{
base.Assign(request);
this.overHTTP2 = request.Tag as OverHTTP2;
this.overHTTP2.SetHTTP2Handler(this.parent);
}
protected override void ProcessIncomingDATAFrame(ref HTTP2FrameHeaderAndPayload frame, ref uint windowUpdate)
{
try
{
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open)
{
// ERROR!
return;
}
this.downloaded += frame.PayloadLength;
this.overHTTP2.OnReadThread(frame.Payload.AsBuffer((int)frame.PayloadOffset, (int)frame.PayloadLength));
// frame's buffer will be released later
frame.DontUseMemPool = true;
// Track received data, and if necessary(local window getting too low), send a window update frame
if (this.localWindow < frame.PayloadLength)
{
HTTPManager.Logger.Error(nameof(HTTP2WebSocketStream), string.Format("[{0}] Frame's PayloadLength ({1:N0}) is larger then local window ({2:N0}). Frame: {3}", this.Id, frame.PayloadLength, this.localWindow, frame), this.Context, this.AssignedRequest.Context, this.parent.Context);
}
else
this.localWindow -= frame.PayloadLength;
if ((frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0)
this.isEndSTRReceived = true;
if (this.isEndSTRReceived)
{
HTTPManager.Logger.Information(nameof(HTTP2WebSocketStream), string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded), this.Context, this.AssignedRequest.Context, this.parent.Context);
// create a short living thread to process the downloaded data:
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTP2Stream, FramesAsStreamView>(FinishRequest, this, this.dataView);
this.dataView = null;
if (this.State == HTTP2StreamStates.HalfClosedLocal)
this.State = HTTP2StreamStates.Closed;
else
this.State = HTTP2StreamStates.HalfClosedRemote;
}
if (this.isEndSTRReceived || this.localWindow <= this.windowUpdateThreshold)
windowUpdate += this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] - this.localWindow - windowUpdate;
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(HTTP2WebSocketStream), nameof(ProcessIncomingDATAFrame), ex, this.parent.Context);
}
}
protected override void ProcessOpenState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
try
{
// remote Window can be negative! See https://httpwg.org/specs/rfc7540.html#InitialWindowSize
if (this.remoteWindow <= 0)
{
HTTPManager.Logger.Information(nameof(HTTP2WebSocketStream), string.Format("[{0}] Skipping data sending as remote Window is {1}!", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
return;
}
this.overHTTP2.PreReadCallback();
Int64 maxFragmentSize = Math.Min(BestHTTP.WebSocket.WebSocket.MaxFragmentSize, this.settings.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE]);
Int64 maxFrameSize = Math.Min(maxFragmentSize, this.remoteWindow);
if (chunkQueue.Count == 0)
{
if (this.overHTTP2.frames.TryDequeue(out var frame))
{
this.overHTTP2._bufferedAmount -= (int)frame.Data.Count;
frame.WriteTo((header, data) => chunkQueue.Add(new KeyValuePair<BufferSegment, BufferSegment>(header, data)), (uint)maxFragmentSize, false, this.Context);
}
}
while (this.remoteWindow >= 6 && chunkQueue.Count > 0)
{
var kvp = chunkQueue[0];
BufferSegment header = kvp.Key;
BufferSegment data = kvp.Value;
int minBytes = header.Count;
int maxBytes = minBytes + data.Count;
// remote window is less than the minimum we have to send, or
// the frame has data but we have space only to send the websocket header
if (this.remoteWindow < minBytes || (maxBytes > minBytes && this.remoteWindow == minBytes))
return;
HTTP2FrameHeaderAndPayload headerFrame = new HTTP2FrameHeaderAndPayload();
headerFrame.Type = HTTP2FrameTypes.DATA;
headerFrame.StreamId = this.Id;
headerFrame.PayloadOffset = (uint)header.Offset;
headerFrame.PayloadLength = (uint)header.Count;
headerFrame.Payload = header.Data;
headerFrame.DontUseMemPool = false;
if (data.Count > 0)
{
HTTP2FrameHeaderAndPayload dataFrame = new HTTP2FrameHeaderAndPayload();
dataFrame.Type = HTTP2FrameTypes.DATA;
dataFrame.StreamId = this.Id;
var buff = data.Slice(data.Offset, (int)Math.Min(data.Count, maxFrameSize));
dataFrame.PayloadOffset = (uint)buff.Offset;
dataFrame.PayloadLength = (uint)buff.Count;
dataFrame.Payload = buff.Data;
data = data.Slice(buff.Offset + buff.Count);
if (data.Count == 0)
chunkQueue.RemoveAt(0);
else
chunkQueue[0] = new KeyValuePair<BufferSegment, BufferSegment>(header, data);
// release the buffer only with the final frame and with the final frame's last data chunk
bool isLast = (header.Data[header.Offset] & 0x80) != 0 /*&& chunkQueue.Count == 0*/;
dataFrame.DontUseMemPool = !isLast;
this.outgoing.Enqueue(headerFrame);
this.outgoing.Enqueue(dataFrame);
}
else
{
this.outgoing.Enqueue(headerFrame);
chunkQueue.RemoveAt(0);
}
}
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(HTTP2WebSocketStream), nameof(ProcessOpenState), ex, this.parent.Context);
}
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,199 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using System;
using System.Collections.Generic;
namespace BestHTTP.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 = PlatformSupport.Text.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 PlatformSupport.Text.StringBuilderPool.ReleaseAndGrab(sb);
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5b73749356303be43b6d9debd22d7937
timeCreated: 1571210041
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2b07c0aa55ea00940b8c8bae8d5d9c34
timeCreated: 1571210040
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: