mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 10:46:17 +00:00
提交Unity 联机Pro
This commit is contained in:
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53d7db68937661d4eb9c0fe989b9f46c
|
||||
timeCreated: 1571210041
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 591ec412e5c6f2a42b591c9c02921d33
|
||||
timeCreated: 1571210041
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b9891b070f01ec458dd74bd4e91cf75
|
||||
timeCreated: 1571210041
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a3c905b246be424b8afe85c7e0a8526
|
||||
timeCreated: 1571210041
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06f965acc306c134093ce2a816832155
|
||||
timeCreated: 1571210040
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21ffa29fa36f0aa48bcf898a72ab5ecf
|
||||
timeCreated: 1571210040
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 218daa3af21407f40ac4304f80a79b59
|
||||
timeCreated: 1571210040
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8916e7811100a643b66249f17136fcc
|
||||
timeCreated: 1571210041
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38a4e3fed00ab4c4181c1a7ff3dcb858
|
||||
timeCreated: 1571210041
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc308fd8e0bd56844b6a3511b489602c
|
||||
timeCreated: 1571210042
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70dfdb0b300f8494cbfe6474037178d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b73749356303be43b6d9debd22d7937
|
||||
timeCreated: 1571210041
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b07c0aa55ea00940b8c8bae8d5d9c34
|
||||
timeCreated: 1571210040
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user