add all
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using System;
|
||||
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
public static class BufferHelper
|
||||
{
|
||||
public static void SetUInt16(byte[] buffer, int offset, UInt16 value)
|
||||
{
|
||||
buffer[offset + 1] = (byte)(value);
|
||||
buffer[offset + 0] = (byte)(value >> 8);
|
||||
}
|
||||
|
||||
public static void SetUInt24(byte[] buffer, int offset, UInt32 value)
|
||||
{
|
||||
buffer[offset + 2] = (byte)(value);
|
||||
buffer[offset + 1] = (byte)(value >> 8);
|
||||
buffer[offset + 0] = (byte)(value >> 16);
|
||||
}
|
||||
|
||||
public static void SetUInt24(Span<byte> buffer, int offset, UInt32 value)
|
||||
{
|
||||
buffer[offset + 2] = (byte)(value);
|
||||
buffer[offset + 1] = (byte)(value >> 8);
|
||||
buffer[offset + 0] = (byte)(value >> 16);
|
||||
}
|
||||
|
||||
public static void SetUInt31(byte[] buffer, int offset, UInt32 value)
|
||||
{
|
||||
buffer[offset + 3] = (byte)(value);
|
||||
buffer[offset + 2] = (byte)(value >> 8);
|
||||
buffer[offset + 1] = (byte)(value >> 16);
|
||||
buffer[offset + 0] = (byte)((value >> 24) & 0x7F);
|
||||
}
|
||||
|
||||
public static void SetUInt31(Span<byte> buffer, int offset, UInt32 value)
|
||||
{
|
||||
buffer[offset + 3] = (byte)(value);
|
||||
buffer[offset + 2] = (byte)(value >> 8);
|
||||
buffer[offset + 1] = (byte)(value >> 16);
|
||||
buffer[offset + 0] = (byte)((value >> 24) & 0x7F);
|
||||
}
|
||||
|
||||
public static void SetUInt32(byte[] buffer, int offset, UInt32 value)
|
||||
{
|
||||
buffer[offset + 3] = (byte)(value);
|
||||
buffer[offset + 2] = (byte)(value >> 8);
|
||||
buffer[offset + 1] = (byte)(value >> 16);
|
||||
buffer[offset + 0] = (byte)(value >> 24);
|
||||
}
|
||||
|
||||
public static void SetLong(byte[] buffer, int offset, long value)
|
||||
{
|
||||
buffer[offset + 7] = (byte)(value);
|
||||
buffer[offset + 6] = (byte)(value >> 8);
|
||||
buffer[offset + 5] = (byte)(value >> 16);
|
||||
buffer[offset + 4] = (byte)(value >> 24);
|
||||
buffer[offset + 3] = (byte)(value >> 32);
|
||||
buffer[offset + 2] = (byte)(value >> 40);
|
||||
buffer[offset + 1] = (byte)(value >> 48);
|
||||
buffer[offset + 0] = (byte)(value >> 56);
|
||||
}
|
||||
|
||||
public static byte SetBit(byte value, byte bitIdx, bool bitValue)
|
||||
{
|
||||
// bitIdx: 01234567
|
||||
return SetBit(value, bitIdx, Convert.ToByte(bitValue));
|
||||
}
|
||||
|
||||
public static byte SetBit(byte value, byte bitIdx, byte bitValue)
|
||||
{
|
||||
// bitIdx: 01234567
|
||||
|
||||
return (byte)((value ^ (value & (0x80 >> bitIdx))) | bitValue << (7 - bitIdx));
|
||||
}
|
||||
|
||||
public static byte ReadBit(byte value, byte bitIdx)
|
||||
{
|
||||
// bitIdx: 01234567
|
||||
byte mask = (byte)(0x80 >> bitIdx);
|
||||
|
||||
return (byte)((value & mask) >> (7 - bitIdx));
|
||||
}
|
||||
|
||||
public static byte ReadValue(byte value, byte fromBit, byte toBit)
|
||||
{
|
||||
// bitIdx: 01234567
|
||||
|
||||
byte result = 0;
|
||||
short idx = toBit;
|
||||
|
||||
while (idx >= fromBit)
|
||||
{
|
||||
result += (byte)(ReadBit(value, (byte)idx) << (toBit - idx));
|
||||
idx--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static UInt16 ReadUInt16(byte[] buffer, int offset)
|
||||
{
|
||||
return (UInt16)(buffer[offset + 1] | buffer[offset] << 8);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt24(byte[] buffer, int offset)
|
||||
{
|
||||
return (UInt32)(buffer[offset + 2] |
|
||||
buffer[offset + 1] << 8 |
|
||||
buffer[offset + 0] << 16
|
||||
);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt31(byte[] buffer, int offset)
|
||||
{
|
||||
return (UInt32)(buffer[offset + 3] |
|
||||
buffer[offset + 2] << 8 |
|
||||
buffer[offset + 1] << 16 |
|
||||
(buffer[offset] & 0x7F) << 24
|
||||
);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt31(BufferSegment buffer, int offset)
|
||||
{
|
||||
return (UInt32)( buffer.Data[offset + 3] |
|
||||
buffer.Data[offset + 2] << 8 |
|
||||
buffer.Data[offset + 1] << 16 |
|
||||
(buffer.Data[offset] & 0x7F) << 24
|
||||
);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt32(byte[] buffer, int offset)
|
||||
{
|
||||
return (UInt32)(buffer[offset + 3] |
|
||||
buffer[offset + 2] << 8 |
|
||||
buffer[offset + 1] << 16 |
|
||||
buffer[offset + 0] << 24
|
||||
);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt32(BufferSegment buffer, int offset)
|
||||
{
|
||||
return (UInt32)(buffer.Data[offset + 3] |
|
||||
buffer.Data[offset + 2] << 8 |
|
||||
buffer.Data[offset + 1] << 16 |
|
||||
buffer.Data[offset + 0] << 24
|
||||
);
|
||||
}
|
||||
|
||||
public static long ReadLong(BufferSegment buffer, int offset)
|
||||
{
|
||||
return (long)buffer.Data[offset + 7] |
|
||||
(long)buffer.Data[offset + 6] << 8 |
|
||||
(long)buffer.Data[offset + 5] << 16 |
|
||||
(long)buffer.Data[offset + 4] << 24 |
|
||||
(long)buffer.Data[offset + 3] << 32 |
|
||||
(long)buffer.Data[offset + 2] << 40 |
|
||||
(long)buffer.Data[offset + 1] << 48 |
|
||||
(long)buffer.Data[offset + 0] << 56;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ddab2aecdc1bd94688535c6d3532949
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,203 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.PlatformSupport.Text;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
public interface IFrameDataView : IDisposable
|
||||
{
|
||||
long Length { get; }
|
||||
long Position { get; }
|
||||
|
||||
void AddFrame(HTTP2FrameHeaderAndPayload frame);
|
||||
int ReadByte();
|
||||
int Read(byte[] buffer, int offset, int count);
|
||||
}
|
||||
|
||||
public abstract class CommonFrameView : IFrameDataView
|
||||
{
|
||||
public long Length { get; protected set; }
|
||||
public long Position { get; protected set; }
|
||||
|
||||
protected List<HTTP2FrameHeaderAndPayload> frames = new List<HTTP2FrameHeaderAndPayload>();
|
||||
protected int currentFrameIdx = -1;
|
||||
protected byte[] data;
|
||||
protected int dataOffset;
|
||||
protected int maxOffset;
|
||||
|
||||
public abstract void AddFrame(HTTP2FrameHeaderAndPayload frame);
|
||||
protected abstract long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame);
|
||||
|
||||
public virtual int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
|
||||
return -1;
|
||||
|
||||
int readCount = 0;
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
long copyCount = Math.Min(count, this.maxOffset - this.dataOffset);
|
||||
|
||||
Array.Copy(this.data, this.dataOffset, buffer, offset + readCount, copyCount);
|
||||
|
||||
count -= (int)copyCount;
|
||||
readCount += (int)copyCount;
|
||||
|
||||
this.dataOffset += (int)copyCount;
|
||||
this.Position += copyCount;
|
||||
|
||||
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
|
||||
break;
|
||||
}
|
||||
|
||||
return readCount;
|
||||
}
|
||||
|
||||
public virtual int ReadByte()
|
||||
{
|
||||
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
|
||||
return -1;
|
||||
|
||||
byte data = this.data[this.dataOffset];
|
||||
this.dataOffset++;
|
||||
this.Position++;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected abstract bool AdvanceFrame();
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
for (int i = 0; i < this.frames.Count; ++i)
|
||||
//if (this.frames[i].Payload != null && !this.frames[i].DontUseMemPool)
|
||||
BufferPool.Release(this.frames[i].Payload);
|
||||
this.frames.Clear();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = StringBuilderPool.Get(this.frames.Count + 2);
|
||||
sb.Append("[CommonFrameView ");
|
||||
|
||||
for (int i = 0; i < this.frames.Count; ++i) {
|
||||
sb.AppendFormat("{0} Payload: {1}\n", this.frames[i], this.frames[i].PayloadAsHex());
|
||||
}
|
||||
|
||||
sb.Append("]");
|
||||
|
||||
return StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HeaderFrameView : CommonFrameView
|
||||
{
|
||||
public override void AddFrame(HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
if (frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
|
||||
throw new ArgumentException("HeaderFrameView - Unexpected frame type: " + frame.Type);
|
||||
|
||||
this.frames.Add(frame);
|
||||
this.Length += CalculateDataLengthForFrame(frame);
|
||||
|
||||
if (this.currentFrameIdx == -1)
|
||||
AdvanceFrame();
|
||||
}
|
||||
|
||||
protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
switch (frame.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.HEADERS:
|
||||
return HTTP2FrameHelper.ReadHeadersFrame(frame).HeaderBlockFragment.Count;
|
||||
|
||||
case HTTP2FrameTypes.CONTINUATION:
|
||||
return frame.Payload.Count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override bool AdvanceFrame()
|
||||
{
|
||||
if (this.currentFrameIdx >= this.frames.Count - 1)
|
||||
return false;
|
||||
|
||||
this.currentFrameIdx++;
|
||||
HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx];
|
||||
|
||||
this.data = frame.Payload.Data;
|
||||
|
||||
switch (frame.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.HEADERS:
|
||||
var header = HTTP2FrameHelper.ReadHeadersFrame(frame);
|
||||
this.dataOffset = header.HeaderBlockFragment.Offset;
|
||||
this.maxOffset = this.dataOffset + header.HeaderBlockFragment.Count;
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.CONTINUATION:
|
||||
this.dataOffset = 0;
|
||||
this.maxOffset = frame.Payload.Count;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FramesAsStreamView : Stream
|
||||
{
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanSeek { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
public override long Length { get { return this.view.Length; } }
|
||||
public override long Position { get { return this.view.Position; } set { throw new NotSupportedException(); } }
|
||||
|
||||
private IFrameDataView view;
|
||||
|
||||
public FramesAsStreamView(IFrameDataView view)
|
||||
{
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public void AddFrame(HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
this.view.AddFrame(frame);
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
return this.view.ReadByte();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return this.view.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
this.view.Dispose();
|
||||
}
|
||||
|
||||
public override void Flush() {}
|
||||
public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); }
|
||||
public override void SetLength(long value) { throw new NotImplementedException(); }
|
||||
public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.view.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3117e531a50d6f74faedada4b320cebf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,815 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using Best.HTTP.Shared;
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
public sealed class HPACKEncoder
|
||||
{
|
||||
private HTTP2SettingsManager settingsRegistry;
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#encoding.context
|
||||
// When used for bidirectional communication, such as in HTTP, the encoding and decoding dynamic tables
|
||||
// maintained by an endpoint are completely independent, i.e., the request and response dynamic tables are separate.
|
||||
private HeaderTable requestTable;
|
||||
private HeaderTable responseTable;
|
||||
|
||||
private LoggingContext _context;
|
||||
|
||||
public HPACKEncoder(LoggingContext context, HTTP2SettingsManager registry)
|
||||
{
|
||||
this._context = context;
|
||||
this.settingsRegistry = registry;
|
||||
|
||||
// I'm unsure what settings (local or remote) we should use for these two tables!
|
||||
this.requestTable = new HeaderTable(this.settingsRegistry.MySettings);
|
||||
this.responseTable = new HeaderTable(this.settingsRegistry.RemoteSettings);
|
||||
}
|
||||
|
||||
public void Encode(HTTP2Stream context, HTTPRequest request, Queue<HTTP2FrameHeaderAndPayload> to, UInt32 streamId)
|
||||
{
|
||||
// Add usage of SETTINGS_MAX_HEADER_LIST_SIZE to be able to create a header and one or more continuation fragments
|
||||
// (https://httpwg.org/specs/rfc7540.html#SettingValues)
|
||||
|
||||
using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream(request.UploadSettings.UploadChunkSize))
|
||||
{
|
||||
request.Prepare();
|
||||
|
||||
// add :method
|
||||
switch (request.MethodType)
|
||||
{
|
||||
case HTTPMethods.Get: WriteIndexedHeaderField(bufferStream, 2); break;
|
||||
case HTTPMethods.Post: WriteIndexedHeaderField(bufferStream, 3); break;
|
||||
default:
|
||||
WriteLiteralHeaderFieldWithoutIndexing_IndexedName(bufferStream, 2, HTTPRequest.MethodNames[(int)request.MethodType]);
|
||||
break;
|
||||
}
|
||||
|
||||
// add :authority
|
||||
WriteLiteralHeaderFieldWithoutIndexing_IndexedName(bufferStream, 1, request.CurrentUri.Authority);
|
||||
|
||||
// add :scheme
|
||||
WriteIndexedHeaderField(bufferStream, 7);
|
||||
|
||||
// add :path
|
||||
WriteLiteralHeaderFieldWithoutIndexing_IndexedName(bufferStream, 4, request.CurrentUri.PathAndQuery);
|
||||
|
||||
//bool hasBody = false;
|
||||
|
||||
// add other, regular headers
|
||||
request.EnumerateHeaders((header, values) =>
|
||||
{
|
||||
if (header.Equals("connection", StringComparison.OrdinalIgnoreCase) ||
|
||||
(header.Equals("te", StringComparison.OrdinalIgnoreCase) && !values.Contains("trailers") && values.Count <= 1) ||
|
||||
header.Equals("host", StringComparison.OrdinalIgnoreCase) ||
|
||||
header.Equals("keep-alive", StringComparison.OrdinalIgnoreCase) ||
|
||||
header.StartsWith("proxy-", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
if (header.Equals("content-length", StringComparison.OrdinalIgnoreCase) && values.Count == 1 && int.TryParse(values[0], out int contentLength) && contentLength <= 0 && !context.ParentHandler.ConnectionSettings.ForceSendContentLengthHeader)
|
||||
return;
|
||||
|
||||
//if (!hasBody)
|
||||
// hasBody = header.Equals("content-length", StringComparison.OrdinalIgnoreCase) && int.Parse(values[0]) > 0;
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#HttpSequence
|
||||
// The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
|
||||
if (header.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// error!
|
||||
return;
|
||||
}
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#HttpHeaders
|
||||
// Just as in HTTP/1.x, header field names are strings of ASCII characters that are compared in a case-insensitive fashion.
|
||||
// However, header field names MUST be converted to lowercase prior to their encoding in HTTP/2.
|
||||
// A request or response containing uppercase header field names MUST be treated as malformed
|
||||
if (header.Any(Char.IsUpper))
|
||||
header = header.ToLowerInvariant();
|
||||
|
||||
for (int i = 0; i < values.Count; ++i)
|
||||
{
|
||||
WriteHeader(bufferStream, header, values[i]);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] - Encode - Header({1}/{2}): '{3}': '{4}'", context.Id, i + 1, values.Count, header, values[i]), this._context);
|
||||
}
|
||||
}, true);
|
||||
|
||||
CreateHeaderFrames(to,
|
||||
streamId,
|
||||
bufferStream.ToArray(true, this._context),
|
||||
(UInt32)bufferStream.Length,
|
||||
request.UploadSettings.UploadStream != null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Decode(HTTP2Stream context, Stream stream, List<KeyValuePair<string, string>> to)
|
||||
{
|
||||
int headerType = stream.ReadByte();
|
||||
while (headerType != -1)
|
||||
{
|
||||
byte firstDataByte = (byte)headerType;
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#indexed.header.representation
|
||||
if (BufferHelper.ReadBit(firstDataByte, 0) == 1)
|
||||
{
|
||||
var header = ReadIndexedHeader(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - IndexedHeader: {1}", context.Id, header.ToString()), this._context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
else if (BufferHelper.ReadValue(firstDataByte, 0, 1) == 1)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
if (BufferHelper.ReadValue(firstDataByte, 2, 7) == 0)
|
||||
{
|
||||
// Literal Header Field with Incremental Indexing — New Name
|
||||
var header = ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_NewName: {1}", context.Id, header.ToString()), this._context);
|
||||
|
||||
this.responseTable.Add(header);
|
||||
to.Add(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Literal Header Field with Incremental Indexing — Indexed Name
|
||||
var header = ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_IndexedName: {1}", context.Id, header.ToString()), this._context);
|
||||
|
||||
this.responseTable.Add(header);
|
||||
to.Add(header);
|
||||
}
|
||||
}
|
||||
else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 0)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
|
||||
{
|
||||
// Literal Header Field without Indexing — New Name
|
||||
var header = ReadLiteralHeaderFieldwithoutIndexing_NewName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_NewName: {1}", context.Id, header.ToString()), this._context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Literal Header Field without Indexing — Indexed Name
|
||||
var header = ReadLiteralHeaderFieldwithoutIndexing_IndexedName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_IndexedName: {1}", context.Id, header.ToString()), this._context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
}
|
||||
else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 1)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
|
||||
{
|
||||
// Literal Header Field Never Indexed — New Name
|
||||
var header = ReadLiteralHeaderFieldNeverIndexed_NewName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_NewName: {1}", context.Id, header.ToString()), this._context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Literal Header Field Never Indexed — Indexed Name
|
||||
var header = ReadLiteralHeaderFieldNeverIndexed_IndexedName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_IndexedName: {1}", context.Id, header.ToString()), this._context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
}
|
||||
else if (BufferHelper.ReadValue(firstDataByte, 0, 2) == 1)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#encoding.context.update
|
||||
|
||||
UInt32 newMaxSize = DecodeInteger(5, firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - Dynamic Table Size Update: {1}", context.Id, newMaxSize), this._context);
|
||||
|
||||
//this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE] = (UInt16)newMaxSize;
|
||||
this.responseTable.MaxDynamicTableSize = (UInt16)newMaxSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ERROR
|
||||
}
|
||||
|
||||
headerType = stream.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadIndexedHeader(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#indexed.header.representation
|
||||
|
||||
UInt32 index = DecodeInteger(7, firstByte, stream);
|
||||
return this.responseTable.GetHeader(index);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
UInt32 keyIndex = DecodeInteger(6, firstByte, stream);
|
||||
|
||||
string header = this.responseTable.GetKey(keyIndex);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
string header = DecodeString(stream);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldwithoutIndexing_IndexedName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
UInt32 index = DecodeInteger(4, firstByte, stream);
|
||||
string header = this.responseTable.GetKey(index);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldwithoutIndexing_NewName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
string header = DecodeString(stream);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldNeverIndexed_IndexedName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
UInt32 index = DecodeInteger(4, firstByte, stream);
|
||||
string header = this.responseTable.GetKey(index);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldNeverIndexed_NewName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
string header = DecodeString(stream);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private string DecodeString(Stream stream)
|
||||
{
|
||||
byte start = (byte)stream.ReadByte();
|
||||
bool rawString = BufferHelper.ReadBit(start, 0) == 0;
|
||||
UInt32 stringLength = DecodeInteger(7, start, stream);
|
||||
|
||||
if (stringLength == 0)
|
||||
return string.Empty;
|
||||
|
||||
if (rawString)
|
||||
{
|
||||
byte[] buffer = BufferPool.Get(stringLength, true);
|
||||
|
||||
stream.Read(buffer, 0, (int)stringLength);
|
||||
|
||||
var result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)stringLength);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var node = HuffmanEncoder.GetRoot();
|
||||
byte currentByte = (byte)stream.ReadByte();
|
||||
byte bitIdx = 0; // 0..7
|
||||
|
||||
using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream((int)(stringLength * 1.5f)))
|
||||
{
|
||||
do
|
||||
{
|
||||
byte bitValue = BufferHelper.ReadBit(currentByte, bitIdx);
|
||||
|
||||
if (++bitIdx > 7)
|
||||
{
|
||||
stringLength--;
|
||||
|
||||
if (stringLength > 0)
|
||||
{
|
||||
bitIdx = 0;
|
||||
currentByte = (byte)stream.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
node = HuffmanEncoder.GetNext(node, bitValue);
|
||||
|
||||
if (node.Value != 0)
|
||||
{
|
||||
if (node.Value != HuffmanEncoder.EOS)
|
||||
bufferStream.WriteByte((byte)node.Value);
|
||||
|
||||
node = HuffmanEncoder.GetRoot();
|
||||
}
|
||||
} while (stringLength > 0);
|
||||
|
||||
byte[] buffer = bufferStream.GetBuffer();
|
||||
|
||||
string result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)bufferStream.Length);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateHeaderFrames(Queue<HTTP2FrameHeaderAndPayload> to, UInt32 streamId, byte[] dataToSend, UInt32 payloadLength, bool hasBody)
|
||||
{
|
||||
UInt32 maxFrameSize = this.settingsRegistry.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE];
|
||||
|
||||
// Only one headers frame
|
||||
if (payloadLength <= maxFrameSize)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
|
||||
frameHeader.Type = HTTP2FrameTypes.HEADERS;
|
||||
frameHeader.StreamId = streamId;
|
||||
frameHeader.Flags = (byte)(HTTP2HeadersFlags.END_HEADERS);
|
||||
|
||||
if (!hasBody)
|
||||
frameHeader.Flags |= (byte)(HTTP2HeadersFlags.END_STREAM);
|
||||
|
||||
frameHeader.Payload = dataToSend.AsBuffer((int)payloadLength);
|
||||
|
||||
to.Enqueue(frameHeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
|
||||
frameHeader.Type = HTTP2FrameTypes.HEADERS;
|
||||
frameHeader.StreamId = streamId;
|
||||
|
||||
frameHeader.Payload = dataToSend.AsBuffer((int)maxFrameSize);
|
||||
frameHeader.DontUseMemPool = true;
|
||||
|
||||
if (!hasBody)
|
||||
frameHeader.Flags = (byte)(HTTP2HeadersFlags.END_STREAM);
|
||||
|
||||
to.Enqueue(frameHeader);
|
||||
|
||||
UInt32 offset = maxFrameSize;
|
||||
while (offset < payloadLength)
|
||||
{
|
||||
frameHeader = new HTTP2FrameHeaderAndPayload();
|
||||
frameHeader.Type = HTTP2FrameTypes.CONTINUATION;
|
||||
frameHeader.StreamId = streamId;
|
||||
|
||||
frameHeader.Payload = dataToSend.AsBuffer((int)offset, (int)maxFrameSize);
|
||||
|
||||
offset += maxFrameSize;
|
||||
|
||||
if (offset >= payloadLength)
|
||||
{
|
||||
frameHeader.Flags = (byte)(HTTP2ContinuationFlags.END_HEADERS);
|
||||
// last sent continuation fragment will release back the payload buffer
|
||||
frameHeader.DontUseMemPool = false;
|
||||
}
|
||||
else
|
||||
frameHeader.DontUseMemPool = true;
|
||||
|
||||
to.Enqueue(frameHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteHeader(Stream stream, string header, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#header.representation
|
||||
|
||||
KeyValuePair<UInt32, UInt32> index = this.requestTable.GetIndex(header, value);
|
||||
|
||||
if (index.Key == 0 && index.Value == 0)
|
||||
{
|
||||
WriteLiteralHeaderFieldWithIncrementalIndexing_NewName(stream, header, value);
|
||||
this.requestTable.Add(new KeyValuePair<string, string>(header, value));
|
||||
}
|
||||
else if (index.Key != 0 && index.Value == 0)
|
||||
{
|
||||
WriteLiteralHeaderFieldWithIncrementalIndexing_IndexedName(stream, index.Key, value);
|
||||
this.requestTable.Add(new KeyValuePair<string, string>(header, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteIndexedHeaderField(stream, index.Key);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteIndexedHeaderField(Stream stream, UInt32 index)
|
||||
{
|
||||
byte requiredBytes = RequiredBytesToEncodeInteger(index, 7);
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[0] = 0x80;
|
||||
EncodeInteger(index, 7, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldWithIncrementalIndexing_IndexedName(Stream stream, UInt32 index, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 6) +
|
||||
RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[0] = 0x40;
|
||||
EncodeInteger(index, 6, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldWithIncrementalIndexing_NewName(Stream stream, string header, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[offset++] = 0x40;
|
||||
EncodeString(header, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldWithoutIndexing_IndexedName(Stream stream, UInt32 index, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 4) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[0] = 0;
|
||||
EncodeInteger(index, 4, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldWithoutIndexing_NewName(Stream stream, string header, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[offset++] = 0;
|
||||
EncodeString(header, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldNeverIndexed_IndexedName(Stream stream, UInt32 index, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 4) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[0] = 0x10;
|
||||
EncodeInteger(index, 4, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldNeverIndexed_NewName(Stream stream, string header, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[offset++] = 0x10;
|
||||
EncodeString(header, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteDynamicTableSizeUpdate(Stream stream, UInt16 maxSize)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#encoding.context.update
|
||||
|
||||
UInt32 requiredBytes = RequiredBytesToEncodeInteger(maxSize, 5);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[offset] = 0x20;
|
||||
EncodeInteger(maxSize, 5, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static UInt32 RequiredBytesToEncodeString(string str)
|
||||
{
|
||||
uint requiredBytesForRawStr = RequiredBytesToEncodeRawString(str);
|
||||
uint requiredBytesForHuffman = RequiredBytesToEncodeStringWithHuffman(str);
|
||||
requiredBytesForHuffman += RequiredBytesToEncodeInteger(requiredBytesForHuffman, 7);
|
||||
|
||||
return Math.Min(requiredBytesForRawStr, requiredBytesForHuffman);
|
||||
}
|
||||
|
||||
private static void EncodeString(string str, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
uint requiredBytesForRawStr = RequiredBytesToEncodeRawString(str);
|
||||
uint requiredBytesForHuffman = RequiredBytesToEncodeStringWithHuffman(str);
|
||||
|
||||
// if using huffman encoding would produce the same length, we choose raw encoding instead as it requires
|
||||
// less CPU cicles
|
||||
if (requiredBytesForRawStr <= requiredBytesForHuffman + RequiredBytesToEncodeInteger(requiredBytesForHuffman, 7))
|
||||
EncodeRawStringTo(str, buffer, ref offset);
|
||||
else
|
||||
EncodeStringWithHuffman(str, requiredBytesForHuffman, buffer, ref offset);
|
||||
}
|
||||
|
||||
// This calculates only the length of the compressed string,
|
||||
// additional header length must be calculated using the value returned by this function
|
||||
private static UInt32 RequiredBytesToEncodeStringWithHuffman(string str)
|
||||
{
|
||||
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
|
||||
byte[] strBytes = BufferPool.Get(requiredBytesForStr, true);
|
||||
|
||||
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, strBytes, 0);
|
||||
|
||||
UInt32 requiredBits = 0;
|
||||
|
||||
for (int i = 0; i < requiredBytesForStr; ++i)
|
||||
requiredBits += HuffmanEncoder.GetEntryForCodePoint(strBytes[i]).Bits;
|
||||
|
||||
BufferPool.Release(strBytes);
|
||||
|
||||
return (UInt32)((requiredBits / 8) + ((requiredBits % 8) == 0 ? 0 : 1));
|
||||
}
|
||||
|
||||
private static void EncodeStringWithHuffman(string str, UInt32 encodedLength, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
|
||||
byte[] strBytes = BufferPool.Get(requiredBytesForStr, true);
|
||||
|
||||
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, strBytes, 0);
|
||||
|
||||
// 0. bit: huffman flag
|
||||
buffer[offset] = 0x80;
|
||||
|
||||
// 1..7+ bit: length
|
||||
EncodeInteger(encodedLength, 7, buffer, ref offset);
|
||||
|
||||
byte bufferBitIdx = 0;
|
||||
|
||||
for (int i = 0; i < requiredBytesForStr; ++i)
|
||||
AddCodePointToBuffer(HuffmanEncoder.GetEntryForCodePoint(strBytes[i]), buffer, ref offset, ref bufferBitIdx);
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#string.literal.representation
|
||||
// As the Huffman-encoded data doesn't always end at an octet boundary, some padding is inserted after it,
|
||||
// up to the next octet boundary. To prevent this padding from being misinterpreted as part of the string literal,
|
||||
// the most significant bits of the code corresponding to the EOS (end-of-string) symbol are used.
|
||||
if (bufferBitIdx != 0)
|
||||
AddCodePointToBuffer(HuffmanEncoder.GetEntryForCodePoint(256), buffer, ref offset, ref bufferBitIdx, true);
|
||||
|
||||
BufferPool.Release(strBytes);
|
||||
}
|
||||
|
||||
private static void AddCodePointToBuffer(HuffmanTableEntry code, byte[] buffer, ref UInt32 offset, ref byte bufferBitIdx, bool finishOnBoundary = false)
|
||||
{
|
||||
for (byte codeBitIdx = 1; codeBitIdx <= code.Bits; ++codeBitIdx)
|
||||
{
|
||||
byte bit = code.GetBitAtIdx(codeBitIdx);
|
||||
buffer[offset] = BufferHelper.SetBit(buffer[offset], bufferBitIdx, bit);
|
||||
|
||||
// octet boundary reached, proceed to the next octet
|
||||
if (++bufferBitIdx == 8)
|
||||
{
|
||||
if (++offset < buffer.Length)
|
||||
buffer[offset] = 0;
|
||||
|
||||
if (finishOnBoundary)
|
||||
return;
|
||||
|
||||
bufferBitIdx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static UInt32 RequiredBytesToEncodeRawString(string str)
|
||||
{
|
||||
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
|
||||
int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);
|
||||
|
||||
return (UInt32)(requiredBytesForStr + requiredBytesForLengthPrefix);
|
||||
}
|
||||
|
||||
// This method encodes a string without huffman encoding
|
||||
private static void EncodeRawStringTo(string str, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
uint requiredBytesForStr = (uint)System.Text.Encoding.UTF8.GetByteCount(str);
|
||||
int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);
|
||||
|
||||
UInt32 originalOffset = offset;
|
||||
buffer[offset] = 0;
|
||||
EncodeInteger(requiredBytesForStr, 7, buffer, ref offset);
|
||||
|
||||
// Zero out the huffman flag
|
||||
buffer[originalOffset] = BufferHelper.SetBit(buffer[originalOffset], 0, false);
|
||||
|
||||
if (offset != originalOffset + requiredBytesForLengthPrefix)
|
||||
throw new Exception(string.Format("offset({0}) != originalOffset({1}) + requiredBytesForLengthPrefix({1})", offset, originalOffset, requiredBytesForLengthPrefix));
|
||||
|
||||
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, (int)offset);
|
||||
offset += requiredBytesForStr;
|
||||
}
|
||||
|
||||
private static byte RequiredBytesToEncodeInteger(UInt32 value, byte N)
|
||||
{
|
||||
UInt32 maxValue = (1u << N) - 1;
|
||||
byte count = 0;
|
||||
|
||||
// If the integer value is small enough, i.e., strictly less than 2^N-1, it is encoded within the N-bit prefix.
|
||||
if (value < maxValue)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, all the bits of the prefix are set to 1, and the value, decreased by 2^N-1
|
||||
count++;
|
||||
value -= maxValue;
|
||||
|
||||
while (value >= 0x80)
|
||||
{
|
||||
// The most significant bit of each octet is used as a continuation flag: its value is set to 1 except for the last octet in the list.
|
||||
count++;
|
||||
value = value / 0x80;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#integer.representation
|
||||
private static void EncodeInteger(UInt32 value, byte N, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
// 2^N - 1
|
||||
UInt32 maxValue = (1u << N) - 1;
|
||||
|
||||
// If the integer value is small enough, i.e., strictly less than 2^N-1, it is encoded within the N-bit prefix.
|
||||
if (value < maxValue)
|
||||
{
|
||||
buffer[offset++] |= (byte)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, all the bits of the prefix are set to 1, and the value, decreased by 2^N-1
|
||||
buffer[offset++] |= (byte)(0xFF >> (8 - N));
|
||||
value -= maxValue;
|
||||
|
||||
while (value >= 0x80)
|
||||
{
|
||||
// The most significant bit of each octet is used as a continuation flag: its value is set to 1 except for the last octet in the list.
|
||||
buffer[offset++] = (byte)(0x80 | (0x7F & value));
|
||||
value = value / 0x80;
|
||||
}
|
||||
|
||||
buffer[offset++] = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#integer.representation
|
||||
private static UInt32 DecodeInteger(byte N, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
// The starting value is the value behind the mask of the N bits
|
||||
UInt32 value = (UInt32)(buffer[offset++] & (byte)(0xFF >> (8 - N)));
|
||||
|
||||
// All N bits are 1s ? If so, we have at least one another byte to decode
|
||||
if (value == (1u << N) - 1)
|
||||
{
|
||||
byte shift = 0;
|
||||
|
||||
do
|
||||
{
|
||||
// The most significant bit is a continuation flag, so we have to mask it out
|
||||
value += (UInt32)((buffer[offset] & 0x7F) << shift);
|
||||
shift += 7;
|
||||
} while ((buffer[offset++] & 0x80) == 0x80);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#integer.representation
|
||||
private static UInt32 DecodeInteger(byte N, byte data, Stream stream)
|
||||
{
|
||||
// The starting value is the value behind the mask of the N bits
|
||||
UInt32 value = (UInt32)(data & (byte)(0xFF >> (8 - N)));
|
||||
|
||||
// All N bits are 1s ? If so, we have at least one another byte to decode
|
||||
if (value == (1u << N) - 1)
|
||||
{
|
||||
byte shift = 0;
|
||||
|
||||
do
|
||||
{
|
||||
data = (byte)stream.ReadByte();
|
||||
|
||||
// The most significant bit is a continuation flag, so we have to mask it out
|
||||
value += (UInt32)((data & 0x7F) << shift);
|
||||
shift += 7;
|
||||
} while ((data & 0x80) == 0x80);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.requestTable.ToString() + this.responseTable.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa3995669bb00bb4b9662903873154c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,93 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
using System;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings for HTTP/2 connections when the Connect protocol is available.
|
||||
/// </summary>
|
||||
public sealed class WebSocketOverHTTP2Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Set it to false to disable Websocket Over HTTP/2 (RFC 8441). It's true by default.
|
||||
/// </summary>
|
||||
public bool EnableWebSocketOverHTTP2 { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Set it to disable fallback logic from the Websocket Over HTTP/2 implementation to the 'old' HTTP/1 implementation when it fails to connect.
|
||||
/// </summary>
|
||||
public bool EnableImplementationFallback { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Settings for HTTP/2 connections.
|
||||
/// </summary>
|
||||
public sealed class HTTP2ConnectionSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// When set to false, the plugin will not try to use HTTP/2 connections.
|
||||
/// </summary>
|
||||
public bool EnableHTTP2Connections = true;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum size of the HPACK header table.
|
||||
/// </summary>
|
||||
public UInt32 HeaderTableSize = 4096; // Spec default: 4096
|
||||
|
||||
/// <summary>
|
||||
/// Maximum concurrent http2 stream on http2 connection will allow. Its default value is 128;
|
||||
/// </summary>
|
||||
public UInt32 MaxConcurrentStreams = 128; // Spec default: not defined
|
||||
|
||||
/// <summary>
|
||||
/// Initial window size of a http2 stream. Its default value is 65535, can be controlled through the HTTPRequest's DownloadSettings object.
|
||||
/// </summary>
|
||||
public UInt32 InitialStreamWindowSize = UInt16.MaxValue; // Spec default: 65535
|
||||
|
||||
/// <summary>
|
||||
/// Global window size of a http/2 connection. Its default value is the maximum possible value on 31 bits.
|
||||
/// </summary>
|
||||
public UInt32 InitialConnectionWindowSize = HTTP2ContentConsumer.MaxValueFor31Bits; // Spec default: 65535
|
||||
|
||||
/// <summary>
|
||||
/// Maximum size of a http2 frame.
|
||||
/// </summary>
|
||||
public UInt32 MaxFrameSize = 16384; // 16384 spec def.
|
||||
|
||||
/// <summary>
|
||||
/// Not used.
|
||||
/// </summary>
|
||||
public UInt32 MaxHeaderListSize = UInt32.MaxValue; // Spec default: infinite
|
||||
|
||||
/// <summary>
|
||||
/// With HTTP/2 only one connection will be open so we can keep it open longer as we hope it will be reused more.
|
||||
/// </summary>
|
||||
public TimeSpan MaxIdleTime = TimeSpan.FromSeconds(120);
|
||||
|
||||
/// <summary>
|
||||
/// Time between two ping messages.
|
||||
/// </summary>
|
||||
public TimeSpan PingFrequency = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Timeout to receive a ping acknowledgement from the server. If no ack received in this time the connection will be treated as broken.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to enable RFC 8441 "Bootstrapping WebSockets with HTTP/2" (https://tools.ietf.org/html/rfc8441).
|
||||
/// </summary>
|
||||
public bool EnableConnectProtocol = false;
|
||||
|
||||
/// <summary>
|
||||
/// Settings for WebSockets over HTTP/2 (RFC 8441)
|
||||
/// </summary>
|
||||
public WebSocketOverHTTP2Settings WebSocketOverHTTP2Settings = new WebSocketOverHTTP2Settings();
|
||||
|
||||
/// <summary>
|
||||
/// When set to <c>true</c> the plugin will send a <c>Content-Length</c> header, even if it has a <c>0</c> value.
|
||||
/// </summary>
|
||||
public bool ForceSendContentLengthHeader { get; set; } = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40bfeb8809240f346a1a067b7fed0f3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,709 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
#define ENABLE_LOGGING
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.PlatformSupport.Threading;
|
||||
using Best.HTTP.Shared;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
using Best.HTTP.Shared.PlatformSupport.Network.Tcp;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
public delegate HTTP2Stream CustomHTTP2StreamFactory(HTTPRequest request, uint streamId, HTTP2ContentConsumer parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder);
|
||||
|
||||
public sealed class HTTP2ContentConsumer : IHTTPRequestHandler, IContentConsumer, IThreadSignaler
|
||||
{
|
||||
public KeepAliveHeader KeepAlive { get { return null; } }
|
||||
|
||||
public bool CanProcessMultiple { get { return !this.SentGoAwayFrame && this.isRunning && this._maxAssignedRequests > 1; } }
|
||||
|
||||
public int AssignedRequests => this._assignedRequest;
|
||||
private int _assignedRequest;
|
||||
|
||||
public int MaxAssignedRequests => this._maxAssignedRequests;
|
||||
private int _maxAssignedRequests = 1;
|
||||
|
||||
public const UInt32 MaxValueFor31Bits = 0xFFFFFFFF >> 1;
|
||||
|
||||
public double Latency { get; private set; }
|
||||
|
||||
public HTTP2SettingsManager settings;
|
||||
public HPACKEncoder HPACKEncoder;
|
||||
|
||||
public LoggingContext Context { get; private set; }
|
||||
|
||||
public PeekableContentProviderStream ContentProvider { get; private set; }
|
||||
|
||||
public HTTP2ConnectionSettings ConnectionSettings { get; private set; }
|
||||
|
||||
private DateTime lastPingSent = DateTime.MinValue;
|
||||
private int waitingForPingAck = 0;
|
||||
private DateTime lastDataFrameReceived = DateTime.MinValue;
|
||||
|
||||
public static int RTTBufferCapacity = 5;
|
||||
private CircularBuffer<double> rtts = new CircularBuffer<double>(RTTBufferCapacity);
|
||||
|
||||
private volatile bool isRunning;
|
||||
|
||||
private AutoResetEvent newFrameSignal = new AutoResetEvent(false);
|
||||
|
||||
private ConcurrentQueue<HTTPRequest> requestQueue = new ConcurrentQueue<HTTPRequest>();
|
||||
|
||||
private List<HTTP2Stream> clientInitiatedStreams = new List<HTTP2Stream>();
|
||||
|
||||
private ConcurrentQueue<HTTP2FrameHeaderAndPayload> newFrames = new ConcurrentQueue<HTTP2FrameHeaderAndPayload>();
|
||||
|
||||
private List<HTTP2FrameHeaderAndPayload> outgoingFrames = new List<HTTP2FrameHeaderAndPayload>();
|
||||
|
||||
private UInt32 remoteWindow;
|
||||
private DateTime lastInteraction;
|
||||
private DateTime goAwaySentAt = DateTime.MaxValue;
|
||||
private bool SentGoAwayFrame { get => this.goAwaySentAt != DateTime.MaxValue; }
|
||||
|
||||
private HTTPOverTCPConnection conn;
|
||||
|
||||
private TimeSpan MaxGoAwayWaitTime { get { return !this.SentGoAwayFrame ? TimeSpan.MaxValue : TimeSpan.FromMilliseconds(Math.Max(this.Latency * 2.5, 1500)); } }
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#StreamIdentifiers
|
||||
// Streams initiated by a client MUST use odd-numbered stream identifiers
|
||||
// With an initial value of -1, the first client initiated stream's id going to be 1.
|
||||
private long LastStreamId = -1;
|
||||
|
||||
public HTTP2ContentConsumer(HTTPOverTCPConnection conn)
|
||||
{
|
||||
this.Context = new LoggingContext(this);
|
||||
this.Context.Add("Parent", conn.Context);
|
||||
|
||||
this.conn = conn;
|
||||
this.isRunning = true;
|
||||
|
||||
this.ConnectionSettings = HTTPManager.PerHostSettings.Get(conn.HostKey).HTTP2ConnectionSettings;
|
||||
this.settings = new HTTP2SettingsManager(this.Context, this.ConnectionSettings);
|
||||
|
||||
Process(this.conn.CurrentRequest);
|
||||
}
|
||||
|
||||
public void Process(HTTPRequest request)
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Process request called", this.Context);
|
||||
#endif
|
||||
|
||||
request.TimeoutSettings.SetProcessing(this.lastInteraction = DateTime.UtcNow);
|
||||
|
||||
Interlocked.Increment(ref this._assignedRequest);
|
||||
|
||||
this.requestQueue.Enqueue(request);
|
||||
SignalThread(SignalHandlerTypes.Signal);
|
||||
}
|
||||
|
||||
public void SignalThread(SignalHandlerTypes signalType) => this.newFrameSignal?.Set();
|
||||
|
||||
public void RunHandler()
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Processing thread up and running!", this.Context);
|
||||
#endif
|
||||
|
||||
ThreadedRunner.SetThreadName("Best.HTTP2 Process");
|
||||
|
||||
string abortWithMessage = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
bool atLeastOneStreamHasAFrameToSend = true;
|
||||
|
||||
this.HPACKEncoder = new HPACKEncoder(this.Context, this.settings);
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#InitialWindowSize
|
||||
// The connection flow-control window is also 65,535 octets.
|
||||
this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
|
||||
|
||||
// we want to pack as many data as we can in one tcp segment, but setting the buffer's size too high
|
||||
// we might keep data too long and send them in bursts instead of in a steady stream.
|
||||
// Keeping it too low might result in a full tcp segment and one with very low payload
|
||||
// Is it possible that one full tcp segment sized buffer would be the best, or multiple of it.
|
||||
// It would keep the network busy without any fragments. The ethernet layer has a maximum of 1500 bytes,
|
||||
// but there's two layers of 20 byte headers each, so as a theoretical maximum it's 1500-20-20 bytes.
|
||||
// On the other hand, if the buffer is small (1-2), that means that for larger data, we have to do a lot
|
||||
// of system calls, in that case a larger buffer might be better. Still, if we are not cpu bound,
|
||||
// a well saturated network might serve us better.
|
||||
using (WriteOnlyBufferedStream bufferedStream = new WriteOnlyBufferedStream(this.conn.TopStream, 1024 * 1024 /*1500 - 20 - 20*/, this.Context))
|
||||
{
|
||||
// The client connection preface starts with a sequence of 24 octets
|
||||
// Connection preface starts with the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n).
|
||||
ReadOnlySpan<byte> MAGIC = stackalloc byte[24] { 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a };
|
||||
bufferedStream.Write(MAGIC);
|
||||
|
||||
// This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty.
|
||||
// The client sends the client connection preface immediately upon receipt of a
|
||||
// 101 (Switching Protocols) response (indicating a successful upgrade)
|
||||
// or as the first application data octets of a TLS connection
|
||||
|
||||
this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] = this.ConnectionSettings.InitialStreamWindowSize;
|
||||
this.settings.InitiatedMySettings[HTTP2Settings.MAX_CONCURRENT_STREAMS] = this.ConnectionSettings.MaxConcurrentStreams;
|
||||
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] = (uint)(this.ConnectionSettings.EnableConnectProtocol ? 1 : 0);
|
||||
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_PUSH] = 0;
|
||||
this.settings.SendChanges(this.outgoingFrames);
|
||||
this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
|
||||
|
||||
// The default window size for the whole connection is 65535 bytes,
|
||||
// but we want to set it to the maximum possible value.
|
||||
Int64 initialConnectionWindowSize = this.ConnectionSettings.InitialConnectionWindowSize;
|
||||
|
||||
// yandex.ru returns with an FLOW_CONTROL_ERROR (3) error when the plugin tries to set the connection window to 2^31 - 1
|
||||
// and works only with a maximum value of 2^31 - 10Mib (10 * 1024 * 1024).
|
||||
if (initialConnectionWindowSize == HTTP2ContentConsumer.MaxValueFor31Bits)
|
||||
initialConnectionWindowSize -= 10 * 1024 * 1024;
|
||||
|
||||
if (initialConnectionWindowSize > 65535)
|
||||
{
|
||||
Int64 initialConnectionWindowSizeDiff = initialConnectionWindowSize - 65535;
|
||||
if (initialConnectionWindowSizeDiff > 0)
|
||||
this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)initialConnectionWindowSizeDiff, this.Context));
|
||||
}
|
||||
|
||||
initialConnectionWindowSize -= 65535;
|
||||
|
||||
// local, per-connection window
|
||||
long localConnectionWindow = initialConnectionWindowSize;
|
||||
UInt32 updateConnectionWindowAt = (UInt32)(localConnectionWindow / 2);
|
||||
|
||||
while (this.isRunning)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
if (!atLeastOneStreamHasAFrameToSend)
|
||||
{
|
||||
// buffered stream will call flush automatically if its internal buffer is full.
|
||||
// But we have to make it sure that we flush remaining data before we go to sleep.
|
||||
bufferedStream.Flush();
|
||||
|
||||
// Wait until we have to send the next ping, OR a new frame is received on the read thread.
|
||||
// lastPingSent Now lastPingSent+frequency lastPingSent+Ping timeout
|
||||
//----|---------------------|---------------|----------------------|----------------------|------------|
|
||||
// lastInteraction lastInteraction + MaxIdleTime
|
||||
|
||||
var sendPingAt = this.lastPingSent + this.ConnectionSettings.PingFrequency;
|
||||
var timeoutAt = this.waitingForPingAck != 0 ? this.lastPingSent + this.ConnectionSettings.Timeout : DateTime.MaxValue;
|
||||
|
||||
// sendPingAt can be in the past if Timeout is larger than PingFrequency
|
||||
var nextPingInteraction = sendPingAt < timeoutAt && sendPingAt >= now ? sendPingAt : timeoutAt;
|
||||
|
||||
var disconnectByIdleAt = this.lastInteraction + this.ConnectionSettings.MaxIdleTime;
|
||||
|
||||
var nextDueClientInteractionAt = nextPingInteraction < disconnectByIdleAt ? nextPingInteraction : disconnectByIdleAt;
|
||||
int wait = (int)(nextDueClientInteractionAt - now).TotalMilliseconds;
|
||||
|
||||
wait = (int)Math.Min(wait, this.MaxGoAwayWaitTime.TotalMilliseconds);
|
||||
|
||||
TimeSpan nextStreamInteraction = TimeSpan.MaxValue;
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; i++)
|
||||
{
|
||||
var streamInteraction = this.clientInitiatedStreams[i].NextInteraction;
|
||||
if (streamInteraction < nextStreamInteraction)
|
||||
nextStreamInteraction = streamInteraction;
|
||||
}
|
||||
|
||||
wait = (int)Math.Min(wait, nextStreamInteraction.TotalMilliseconds);
|
||||
wait = (int)Math.Min(wait, 1000);
|
||||
|
||||
if (wait >= 1)
|
||||
{
|
||||
//if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
|
||||
// HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), string.Format("Sleeping for {0:N0}ms", wait), this.Context);
|
||||
this.newFrameSignal.WaitOne(wait);
|
||||
|
||||
now = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't send a new ping until a pong isn't received for the last one
|
||||
if (now - this.lastPingSent >= this.ConnectionSettings.PingFrequency && Interlocked.CompareExchange(ref this.waitingForPingAck, 1, 0) == 0)
|
||||
{
|
||||
this.lastPingSent = now;
|
||||
|
||||
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.None, this.Context);
|
||||
BufferHelper.SetLong(frame.Payload.Data, 0, now.Ticks);
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), $"PING frame created with payload: {frame.Payload.Slice(0, 8)}", this.Context);
|
||||
#endif
|
||||
|
||||
this.outgoingFrames.Add(frame);
|
||||
}
|
||||
|
||||
// Process received frames
|
||||
HTTP2FrameHeaderAndPayload header;
|
||||
while (this.newFrames.TryDequeue(out header))
|
||||
{
|
||||
if (header.StreamId > 0)
|
||||
{
|
||||
switch (header.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.DATA:
|
||||
localConnectionWindow -= header.Payload.Count;
|
||||
lastDataFrameReceived = now;
|
||||
break;
|
||||
}
|
||||
|
||||
HTTP2Stream http2Stream = FindStreamById(header.StreamId);
|
||||
|
||||
// Add frame to the stream, so it can process it when its Process function is called
|
||||
if (http2Stream != null)
|
||||
{
|
||||
http2Stream.AddFrame(header, this.outgoingFrames);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error? It's possible that we closed and removed the stream while the server was in the middle of sending frames
|
||||
#if ENABLE_LOGGING
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Warning(nameof(HTTP2ContentConsumer), $"Can't deliver frame: {header}, because no stream could be found for its Id!", this.Context);
|
||||
#endif
|
||||
|
||||
BufferPool.Release(header.Payload);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (header.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.SETTINGS:
|
||||
this.settings.Process(header, this.outgoingFrames);
|
||||
|
||||
Interlocked.Exchange(ref this._maxAssignedRequests,
|
||||
(int)Math.Min(this.ConnectionSettings.MaxConcurrentStreams,
|
||||
this.settings.RemoteSettings[HTTP2Settings.MAX_CONCURRENT_STREAMS]));
|
||||
|
||||
/*
|
||||
PluginEventHelper.EnqueuePluginEvent(
|
||||
new PluginEventInfo(PluginEvents.HTTP2ConnectProtocol,
|
||||
new HTTP2ConnectProtocolInfo(this.conn.HostKey,
|
||||
this.settings.MySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1 && this.settings.RemoteSettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1)));
|
||||
*/
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.PING:
|
||||
var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);
|
||||
|
||||
if ((pingFrame.Flags & HTTP2PingFlags.ACK) != 0)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref this.waitingForPingAck, 0, 1) == 0)
|
||||
break; // waitingForPingAck was 0 == aren't expecting a ping ack!
|
||||
|
||||
// it was an ack, payload must contain what we sent
|
||||
|
||||
var ticks = BufferHelper.ReadLong(pingFrame.OpaqueData, 0);
|
||||
|
||||
// the difference between the current time and the time when the ping message is sent
|
||||
TimeSpan diff = TimeSpan.FromTicks(now.Ticks - ticks);
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
if (diff.TotalSeconds > 10 || diff.TotalSeconds < 0)
|
||||
HTTPManager.Logger.Warning(nameof(HTTP2ContentConsumer), $"Pong received with weird diff: {diff}! Payload: {pingFrame.OpaqueData}", this.Context);
|
||||
#endif
|
||||
|
||||
// add it to the buffer
|
||||
this.rtts.Add(diff.TotalMilliseconds);
|
||||
|
||||
// and calculate the new latency
|
||||
this.Latency = CalculateLatency();
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Verbose(nameof(HTTP2ContentConsumer), string.Format("Latency: {0:F2}ms, RTT buffer: {1}", this.Latency, this.rtts.ToString()), this.Context);
|
||||
#endif
|
||||
}
|
||||
else if ((pingFrame.Flags & HTTP2PingFlags.ACK) == 0)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PING
|
||||
// if it wasn't an ack for our ping, we have to send one
|
||||
|
||||
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.ACK, this.Context);
|
||||
Array.Copy(pingFrame.OpaqueData.Data, 0, frame.Payload.Data, 0, pingFrame.OpaqueData.Count);
|
||||
|
||||
this.outgoingFrames.Insert(0, frame);
|
||||
}
|
||||
|
||||
BufferPool.Release(pingFrame.OpaqueData);
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.WINDOW_UPDATE:
|
||||
var windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(header);
|
||||
this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.GOAWAY:
|
||||
// parse the frame, so we can print out detailed information
|
||||
HTTP2GoAwayFrame goAwayFrame = HTTP2FrameHelper.ReadGoAwayFrame(header);
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Received GOAWAY frame: " + goAwayFrame.ToString(), this.Context);
|
||||
#endif
|
||||
|
||||
abortWithMessage = string.Format("Server closing the connection! Error code: {0} ({1}) Additonal Debug Data: {2}",
|
||||
goAwayFrame.Error, goAwayFrame.ErrorCode, goAwayFrame.AdditionalDebugData);
|
||||
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
|
||||
this.clientInitiatedStreams[i].Abort(abortWithMessage);
|
||||
this.clientInitiatedStreams.Clear();
|
||||
|
||||
// set the running flag to false, so the thread can exit
|
||||
this.isRunning = false;
|
||||
|
||||
BufferPool.Release(goAwayFrame.AdditionalDebugData);
|
||||
|
||||
//this.conn.State = HTTPConnectionStates.Closed;
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.ALT_SVC:
|
||||
//HTTP2AltSVCFrame altSvcFrame = HTTP2FrameHelper.ReadAltSvcFrame(header);
|
||||
|
||||
// Implement
|
||||
//HTTPManager.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(altSvcFrame.Origin, ))
|
||||
break;
|
||||
}
|
||||
|
||||
if (header.Payload != null)
|
||||
BufferPool.Release(header.Payload);
|
||||
}
|
||||
}
|
||||
|
||||
// If no pong received in a (configurable) reasonable time, treat the connection broken
|
||||
if (this.waitingForPingAck != 0)
|
||||
{
|
||||
// Even if we weren't received a ping ack, check for data frames.
|
||||
// If data is flowing in, we can say that the connection is still healthy.
|
||||
var lastPingDiff = now - this.lastPingSent;
|
||||
var lastDataDiff = now - this.lastDataFrameReceived;
|
||||
|
||||
if(lastPingDiff >= this.ConnectionSettings.Timeout && lastDataDiff >= this.ConnectionSettings.Timeout)
|
||||
throw new TimeoutException("Ping ACK isn't received in time!");
|
||||
}
|
||||
|
||||
// pre-test stream count to lock only when truly needed.
|
||||
if (this.clientInitiatedStreams.Count < _maxAssignedRequests && this.isRunning)
|
||||
{
|
||||
// grab requests from queue
|
||||
HTTPRequest request;
|
||||
while (this.clientInitiatedStreams.Count < _maxAssignedRequests && this.requestQueue.TryDequeue(out request))
|
||||
{
|
||||
HTTP2Stream newStream = null;
|
||||
|
||||
if (request.Tag is CustomHTTP2StreamFactory factory)
|
||||
{
|
||||
newStream = factory(request, (UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
|
||||
}
|
||||
else
|
||||
{
|
||||
newStream = new HTTP2Stream((UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
|
||||
}
|
||||
|
||||
newStream.Assign(request);
|
||||
this.clientInitiatedStreams.Add(newStream);
|
||||
}
|
||||
}
|
||||
|
||||
// send any settings changes
|
||||
this.settings.SendChanges(this.outgoingFrames);
|
||||
|
||||
atLeastOneStreamHasAFrameToSend = false;
|
||||
|
||||
// process other streams
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
|
||||
{
|
||||
var stream = this.clientInitiatedStreams[i];
|
||||
stream.Process(this.outgoingFrames);
|
||||
|
||||
// remove closed, empty streams (not enough to check the closed flag, a closed stream still can contain frames to send)
|
||||
if (stream.State == HTTP2StreamStates.Closed && !stream.HasFrameToSend)
|
||||
{
|
||||
this.clientInitiatedStreams.RemoveAt(i--);
|
||||
stream.Removed();
|
||||
|
||||
Interlocked.Decrement(ref this._assignedRequest);
|
||||
}
|
||||
|
||||
atLeastOneStreamHasAFrameToSend |= stream.HasFrameToSend;
|
||||
|
||||
this.lastInteraction = now;
|
||||
}
|
||||
|
||||
// If we encounter a data frame that too large for the current remote window, we have to stop
|
||||
// sending all data frames as we could send smaller data frames before the large ones.
|
||||
// Room for improvement: An improvement would be here to stop data frame sending per-stream.
|
||||
bool haltDataSending = false;
|
||||
|
||||
if (this.ShutdownType == ShutdownTypes.Running && !this.SentGoAwayFrame && now - this.lastInteraction >= this.ConnectionSettings.MaxIdleTime)
|
||||
{
|
||||
this.lastInteraction = now;
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Reached idle time, sending GoAway frame!", this.Context);
|
||||
#endif
|
||||
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR, this.Context));
|
||||
this.goAwaySentAt = now;
|
||||
}
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#GOAWAY
|
||||
// Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote peer can know whether a stream has been partially processed or not.
|
||||
if (this.ShutdownType == ShutdownTypes.Gentle && !this.SentGoAwayFrame)
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Connection abort requested, sending GoAway frame!", this.Context);
|
||||
#endif
|
||||
|
||||
this.outgoingFrames.Clear();
|
||||
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR, this.Context));
|
||||
this.goAwaySentAt = now;
|
||||
}
|
||||
|
||||
if (this.isRunning && this.SentGoAwayFrame && now - goAwaySentAt >= this.MaxGoAwayWaitTime)
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "No GoAway frame received back. Really quitting now!", this.Context);
|
||||
#endif
|
||||
this.isRunning = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localConnectionWindow < updateConnectionWindowAt)
|
||||
{
|
||||
UInt32 diff = (UInt32)(initialConnectionWindowSize - localConnectionWindow);
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), $"Updating local connection window by {diff:N0} ({initialConnectionWindowSize:N0} - {localConnectionWindow:N0})", this.Context);
|
||||
#endif
|
||||
|
||||
this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, diff, this.Context));
|
||||
localConnectionWindow = initialConnectionWindowSize;
|
||||
}
|
||||
|
||||
// Go through all the collected frames and send them.
|
||||
for (int i = 0; i < this.outgoingFrames.Count; ++i)
|
||||
{
|
||||
var frame = this.outgoingFrames[i];
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
if (HTTPManager.Logger.IsDiagnostic && frame.Type != HTTP2FrameTypes.DATA /*&& frame.Type != HTTP2FrameTypes.PING*/)
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Sending frame: " + frame.ToString(), this.Context);
|
||||
#endif
|
||||
|
||||
// post process frames
|
||||
switch (frame.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.DATA:
|
||||
if (haltDataSending)
|
||||
continue;
|
||||
|
||||
// if the tracked remoteWindow is smaller than the frame's payload, we stop sending
|
||||
// data frames until we receive window-update frames
|
||||
if (frame.Payload.Count > this.remoteWindow)
|
||||
{
|
||||
haltDataSending = true;
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Warning(nameof(HTTP2ContentConsumer), string.Format("Data sending halted for this round. Remote Window: {0:N0}, frame: {1}", this.remoteWindow, frame.ToString()), this.Context);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
this.outgoingFrames.RemoveAt(i--);
|
||||
|
||||
static void SendHeader(WriteOnlyBufferedStream stream,
|
||||
HTTP2FrameHeaderAndPayload headerAndPayload)
|
||||
{
|
||||
Span<byte> frameData = stackalloc byte[9];
|
||||
HTTP2FrameHelper.HeaderAsBinary(headerAndPayload, frameData);
|
||||
stream.Write(frameData);
|
||||
}
|
||||
|
||||
SendHeader(bufferedStream, frame);
|
||||
|
||||
if (frame.Payload.Count > 0)
|
||||
{
|
||||
bufferedStream.Write(frame.Payload.Data, frame.Payload.Offset, frame.Payload.Count);
|
||||
|
||||
if (!frame.DontUseMemPool)
|
||||
BufferPool.Release(frame.Payload);
|
||||
}
|
||||
|
||||
if (frame.Type == HTTP2FrameTypes.DATA)
|
||||
this.remoteWindow -= (uint)frame.Payload.Count;
|
||||
}
|
||||
|
||||
bufferedStream.Flush();
|
||||
} // while (this.isRunning)
|
||||
|
||||
bufferedStream.Flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
abortWithMessage = ex.ToString();
|
||||
// Log out the exception if it's a non-expected one.
|
||||
if (this.ShutdownType == ShutdownTypes.Running && this.isRunning && !this.SentGoAwayFrame && !HTTPManager.IsQuitting)
|
||||
HTTPManager.Logger.Exception(nameof(HTTP2ContentConsumer), "Sender thread", ex, this.Context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.isRunning = false;
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), $"Sender thread closing - cleaning up remaining requests({this.clientInitiatedStreams.Count})...", this.Context);
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrEmpty(abortWithMessage))
|
||||
abortWithMessage = "Connection closed unexpectedly";
|
||||
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
|
||||
this.clientInitiatedStreams[i].Abort(abortWithMessage);
|
||||
this.clientInitiatedStreams.Clear();
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Sender thread closing", this.Context);
|
||||
#endif
|
||||
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
|
||||
{
|
||||
switch (setting)
|
||||
{
|
||||
case HTTP2Settings.INITIAL_WINDOW_SIZE:
|
||||
this.remoteWindow = newValue - (oldValue - this.remoteWindow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBinding(PeekableContentProviderStream contentProvider) => this.ContentProvider = contentProvider;
|
||||
public void UnsetBinding() => this.ContentProvider = null;
|
||||
|
||||
public void OnContent()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (this.isRunning && HTTP2FrameHelper.CanReadFullFrame(this.ContentProvider))
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload header = HTTP2FrameHelper.ReadHeader(this.ContentProvider, this.Context);
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
if (HTTPManager.Logger.IsDiagnostic /*&& header.Type != HTTP2FrameTypes.DATA /*&& header.Type != HTTP2FrameTypes.PING*/)
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "New frame received: " + header.ToString(), this.Context);
|
||||
#endif
|
||||
|
||||
// Add the new frame to the queue. Processing it on the write thread gives us the advantage that
|
||||
// we don't have to deal with too much locking.
|
||||
this.newFrames.Enqueue(header);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(HTTP2ContentConsumer), "", ex, this.Context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// ping write thread to process the new frame
|
||||
this.newFrameSignal?.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConnectionClosed()
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Verbose(nameof(HTTP2ContentConsumer), $"{nameof(OnConnectionClosed)}({this.isRunning})", this.Context);
|
||||
#endif
|
||||
this.isRunning = false;
|
||||
this.newFrameSignal?.Set();
|
||||
}
|
||||
|
||||
public void OnError(Exception ex)
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Exception(nameof(HTTP2ContentConsumer), $"{nameof(OnError)}({this.isRunning}, {ex})", ex, this.Context);
|
||||
#endif
|
||||
this.isRunning = false;
|
||||
this.newFrameSignal?.Set();
|
||||
}
|
||||
|
||||
private double CalculateLatency()
|
||||
{
|
||||
if (this.rtts.Count == 0)
|
||||
return 0;
|
||||
|
||||
double sumLatency = 0;
|
||||
for (int i = 0; i < this.rtts.Count; ++i)
|
||||
sumLatency += this.rtts[i];
|
||||
|
||||
return sumLatency / this.rtts.Count;
|
||||
}
|
||||
|
||||
HTTP2Stream FindStreamById(UInt32 streamId)
|
||||
{
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
|
||||
{
|
||||
var stream = this.clientInitiatedStreams[i];
|
||||
if (stream.Id == streamId)
|
||||
return stream;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ShutdownTypes ShutdownType { get; private set; }
|
||||
|
||||
public void Shutdown(ShutdownTypes type)
|
||||
{
|
||||
this.ShutdownType = type;
|
||||
|
||||
switch (this.ShutdownType)
|
||||
{
|
||||
case ShutdownTypes.Gentle:
|
||||
this.newFrameSignal.Set();
|
||||
break;
|
||||
|
||||
case ShutdownTypes.Immediate:
|
||||
this.conn?.TopStream?.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), "Dispose", this.Context);
|
||||
#endif
|
||||
|
||||
while (this.newFrames.TryDequeue(out var frame))
|
||||
BufferPool.Release(frame.Payload);
|
||||
|
||||
foreach (var frame in this.outgoingFrames)
|
||||
BufferPool.Release(frame.Payload);
|
||||
this.outgoingFrames.Clear();
|
||||
|
||||
HTTPRequest request = null;
|
||||
while (this.requestQueue.TryDequeue(out request))
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information(nameof(HTTP2ContentConsumer), string.Format("Dispose - Request '{0}' IsCancellationRequested: {1}", request.CurrentUri.ToString(), request.IsCancellationRequested.ToString()), this.Context);
|
||||
#endif
|
||||
RequestEventHelper.EnqueueRequestEvent(request.IsCancellationRequested ? new RequestEventInfo(request, HTTPRequestStates.Aborted, null) : new RequestEventInfo(request, RequestEvents.Resend));
|
||||
}
|
||||
|
||||
this.newFrameSignal?.Close();
|
||||
this.newFrameSignal = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e555764ed1609a4bb57371203918367
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,430 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#ErrorCodes
|
||||
public enum HTTP2ErrorCodes
|
||||
{
|
||||
NO_ERROR = 0x00,
|
||||
PROTOCOL_ERROR = 0x01,
|
||||
INTERNAL_ERROR = 0x02,
|
||||
FLOW_CONTROL_ERROR = 0x03,
|
||||
SETTINGS_TIMEOUT = 0x04,
|
||||
STREAM_CLOSED = 0x05,
|
||||
FRAME_SIZE_ERROR = 0x06,
|
||||
REFUSED_STREAM = 0x07,
|
||||
CANCEL = 0x08,
|
||||
COMPRESSION_ERROR = 0x09,
|
||||
CONNECT_ERROR = 0x0A,
|
||||
ENHANCE_YOUR_CALM = 0x0B,
|
||||
INADEQUATE_SECURITY = 0x0C,
|
||||
HTTP_1_1_REQUIRED = 0x0D
|
||||
}
|
||||
|
||||
public static class HTTP2FrameHelper
|
||||
{
|
||||
public static HTTP2ContinuationFrame ReadContinuationFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#CONTINUATION
|
||||
|
||||
HTTP2ContinuationFrame frame = new HTTP2ContinuationFrame(header);
|
||||
|
||||
frame.HeaderBlockFragment = header.Payload;
|
||||
header.Payload = BufferSegment.Empty;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2WindowUpdateFrame ReadWindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
|
||||
|
||||
HTTP2WindowUpdateFrame frame = new HTTP2WindowUpdateFrame(header);
|
||||
|
||||
frame.ReservedBit = BufferHelper.ReadBit(header.Payload.Data[0], 0);
|
||||
frame.WindowSizeIncrement = BufferHelper.ReadUInt31(header.Payload, 0);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2GoAwayFrame ReadGoAwayFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#GOAWAY
|
||||
// str id error
|
||||
// | 0, 1, 2, 3 | 4, 5, 6, 7 | ...
|
||||
|
||||
HTTP2GoAwayFrame frame = new HTTP2GoAwayFrame(header);
|
||||
|
||||
frame.ReservedBit = BufferHelper.ReadBit(header.Payload.Data[0], 0);
|
||||
frame.LastStreamId = BufferHelper.ReadUInt31(header.Payload, 0);
|
||||
frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 4);
|
||||
|
||||
var additionalDebugDataLength = header.Payload.Count - 8;
|
||||
if (additionalDebugDataLength > 0)
|
||||
{
|
||||
var buff = BufferPool.Get(additionalDebugDataLength, true);
|
||||
Array.Copy(header.Payload.Data, 8, buff, 0, additionalDebugDataLength);
|
||||
|
||||
frame.AdditionalDebugData = buff.AsBuffer(additionalDebugDataLength);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2PingFrame ReadPingFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PING
|
||||
|
||||
HTTP2PingFrame frame = new HTTP2PingFrame(header);
|
||||
|
||||
Array.Copy(header.Payload.Data, 0, frame.OpaqueData.Data, 0, frame.OpaqueData.Count);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2PushPromiseFrame ReadPush_PromiseFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PUSH_PROMISE
|
||||
|
||||
HTTP2PushPromiseFrame frame = new HTTP2PushPromiseFrame(header);
|
||||
int HeaderBlockFragmentLength = header.Payload.Count - 4; // PromisedStreamId
|
||||
|
||||
bool isPadded = (frame.Flags & HTTP2PushPromiseFlags.PADDED) != 0;
|
||||
if (isPadded)
|
||||
{
|
||||
frame.PadLength = header.Payload.Data[0];
|
||||
|
||||
HeaderBlockFragmentLength -= 1 + (frame.PadLength ?? 0);
|
||||
}
|
||||
|
||||
frame.ReservedBit = BufferHelper.ReadBit(header.Payload.Data[1], 0);
|
||||
frame.PromisedStreamId = BufferHelper.ReadUInt31(header.Payload, 1);
|
||||
|
||||
var HeaderBlockFragmentIdx = isPadded ? 5 : 4;
|
||||
frame.HeaderBlockFragment = header.Payload.Slice(HeaderBlockFragmentIdx, HeaderBlockFragmentLength);
|
||||
header.Payload = BufferSegment.Empty;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2RSTStreamFrame ReadRST_StreamFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
|
||||
|
||||
HTTP2RSTStreamFrame frame = new HTTP2RSTStreamFrame(header);
|
||||
frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 0);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2PriorityFrame ReadPriorityFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PRIORITY
|
||||
|
||||
if (header.Payload.Count != 5)
|
||||
{
|
||||
//throw FRAME_SIZE_ERROR
|
||||
}
|
||||
|
||||
HTTP2PriorityFrame frame = new HTTP2PriorityFrame(header);
|
||||
|
||||
frame.IsExclusive = BufferHelper.ReadBit(header.Payload.Data[0], 0);
|
||||
frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, 0);
|
||||
frame.Weight = header.Payload.Data[4];
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2HeadersFrame ReadHeadersFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#HEADERS
|
||||
|
||||
HTTP2HeadersFrame frame = new HTTP2HeadersFrame(header);
|
||||
var HeaderBlockFragmentLength = header.Payload.Count;
|
||||
|
||||
bool isPadded = (frame.Flags & HTTP2HeadersFlags.PADDED) != 0;
|
||||
bool isPriority = (frame.Flags & HTTP2HeadersFlags.PRIORITY) != 0;
|
||||
|
||||
int payloadIdx = 0;
|
||||
|
||||
if (isPadded)
|
||||
{
|
||||
frame.PadLength = header.Payload.Data[payloadIdx++];
|
||||
|
||||
int subLength = 1 + (frame.PadLength ?? 0);
|
||||
if (subLength <= HeaderBlockFragmentLength)
|
||||
HeaderBlockFragmentLength -= subLength;
|
||||
//else
|
||||
// throw PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
if (isPriority)
|
||||
{
|
||||
frame.IsExclusive = BufferHelper.ReadBit(header.Payload.Data[payloadIdx], 0);
|
||||
frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, payloadIdx);
|
||||
payloadIdx += 4;
|
||||
frame.Weight = header.Payload.Data[payloadIdx++];
|
||||
|
||||
int subLength = 5;
|
||||
if (subLength <= HeaderBlockFragmentLength)
|
||||
HeaderBlockFragmentLength -= subLength;
|
||||
//else
|
||||
// throw PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
var HeaderBlockFragmentIdx = payloadIdx;
|
||||
frame.HeaderBlockFragment = header.Payload.Slice(HeaderBlockFragmentIdx, HeaderBlockFragmentLength);
|
||||
|
||||
header.Payload = BufferSegment.Empty;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2DataFrame ReadDataFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#DATA
|
||||
|
||||
HTTP2DataFrame frame = new HTTP2DataFrame(header);
|
||||
|
||||
var DataLength = header.Payload.Count;
|
||||
|
||||
bool isPadded = (frame.Flags & HTTP2DataFlags.PADDED) != 0;
|
||||
if (isPadded)
|
||||
{
|
||||
frame.PadLength = header.Payload.Data[0];
|
||||
|
||||
int subLength = 1 + (frame.PadLength ?? 0);
|
||||
if (subLength <= DataLength)
|
||||
DataLength -= subLength;
|
||||
//else
|
||||
// throw PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
var DataIdx = isPadded ? 1 : 0;
|
||||
|
||||
frame.Data = header.Payload.Slice(DataIdx, DataLength);
|
||||
header.Payload = BufferSegment.Empty;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2AltSVCFrame ReadAltSvcFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
HTTP2AltSVCFrame frame = new HTTP2AltSVCFrame(header);
|
||||
|
||||
// Implement
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static void StreamRead(Stream stream, byte[] buffer, int offset, uint count)
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
uint sumRead = 0;
|
||||
do
|
||||
{
|
||||
int readCount = (int)(count - sumRead);
|
||||
int streamReadCount = stream.Read(buffer, (int)(offset + sumRead), readCount);
|
||||
if (streamReadCount <= 0 && readCount > 0)
|
||||
throw new Exception("TCP Stream closed!");
|
||||
sumRead += (uint)streamReadCount;
|
||||
} while (sumRead < count);
|
||||
}
|
||||
|
||||
public static void StreamRead(Stream stream, BufferSegment buffer)
|
||||
{
|
||||
if (buffer.Count == 0)
|
||||
return;
|
||||
|
||||
uint sumRead = 0;
|
||||
do
|
||||
{
|
||||
int readCount = (int)(buffer.Count - sumRead);
|
||||
int streamReadCount = stream.Read(buffer.Data, (int)(buffer.Offset + sumRead), readCount);
|
||||
if (streamReadCount <= 0 && readCount > 0)
|
||||
throw new Exception("TCP Stream closed!");
|
||||
sumRead += (uint)streamReadCount;
|
||||
} while (sumRead < buffer.Count);
|
||||
}
|
||||
|
||||
public static void HeaderAsBinary(HTTP2FrameHeaderAndPayload header, Span<byte> buffer)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#FrameHeader
|
||||
|
||||
BufferHelper.SetUInt24(buffer, 0, (uint)header.Payload.Count);
|
||||
buffer[3] = (byte)header.Type;
|
||||
buffer[4] = header.Flags;
|
||||
BufferHelper.SetUInt31(buffer, 5, header.StreamId);
|
||||
}
|
||||
|
||||
public unsafe static bool CanReadFullFrame(PeekableStream stream)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#FrameHeader
|
||||
|
||||
// A frame without any payload is 9 bytes
|
||||
if (stream.Length < 9)
|
||||
return false;
|
||||
|
||||
stream.BeginPeek();
|
||||
|
||||
// First 3 bytes are the payload length
|
||||
var rawLength = stackalloc byte[3];
|
||||
rawLength[0] = (byte)stream.PeekByte();
|
||||
rawLength[1] = (byte)stream.PeekByte();
|
||||
rawLength[2] = (byte)stream.PeekByte();
|
||||
|
||||
var payloadLength = (UInt32)(rawLength[2] | rawLength[1] << 8 | rawLength[0] << 16);
|
||||
|
||||
return stream.Length >= (9 + payloadLength);
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload ReadHeader(Stream stream, LoggingContext context)
|
||||
{
|
||||
byte[] buffer = BufferPool.Get(9, true, context);
|
||||
using var _ = buffer.AsAutoRelease();
|
||||
|
||||
StreamRead(stream, buffer, 0, 9);
|
||||
|
||||
HTTP2FrameHeaderAndPayload header = new HTTP2FrameHeaderAndPayload();
|
||||
|
||||
var PayloadLength = (int)BufferHelper.ReadUInt24(buffer, 0);
|
||||
header.Type = (HTTP2FrameTypes)buffer[3];
|
||||
header.Flags = buffer[4];
|
||||
header.StreamId = BufferHelper.ReadUInt31(buffer, 5);
|
||||
|
||||
header.Payload = BufferPool.Get(PayloadLength, true, context).AsBuffer(PayloadLength);
|
||||
|
||||
try
|
||||
{
|
||||
StreamRead(stream, header.Payload);
|
||||
}
|
||||
catch
|
||||
{
|
||||
BufferPool.Release(header.Payload);
|
||||
throw;
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
public static HTTP2SettingsFrame ReadSettings(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
HTTP2SettingsFrame frame = new HTTP2SettingsFrame(header);
|
||||
|
||||
if (header.Payload.Count > 0)
|
||||
{
|
||||
int kvpCount = (int)(header.Payload.Count / 6);
|
||||
|
||||
frame.Settings = new List<KeyValuePair<HTTP2Settings, uint>>(kvpCount);
|
||||
for (int i = 0; i < kvpCount; ++i)
|
||||
{
|
||||
HTTP2Settings key = (HTTP2Settings)BufferHelper.ReadUInt16(header.Payload.Data, i * 6);
|
||||
UInt32 value = BufferHelper.ReadUInt32(header.Payload, (i * 6) + 2);
|
||||
|
||||
frame.Settings.Add(new KeyValuePair<HTTP2Settings, uint>(key, value));
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateACKSettingsFrame()
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.SETTINGS;
|
||||
frame.Flags = (byte)HTTP2SettingsFlags.ACK;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateSettingsFrame(List<KeyValuePair<HTTP2Settings, UInt32>> settings, LoggingContext context)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.SETTINGS;
|
||||
frame.Flags = 0;
|
||||
var PayloadLength = settings.Count * 6;
|
||||
|
||||
frame.Payload = BufferPool.Get(PayloadLength, true, context).AsBuffer(PayloadLength);
|
||||
|
||||
for (int i = 0; i < settings.Count; ++i)
|
||||
{
|
||||
BufferHelper.SetUInt16(frame.Payload.Data, i * 6, (UInt16)settings[i].Key);
|
||||
BufferHelper.SetUInt32(frame.Payload.Data, (i * 6) + 2, settings[i].Value);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreatePingFrame(HTTP2PingFlags flags, LoggingContext context)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PING
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.PING;
|
||||
frame.Flags = (byte)flags;
|
||||
frame.StreamId = 0;
|
||||
frame.Payload = BufferPool.Get(8, true, context).AsBuffer(8);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateWindowUpdateFrame(UInt32 streamId, UInt32 windowSizeIncrement, LoggingContext context)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.WINDOW_UPDATE;
|
||||
frame.Flags = 0;
|
||||
frame.StreamId = streamId;
|
||||
frame.Payload = BufferPool.Get(4, true, context).AsBuffer(4);
|
||||
|
||||
BufferHelper.SetBit(0, 0, 0);
|
||||
BufferHelper.SetUInt31(frame.Payload.Data, 0, windowSizeIncrement);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateGoAwayFrame(UInt32 lastStreamId, HTTP2ErrorCodes error, LoggingContext context)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#GOAWAY
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.GOAWAY;
|
||||
frame.Flags = 0;
|
||||
frame.StreamId = 0;
|
||||
frame.Payload = BufferPool.Get(8, true, context).AsBuffer(8);
|
||||
|
||||
BufferHelper.SetUInt31(frame.Payload.Data, 0, lastStreamId);
|
||||
BufferHelper.SetUInt31(frame.Payload.Data, 4, (UInt32)error);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateRSTFrame(UInt32 streamId, HTTP2ErrorCodes errorCode, LoggingContext context)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.RST_STREAM;
|
||||
frame.Flags = 0;
|
||||
frame.StreamId = streamId;
|
||||
frame.Payload = BufferPool.Get(4, true, context).AsBuffer(4);
|
||||
|
||||
BufferHelper.SetUInt32(frame.Payload.Data, 0, (UInt32)errorCode);
|
||||
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed90f479cb36204458007c8c20c99490
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,392 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.PlatformSupport.Text;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#iana-frames
|
||||
public enum HTTP2FrameTypes : byte
|
||||
{
|
||||
DATA = 0x00,
|
||||
HEADERS = 0x01,
|
||||
PRIORITY = 0x02,
|
||||
RST_STREAM = 0x03,
|
||||
SETTINGS = 0x04,
|
||||
PUSH_PROMISE = 0x05,
|
||||
PING = 0x06,
|
||||
GOAWAY = 0x07,
|
||||
WINDOW_UPDATE = 0x08,
|
||||
CONTINUATION = 0x09,
|
||||
|
||||
// https://tools.ietf.org/html/rfc7838#section-4
|
||||
ALT_SVC = 0x0A
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2DataFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
END_STREAM = 0x01,
|
||||
PADDED = 0x08,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2HeadersFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
END_STREAM = 0x01,
|
||||
END_HEADERS = 0x04,
|
||||
PADDED = 0x08,
|
||||
PRIORITY = 0x20,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2SettingsFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
ACK = 0x01,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2PushPromiseFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
END_HEADERS = 0x04,
|
||||
PADDED = 0x08,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2PingFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
ACK = 0x01,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2ContinuationFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
END_HEADERS = 0x04,
|
||||
}
|
||||
|
||||
public struct HTTP2FrameHeaderAndPayload
|
||||
{
|
||||
public HTTP2FrameTypes Type;
|
||||
public byte Flags;
|
||||
public UInt32 StreamId;
|
||||
|
||||
//public byte[] Payload;
|
||||
public BufferSegment Payload;
|
||||
|
||||
//public UInt32 PayloadOffset;
|
||||
//public UInt32 PayloadLength;
|
||||
|
||||
public bool DontUseMemPool;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[HTTP2FrameHeaderAndPayload Type: {Type}, Flags: {Flags.ToBinaryStr()}, StreamId: {StreamId}, DontUseMemPool: {DontUseMemPool}, Payload: {Payload}]";
|
||||
}
|
||||
|
||||
public string PayloadAsHex() => this.Payload.ToString();
|
||||
}
|
||||
|
||||
public struct HTTP2SettingsFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2SettingsFlags Flags { get { return (HTTP2SettingsFlags)this.Header.Flags; } }
|
||||
public List<KeyValuePair<HTTP2Settings, UInt32>> Settings;
|
||||
|
||||
public HTTP2SettingsFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.Settings = null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string settings = null;
|
||||
if (this.Settings != null)
|
||||
{
|
||||
System.Text.StringBuilder sb = StringBuilderPool.Get(this.Settings.Count + 2);
|
||||
|
||||
sb.Append("[");
|
||||
foreach (var kvp in this.Settings)
|
||||
sb.AppendFormat("[{0}: {1}]", kvp.Key, kvp.Value);
|
||||
sb.Append("]");
|
||||
|
||||
settings = StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
|
||||
return string.Format("[HTTP2SettingsFrame Header: {0}, Flags: {1}, Settings: {2}]", this.Header.ToString(), this.Flags, settings ?? "Empty");
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2DataFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2DataFlags Flags { get { return (HTTP2DataFlags)this.Header.Flags; } }
|
||||
|
||||
public byte? PadLength;
|
||||
|
||||
public BufferSegment Data;
|
||||
|
||||
public HTTP2DataFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.PadLength = null;
|
||||
this.Data = BufferSegment.Empty;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2DataFrame Header: {0}, Flags: {1}, PadLength: {2}, Data: {3}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
|
||||
this.Data.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2HeadersFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2HeadersFlags Flags { get { return (HTTP2HeadersFlags)this.Header.Flags; } }
|
||||
|
||||
public byte? PadLength;
|
||||
public byte? IsExclusive;
|
||||
public UInt32? StreamDependency;
|
||||
public byte? Weight;
|
||||
|
||||
public BufferSegment HeaderBlockFragment;
|
||||
|
||||
public HTTP2HeadersFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.PadLength = null;
|
||||
this.IsExclusive = null;
|
||||
this.StreamDependency = null;
|
||||
this.Weight = null;
|
||||
this.HeaderBlockFragment = BufferSegment.Empty;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2HeadersFrame Header: {0}, Flags: {1}, PadLength: {2}, IsExclusive: {3}, StreamDependency: {4}, Weight: {5}, HeaderBlockFragmentLength: {6}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
|
||||
this.IsExclusive == null ? "Empty" : this.IsExclusive.Value.ToString(),
|
||||
this.StreamDependency == null ? "Empty" : this.StreamDependency.Value.ToString(),
|
||||
this.Weight == null ? "Empty" : this.Weight.Value.ToString(),
|
||||
this.HeaderBlockFragment.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2PriorityFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public byte IsExclusive;
|
||||
public UInt32 StreamDependency;
|
||||
public byte Weight;
|
||||
|
||||
public HTTP2PriorityFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.IsExclusive = 0;
|
||||
this.StreamDependency = 0;
|
||||
this.Weight = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2PriorityFrame Header: {0}, IsExclusive: {1}, StreamDependency: {2}, Weight: {3}]",
|
||||
this.Header.ToString(), this.IsExclusive, this.StreamDependency, this.Weight);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2RSTStreamFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public UInt32 ErrorCode;
|
||||
public HTTP2ErrorCodes Error { get { return (HTTP2ErrorCodes)this.ErrorCode; } }
|
||||
|
||||
public HTTP2RSTStreamFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.ErrorCode = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2RST_StreamFrame Header: {0}, Error: {1}({2})]", this.Header.ToString(), this.Error, this.ErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2PushPromiseFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2PushPromiseFlags Flags { get { return (HTTP2PushPromiseFlags)this.Header.Flags; } }
|
||||
|
||||
public byte? PadLength;
|
||||
public byte ReservedBit;
|
||||
public UInt32 PromisedStreamId;
|
||||
|
||||
public BufferSegment HeaderBlockFragment;
|
||||
//public UInt32 HeaderBlockFragmentIdx;
|
||||
//public byte[] HeaderBlockFragment;
|
||||
//public UInt32 HeaderBlockFragmentLength;
|
||||
|
||||
public HTTP2PushPromiseFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.PadLength = null;
|
||||
this.ReservedBit = 0;
|
||||
this.PromisedStreamId = 0;
|
||||
//this.HeaderBlockFragmentIdx = 0;
|
||||
//this.HeaderBlockFragment = null;
|
||||
//this.HeaderBlockFragmentLength = 0;
|
||||
this.HeaderBlockFragment = BufferSegment.Empty;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2Push_PromiseFrame Header: {0}, Flags: {1}, PadLength: {2}, ReservedBit: {3}, PromisedStreamId: {4}, HeaderBlockFragmentLength: {5}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.PadLength == null ? "Empty" : this.PadLength.Value.ToString(),
|
||||
this.ReservedBit,
|
||||
this.PromisedStreamId,
|
||||
this.HeaderBlockFragment);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2PingFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2PingFlags Flags { get { return (HTTP2PingFlags)this.Header.Flags; } }
|
||||
|
||||
//public readonly byte[] OpaqueData;
|
||||
//public readonly byte OpaqueDataLength;
|
||||
public readonly BufferSegment OpaqueData;
|
||||
|
||||
public HTTP2PingFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.OpaqueData = BufferPool.Get(8, true).AsBuffer(8);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2PingFrame Header: {0}, Flags: {1}, OpaqueData: {2}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.OpaqueData);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2GoAwayFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2ErrorCodes Error { get { return (HTTP2ErrorCodes)this.ErrorCode; } }
|
||||
|
||||
public byte ReservedBit;
|
||||
public UInt32 LastStreamId;
|
||||
public UInt32 ErrorCode;
|
||||
|
||||
public BufferSegment AdditionalDebugData;
|
||||
//public byte[] AdditionalDebugData;
|
||||
//public UInt32 AdditionalDebugDataLength;
|
||||
|
||||
public HTTP2GoAwayFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.ReservedBit = 0;
|
||||
this.LastStreamId = 0;
|
||||
this.ErrorCode = 0;
|
||||
this.AdditionalDebugData = BufferSegment.Empty;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2GoAwayFrame Header: {0}, ReservedBit: {1}, LastStreamId: {2}, Error: {3}({4}), AdditionalDebugData: {5}]",
|
||||
this.Header.ToString(),
|
||||
this.ReservedBit,
|
||||
this.LastStreamId,
|
||||
this.Error,
|
||||
this.ErrorCode,
|
||||
this.AdditionalDebugData.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2WindowUpdateFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public byte ReservedBit;
|
||||
public UInt32 WindowSizeIncrement;
|
||||
|
||||
public HTTP2WindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.ReservedBit = 0;
|
||||
this.WindowSizeIncrement = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2WindowUpdateFrame Header: {0}, ReservedBit: {1}, WindowSizeIncrement: {2}]",
|
||||
this.Header.ToString(), this.ReservedBit, this.WindowSizeIncrement);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2ContinuationFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public HTTP2ContinuationFlags Flags { get { return (HTTP2ContinuationFlags)this.Header.Flags; } }
|
||||
|
||||
public BufferSegment HeaderBlockFragment;
|
||||
|
||||
public HTTP2ContinuationFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.HeaderBlockFragment = BufferSegment.Empty;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2ContinuationFrame Header: {0}, Flags: {1}, HeaderBlockFragment: {2}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.HeaderBlockFragment);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://tools.ietf.org/html/rfc7838#section-4
|
||||
/// </summary>
|
||||
public struct HTTP2AltSVCFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public string Origin;
|
||||
public string AltSvcFieldValue;
|
||||
|
||||
public HTTP2AltSVCFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.Origin = null;
|
||||
this.AltSvcFieldValue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5b1b3f5f21e2324b9a6513e8d458167
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,160 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Best.HTTP.Response;
|
||||
using Best.HTTP.Response.Decompression;
|
||||
using Best.HTTP.Shared;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
public sealed class HTTP2Response : HTTPResponse
|
||||
{
|
||||
// For progress report
|
||||
public long ExpectedContentLength { get; private set; }
|
||||
|
||||
private string contentEncoding = null;
|
||||
|
||||
bool isPrepared;
|
||||
private IDecompressor _decompressor;
|
||||
|
||||
public HTTP2Response(HTTPRequest request, bool isFromCache)
|
||||
: base(request, isFromCache)
|
||||
{
|
||||
this.HTTPVersion = new Version(2, 0);
|
||||
}
|
||||
|
||||
internal void AddHeaders(List<KeyValuePair<string, string>> headers)
|
||||
{
|
||||
this.ExpectedContentLength = -1;
|
||||
Dictionary<string, List<string>> newHeaders = this.Request.DownloadSettings.OnHeadersReceived != null ? new Dictionary<string, List<string>>() : null;
|
||||
|
||||
for (int i = 0; i < headers.Count; ++i)
|
||||
{
|
||||
KeyValuePair<string, string> header = headers[i];
|
||||
|
||||
if (header.Key.Equals(":status", StringComparison.Ordinal))
|
||||
{
|
||||
base.StatusCode = int.Parse(header.Value);
|
||||
base.Message = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.contentEncoding) && header.Key.Equals("content-encoding", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.contentEncoding = header.Value;
|
||||
}
|
||||
else if (base.Request.DownloadSettings.OnDownloadProgress != null && header.Key.Equals("content-length", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
long contentLength;
|
||||
if (long.TryParse(header.Value, out contentLength))
|
||||
this.ExpectedContentLength = contentLength;
|
||||
else
|
||||
HTTPManager.Logger.Information("HTTP2Response", string.Format("AddHeaders - Can't parse Content-Length as an int: '{0}'", header.Value), this.Context);
|
||||
}
|
||||
|
||||
base.AddHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
if (newHeaders != null)
|
||||
{
|
||||
List<string> values;
|
||||
if (!newHeaders.TryGetValue(header.Key, out values))
|
||||
newHeaders.Add(header.Key, values = new List<string>(1));
|
||||
|
||||
values.Add(header.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ExpectedContentLength == -1 && base.Request.DownloadSettings.OnDownloadProgress != null)
|
||||
HTTPManager.Logger.Information("HTTP2Response", "AddHeaders - No Content-Length header found!", this.Context);
|
||||
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.Request, newHeaders));
|
||||
}
|
||||
|
||||
internal void Prepare(IDownloadContentBufferAvailable bufferAvailable)
|
||||
{
|
||||
if (!this.isPrepared)
|
||||
{
|
||||
this.isPrepared = true;
|
||||
|
||||
CreateDownloadStream(bufferAvailable);
|
||||
|
||||
base.BeginReceiveContent();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ProcessData(BufferSegment payload)
|
||||
{
|
||||
// https://github.com/Benedicht/BestHTTP-Issues/issues/183
|
||||
// If _decompressor is still null, remove the content encoding value and serve the content as-is.
|
||||
if (!string.IsNullOrEmpty(this.contentEncoding) && this._decompressor == null)
|
||||
{
|
||||
if ((this._decompressor = DecompressorFactory.GetDecompressor(this.contentEncoding, this.Context)) == null)
|
||||
this.contentEncoding = null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(this.contentEncoding))
|
||||
{
|
||||
BufferSegment result = BufferSegment.Empty;
|
||||
bool release;
|
||||
try
|
||||
{
|
||||
(result, release) = this._decompressor.Decompress(payload, false, true, this.Context);
|
||||
}
|
||||
catch
|
||||
{
|
||||
BufferPool.Release(payload);
|
||||
throw;
|
||||
}
|
||||
if (release)
|
||||
BufferPool.Release(payload);
|
||||
|
||||
base.FeedDownloadedContentChunk(result);
|
||||
}
|
||||
else
|
||||
base.FeedDownloadedContentChunk(payload);
|
||||
}
|
||||
|
||||
internal void FinishProcessData()
|
||||
{
|
||||
if (this._decompressor != null)
|
||||
{
|
||||
var (decompressed, _) = this._decompressor.Decompress(BufferSegment.Empty, true, true, this.Context);
|
||||
if (decompressed != BufferSegment.Empty)
|
||||
base.FeedDownloadedContentChunk(decompressed);
|
||||
|
||||
this._decompressor.Dispose();
|
||||
this._decompressor = null;
|
||||
}
|
||||
|
||||
base.FinishedContentReceiving();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (this._decompressor != null)
|
||||
{
|
||||
// In some cases, the request is aborted and the decompressor left in an incomplete state.
|
||||
// Closing it might cause an exception that we don't care about.
|
||||
try
|
||||
{
|
||||
this._decompressor.Dispose();
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
|
||||
this._decompressor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe46645d97549c146a8c38f1297a75ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,331 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Best.HTTP.Shared;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
/// <summary>
|
||||
/// <see href="https://httpwg.org/specs/rfc7540.html#iana-settings">Settings Registry</see>
|
||||
/// </summary>
|
||||
public enum HTTP2Settings : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows the sender to inform the remote endpoint of the maximum size of the
|
||||
/// header compression table used to decode header blocks, in octets.
|
||||
/// The encoder can select any size equal to or less than this value
|
||||
/// by using signaling specific to the header compression format inside a header block (see [COMPRESSION]).
|
||||
/// The initial value is 4,096 octets.
|
||||
/// </summary>
|
||||
HEADER_TABLE_SIZE = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// This setting can be used to disable server push (Section 8.2).
|
||||
/// An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0.
|
||||
/// An endpoint that has both set this parameter to 0 and had it acknowledged MUST treat the receipt of a
|
||||
/// PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
///
|
||||
/// The initial value is 1, which indicates that server push is permitted.
|
||||
/// Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
/// </summary>
|
||||
ENABLE_PUSH = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the maximum number of concurrent streams that the sender will allow. This limit is directional:
|
||||
/// it applies to the number of streams that the sender permits the receiver to create.
|
||||
/// Initially, there is no limit to this value. It is recommended that this value be no smaller than 100,
|
||||
/// so as to not unnecessarily limit parallelism.
|
||||
///
|
||||
/// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints.
|
||||
/// A zero value does prevent the creation of new streams;
|
||||
/// however, this can also happen for any limit that is exhausted with active streams.
|
||||
/// Servers SHOULD only set a zero value for short durations; if a server does not wish to accept requests,
|
||||
/// closing the connection is more appropriate.
|
||||
/// </summary>
|
||||
MAX_CONCURRENT_STREAMS = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the sender's initial window size (in octets) for stream-level flow control.
|
||||
/// The initial value is 2^16-1 (65,535) octets.
|
||||
///
|
||||
/// This setting affects the window size of all streams (see Section 6.9.2).
|
||||
///
|
||||
/// Values above the maximum flow-control window size of 2^31-1 MUST be treated as a connection error
|
||||
/// (Section 5.4.1) of type FLOW_CONTROL_ERROR.
|
||||
/// </summary>
|
||||
INITIAL_WINDOW_SIZE = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
|
||||
///
|
||||
/// The initial value is 2^14 (16,384) octets.
|
||||
/// The value advertised by an endpoint MUST be between this initial value and the maximum allowed frame size
|
||||
/// (2^24-1 or 16,777,215 octets), inclusive.
|
||||
/// Values outside this range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
/// </summary>
|
||||
MAX_FRAME_SIZE = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets.
|
||||
/// The value is based on the uncompressed size of header fields,
|
||||
/// including the length of the name and value in octets plus an overhead of 32 octets for each header field.
|
||||
///
|
||||
/// For any given request, a lower limit than what is advertised MAY be enforced. The initial value of this setting is unlimited.
|
||||
/// </summary>
|
||||
MAX_HEADER_LIST_SIZE = 0x06,
|
||||
|
||||
RESERVED = 0x07,
|
||||
|
||||
/// <summary>
|
||||
/// https://tools.ietf.org/html/rfc8441
|
||||
/// Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1, a client MAY use the Extended CONNECT as defined in this document when creating new streams.
|
||||
/// Receipt of this parameter by a server does not have any impact.
|
||||
///
|
||||
/// A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with the value of 0 after previously sending a value of 1.
|
||||
/// </summary>
|
||||
ENABLE_CONNECT_PROTOCOL = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Allow endpoints to omit or ignore HTTP/2 priority signals.
|
||||
/// <see href="https://www.rfc-editor.org/rfc/rfc9218.html">Extensible Prioritization Scheme for HTTP</see>
|
||||
/// </summary>
|
||||
NO_RFC7540_PRIORITIES = 0x09,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a registry for HTTP/2 settings.
|
||||
/// </summary>
|
||||
public sealed class HTTP2SettingsRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the registry is read-only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when a setting changes.
|
||||
/// </summary>
|
||||
public Action<HTTP2SettingsRegistry, HTTP2Settings, UInt32, UInt32> OnSettingChangedEvent;
|
||||
|
||||
private UInt32[] values;
|
||||
private bool[] changeFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Indexer to get or set values based on an <see cref="HTTP2Settings"/> key.
|
||||
/// </summary>
|
||||
/// <param name="setting">The setting key.</param>
|
||||
/// <returns>The value associated with the given setting key.</returns>
|
||||
public UInt32 this[HTTP2Settings setting]
|
||||
{
|
||||
get { return this.values[(ushort)setting]; }
|
||||
|
||||
set
|
||||
{
|
||||
if (this.IsReadOnly)
|
||||
throw new NotSupportedException("It's a read-only one!");
|
||||
|
||||
ushort idx = (ushort)setting;
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#SettingValues
|
||||
// An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST ignore that setting.
|
||||
if (idx == 0 || idx >= this.values.Length)
|
||||
return;
|
||||
|
||||
UInt32 oldValue = this.values[idx];
|
||||
if (oldValue != value)
|
||||
{
|
||||
this.values[idx] = value;
|
||||
this.changeFlags[idx] = true;
|
||||
IsChanged = true;
|
||||
|
||||
if (this.OnSettingChangedEvent != null)
|
||||
this.OnSettingChangedEvent(this, setting, oldValue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether any setting has changed.
|
||||
/// </summary>
|
||||
public bool IsChanged { get; private set; }
|
||||
|
||||
private HTTP2SettingsManager _parent;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the HTTP2SettingsRegistry class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent <see cref="HTTP2SettingsManager"/>.</param>
|
||||
/// <param name="readOnly">Whether this registry is read-only.</param>
|
||||
/// <param name="treatItAsAlreadyChanged">Whether to treat the registry as if a setting has already changed.</param>
|
||||
public HTTP2SettingsRegistry(HTTP2SettingsManager parent, bool readOnly, bool treatItAsAlreadyChanged)
|
||||
{
|
||||
this._parent = parent;
|
||||
|
||||
this.values = new UInt32[HTTP2SettingsManager.KnownSettingsCount];
|
||||
|
||||
this.IsReadOnly = readOnly;
|
||||
if (!this.IsReadOnly)
|
||||
this.changeFlags = new bool[HTTP2SettingsManager.KnownSettingsCount];
|
||||
|
||||
// Set default values (https://httpwg.org/specs/rfc7540.html#iana-settings)
|
||||
this.values[(UInt16)HTTP2Settings.HEADER_TABLE_SIZE] = 4096;
|
||||
this.values[(UInt16)HTTP2Settings.ENABLE_PUSH] = 1;
|
||||
this.values[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = 128;
|
||||
this.values[(UInt16)HTTP2Settings.INITIAL_WINDOW_SIZE] = 65535;
|
||||
this.values[(UInt16)HTTP2Settings.MAX_FRAME_SIZE] = 16384;
|
||||
this.values[(UInt16)HTTP2Settings.MAX_HEADER_LIST_SIZE] = UInt32.MaxValue; // infinite
|
||||
|
||||
if (this.IsChanged = treatItAsAlreadyChanged)
|
||||
{
|
||||
this.changeFlags[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the specified settings into the current registry.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to merge.</param>
|
||||
public void Merge(List<KeyValuePair<HTTP2Settings, UInt32>> settings)
|
||||
{
|
||||
if (settings == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < settings.Count; ++i)
|
||||
{
|
||||
HTTP2Settings setting = settings[i].Key;
|
||||
UInt16 key = (UInt16)setting;
|
||||
UInt32 value = settings[i].Value;
|
||||
|
||||
if (key >= this.values.Length)
|
||||
Array.Resize<uint>(ref this.values, (int)key + 1);
|
||||
|
||||
UInt32 oldValue = this.values[key];
|
||||
this.values[key] = value;
|
||||
|
||||
if (oldValue != value && this.OnSettingChangedEvent != null)
|
||||
this.OnSettingChangedEvent(this, setting, oldValue, value);
|
||||
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Information("HTTP2SettingsRegistry", string.Format("Merge {0}({1}) = {2}", setting, key, value), this._parent.Context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges settings from another HTTP2SettingsRegistry into the current registry.
|
||||
/// </summary>
|
||||
/// <param name="from">The registry to merge settings from.</param>
|
||||
public void Merge(HTTP2SettingsRegistry from)
|
||||
{
|
||||
if (this.values != null)
|
||||
this.values = new uint[from.values.Length];
|
||||
|
||||
for (int i = 0; i < this.values.Length; ++i)
|
||||
this.values[i] = from.values[i];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new HTTP/2 frame based on the current registry settings.
|
||||
/// </summary>
|
||||
/// <param name="context">The logging context.</param>
|
||||
/// <returns>A new HTTP/2 frame.</returns>
|
||||
internal HTTP2FrameHeaderAndPayload CreateFrame(LoggingContext context)
|
||||
{
|
||||
List<KeyValuePair<HTTP2Settings, UInt32>> keyValuePairs = new List<KeyValuePair<HTTP2Settings, uint>>(HTTP2SettingsManager.KnownSettingsCount);
|
||||
|
||||
for (int i = 1; i < HTTP2SettingsManager.KnownSettingsCount; ++i)
|
||||
if (this.changeFlags[i])
|
||||
{
|
||||
keyValuePairs.Add(new KeyValuePair<HTTP2Settings, uint>((HTTP2Settings)i, this[(HTTP2Settings)i]));
|
||||
this.changeFlags[i] = false;
|
||||
}
|
||||
|
||||
this.IsChanged = false;
|
||||
|
||||
return HTTP2FrameHelper.CreateSettingsFrame(keyValuePairs, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class to manager local and remote HTTP/2 settings.
|
||||
/// </summary>
|
||||
public sealed class HTTP2SettingsManager
|
||||
{
|
||||
public static readonly int KnownSettingsCount = Enum.GetNames(typeof(HTTP2Settings)).Length + 1;
|
||||
|
||||
/// <summary>
|
||||
/// This is the ACKd or default settings that we sent to the server.
|
||||
/// </summary>
|
||||
public HTTP2SettingsRegistry MySettings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is the setting that can be changed. It will be sent to the server ASAP, and when ACKd, it will be copied
|
||||
/// to MySettings.
|
||||
/// </summary>
|
||||
public HTTP2SettingsRegistry InitiatedMySettings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Settings of the remote peer
|
||||
/// </summary>
|
||||
public HTTP2SettingsRegistry RemoteSettings { get; private set; }
|
||||
|
||||
public DateTime SettingsChangesSentAt { get; private set; }
|
||||
|
||||
public LoggingContext Context { get; private set; }
|
||||
|
||||
private HTTP2ConnectionSettings _connectionSettings;
|
||||
|
||||
public HTTP2SettingsManager(LoggingContext context, HTTP2ConnectionSettings connectionSettings)
|
||||
{
|
||||
this.Context = context;
|
||||
this._connectionSettings = connectionSettings;
|
||||
|
||||
this.MySettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
|
||||
this.InitiatedMySettings = new HTTP2SettingsRegistry(this, readOnly: false, treatItAsAlreadyChanged: true);
|
||||
this.RemoteSettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
|
||||
this.SettingsChangesSentAt = DateTime.MinValue;
|
||||
}
|
||||
|
||||
internal void Process(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
if (frame.Type != HTTP2FrameTypes.SETTINGS)
|
||||
return;
|
||||
|
||||
HTTP2SettingsFrame settingsFrame = HTTP2FrameHelper.ReadSettings(frame);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HTTP2SettingsManager", "Processing Settings frame: " + settingsFrame.ToString(), this.Context);
|
||||
|
||||
if ((settingsFrame.Flags & HTTP2SettingsFlags.ACK) == HTTP2SettingsFlags.ACK)
|
||||
{
|
||||
this.MySettings.Merge(this.InitiatedMySettings);
|
||||
this.SettingsChangesSentAt = DateTime.MinValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RemoteSettings.Merge(settingsFrame.Settings);
|
||||
outgoingFrames.Add(HTTP2FrameHelper.CreateACKSettingsFrame());
|
||||
}
|
||||
}
|
||||
|
||||
internal void SendChanges(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
if (this.SettingsChangesSentAt != DateTime.MinValue && DateTime.UtcNow - this.SettingsChangesSentAt >= this._connectionSettings.Timeout)
|
||||
{
|
||||
HTTPManager.Logger.Error("HTTP2SettingsManager", "No ACK received for settings frame!", this.Context);
|
||||
this.SettingsChangesSentAt = DateTime.MinValue;
|
||||
}
|
||||
|
||||
// Upon receiving a SETTINGS frame with the ACK flag set, the sender of the altered parameters can rely on the setting having been applied.
|
||||
if (!this.InitiatedMySettings.IsChanged)
|
||||
return;
|
||||
|
||||
outgoingFrames.Add(this.InitiatedMySettings.CreateFrame(this.Context));
|
||||
this.SettingsChangesSentAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9723be24bbd74214dbff4fc66a5b24bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,663 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
#define ENABLE_LOGGING
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Best.HTTP.Request.Upload;
|
||||
using Best.HTTP.Request.Timings;
|
||||
using Best.HTTP.Response;
|
||||
using Best.HTTP.Shared;
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#StreamStates
|
||||
//
|
||||
// Idle
|
||||
// |
|
||||
// V
|
||||
// Open
|
||||
// Receive END_STREAM / | \ Send END_STREAM
|
||||
// v |R V
|
||||
// Half Closed Remote |S Half Closed Locale
|
||||
// \ |T /
|
||||
// Send END_STREAM | RST_STREAM \ | / Receive END_STREAM | RST_STREAM
|
||||
// Receive RST_STREAM \ | / Send RST_STREAM
|
||||
// V
|
||||
// Closed
|
||||
//
|
||||
// IDLE -> send headers -> OPEN -> send data -> HALF CLOSED - LOCAL -> receive headers -> receive Data -> CLOSED
|
||||
// | ^ | ^
|
||||
// +-------------------------------------+ +-----------------------------+
|
||||
// END_STREAM flag present? END_STREAM flag present?
|
||||
//
|
||||
|
||||
public enum HTTP2StreamStates
|
||||
{
|
||||
Idle,
|
||||
//ReservedLocale,
|
||||
//ReservedRemote,
|
||||
Open,
|
||||
HalfClosedLocal,
|
||||
HalfClosedRemote,
|
||||
Closed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements an HTTP/2 logical stream.
|
||||
/// </summary>
|
||||
public class HTTP2Stream : IDownloadContentBufferAvailable
|
||||
{
|
||||
public UInt32 Id { get; private set; }
|
||||
|
||||
public HTTP2StreamStates State {
|
||||
get { return this._state; }
|
||||
|
||||
protected set {
|
||||
var oldState = this._state;
|
||||
|
||||
this._state = value;
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
if (oldState != this._state && HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] State changed from {1} to {2}", this.Id, oldState, this._state), this.Context);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
private HTTP2StreamStates _state;
|
||||
|
||||
/// <summary>
|
||||
/// This flag is checked by the connection to decide whether to do a new processing-frame sending round before sleeping until new data arrives
|
||||
/// </summary>
|
||||
public virtual bool HasFrameToSend
|
||||
{
|
||||
get
|
||||
{
|
||||
// Don't let the connection sleep until
|
||||
return this.outgoing.Count > 0 || // we already booked at least one frame in advance
|
||||
(this.State == HTTP2StreamStates.Open && this.remoteWindow > 0 && this.lastReadCount > 0); // we are in the middle of sending request data
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Next interaction scheduled by the stream relative to *now*. Its default is TimeSpan.MaxValue == no interaction.
|
||||
/// </summary>
|
||||
public virtual TimeSpan NextInteraction { get; } = TimeSpan.MaxValue;
|
||||
|
||||
public HTTPRequest AssignedRequest { get; protected set; }
|
||||
|
||||
public LoggingContext Context { get; protected set; }
|
||||
|
||||
public HTTP2ContentConsumer ParentHandler { get; private set; }
|
||||
|
||||
protected uint downloaded;
|
||||
|
||||
protected HTTP2SettingsManager settings;
|
||||
protected HPACKEncoder encoder;
|
||||
|
||||
// Outgoing frames. The stream will send one frame per Process call, but because one step might be able to
|
||||
// generate more than one frames, we use a list.
|
||||
protected Queue<HTTP2FrameHeaderAndPayload> outgoing = new Queue<HTTP2FrameHeaderAndPayload>();
|
||||
|
||||
protected Queue<HTTP2FrameHeaderAndPayload> incomingFrames = new Queue<HTTP2FrameHeaderAndPayload>();
|
||||
|
||||
protected FramesAsStreamView headerView;
|
||||
|
||||
protected Int64 localWindow;
|
||||
protected Int64 remoteWindow;
|
||||
|
||||
protected uint windowUpdateThreshold;
|
||||
|
||||
protected UInt32 assignDataLength;
|
||||
|
||||
protected long sentData;
|
||||
protected long uploadLength;
|
||||
|
||||
protected bool isRSTFrameSent;
|
||||
protected bool isEndSTRReceived;
|
||||
|
||||
protected HTTP2Response response;
|
||||
|
||||
protected int lastReadCount;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to create a client stream.
|
||||
/// </summary>
|
||||
public HTTP2Stream(UInt32 id, HTTP2ContentConsumer parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder)
|
||||
{
|
||||
this.Id = id;
|
||||
this.ParentHandler = parentHandler;
|
||||
this.settings = registry;
|
||||
this.encoder = hpackEncoder;
|
||||
|
||||
this.Context = new LoggingContext(this);
|
||||
this.Context.Add("id", id);
|
||||
this.Context.Add("Parent", parentHandler.Context);
|
||||
|
||||
this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
|
||||
this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
|
||||
|
||||
// Room for improvement: If INITIAL_WINDOW_SIZE is small (what we can consider a 'small' value?), threshold must be higher
|
||||
this.windowUpdateThreshold = (uint)(this.remoteWindow / 2);
|
||||
}
|
||||
|
||||
public virtual void Assign(HTTPRequest request)
|
||||
{
|
||||
this.Context.Add("Request", request.Context);
|
||||
|
||||
request.Timing.StartNext(TimingEventNames.Request_Sent);
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Request assigned to stream. Remote Window: {1:N0}. Uri: {2}", this.Id, this.remoteWindow, request.CurrentUri.ToString()), this.Context);
|
||||
#endif
|
||||
this.AssignedRequest = request;
|
||||
|
||||
this.downloaded = 0;
|
||||
}
|
||||
|
||||
public void Process(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
if (this.AssignedRequest.IsCancellationRequested && !this.isRSTFrameSent)
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", $"[{this.Id}] Process({this.State}) - IsCancellationRequested", this.Context);
|
||||
#endif
|
||||
|
||||
// These two are already set in HTTPRequest's Abort().
|
||||
//this.AssignedRequest.Response = null;
|
||||
//this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
|
||||
|
||||
this.outgoing.Clear();
|
||||
if (this.State != HTTP2StreamStates.Idle)
|
||||
this.outgoing.Enqueue(HTTP2FrameHelper.CreateRSTFrame(this.Id, HTTP2ErrorCodes.CANCEL, this.Context));
|
||||
|
||||
// We can close the stream if already received headers, or not even sent one
|
||||
if (this.State == HTTP2StreamStates.HalfClosedRemote || this.State == HTTP2StreamStates.HalfClosedLocal || this.State == HTTP2StreamStates.Idle)
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
|
||||
this.isRSTFrameSent = true;
|
||||
}
|
||||
|
||||
// 1.) Go through incoming frames
|
||||
ProcessIncomingFrames(outgoingFrames);
|
||||
|
||||
// 2.) Create outgoing frames based on the stream's state and the request processing state.
|
||||
ProcessState(outgoingFrames);
|
||||
|
||||
// 3.) Send one frame per Process call
|
||||
if (this.outgoing.Count > 0)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frame = this.outgoing.Dequeue();
|
||||
|
||||
outgoingFrames.Add(frame);
|
||||
|
||||
// If END_Stream in header or data frame is present => half closed local
|
||||
if ((frame.Type == HTTP2FrameTypes.HEADERS && (frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0) ||
|
||||
(frame.Type == HTTP2FrameTypes.DATA && (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0))
|
||||
{
|
||||
this.State = HTTP2StreamStates.HalfClosedLocal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFrame(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
// Room for improvement: error check for forbidden frames (like settings) and stream state
|
||||
|
||||
this.incomingFrames.Enqueue(frame);
|
||||
|
||||
ProcessIncomingFrames(outgoingFrames);
|
||||
}
|
||||
|
||||
public void Abort(string msg)
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", $"[{this.Id}] Abort(\"{msg}\", {this.State}, {this.AssignedRequest.State})", this.Context);
|
||||
#endif
|
||||
|
||||
if (this.State != HTTP2StreamStates.Closed)
|
||||
{
|
||||
// TODO: Remove AssignedRequest.State checks. If the main thread has delays processing queued up state change requests,
|
||||
// the request's State can contain old information!
|
||||
|
||||
if (this.AssignedRequest.State != HTTPRequestStates.Processing)
|
||||
{
|
||||
// do nothing, its state is already set.
|
||||
}
|
||||
else if (this.AssignedRequest.IsCancellationRequested)
|
||||
{
|
||||
// These two are already set in HTTPRequest's Abort().
|
||||
//this.AssignedRequest.Response = null;
|
||||
//this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
|
||||
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
}
|
||||
else if (this.AssignedRequest.RetrySettings.Retries >= this.AssignedRequest.RetrySettings.MaxRetries || HTTPManager.IsQuitting)
|
||||
{
|
||||
this.AssignedRequest.Timing.StartNext(TimingEventNames.Queued);
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, HTTPRequestStates.Error, new Exception(msg)));
|
||||
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.AssignedRequest.RetrySettings.Retries++;
|
||||
|
||||
this.AssignedRequest.Response?.Dispose();
|
||||
this.AssignedRequest.Response = null;
|
||||
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.Resend));
|
||||
}
|
||||
}
|
||||
|
||||
this.Removed();
|
||||
}
|
||||
|
||||
protected void ProcessIncomingFrames(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
while (this.incomingFrames.Count > 0)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frame = this.incomingFrames.Dequeue();
|
||||
|
||||
if ((this.isRSTFrameSent || this.AssignedRequest.IsCancellationRequested) && frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
|
||||
{
|
||||
BufferPool.Release(frame.Payload);
|
||||
continue;
|
||||
}
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
if (/*HTTPManager.Logger.Level == Logger.Loglevels.All && */frame.Type != HTTP2FrameTypes.DATA && frame.Type != HTTP2FrameTypes.WINDOW_UPDATE)
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Process - processing frame: {1}", this.Id, frame.ToString()), this.Context);
|
||||
#endif
|
||||
|
||||
switch (frame.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.HEADERS:
|
||||
case HTTP2FrameTypes.CONTINUATION:
|
||||
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open && this.State != HTTP2StreamStates.Idle)
|
||||
{
|
||||
// ERROR!
|
||||
continue;
|
||||
}
|
||||
|
||||
// payload will be released by the view
|
||||
frame.DontUseMemPool = true;
|
||||
|
||||
if (this.headerView == null)
|
||||
{
|
||||
this.AssignedRequest.Timing.StartNext(TimingEventNames.Headers);
|
||||
|
||||
this.headerView = new FramesAsStreamView(new HeaderFrameView());
|
||||
}
|
||||
|
||||
this.headerView.AddFrame(frame);
|
||||
|
||||
// END_STREAM may arrive sooner than an END_HEADERS, so we have to store that we already received it
|
||||
if ((frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0)
|
||||
this.isEndSTRReceived = true;
|
||||
|
||||
if ((frame.Flags & (byte)HTTP2HeadersFlags.END_HEADERS) != 0)
|
||||
{
|
||||
List<KeyValuePair<string, string>> headers = new List<KeyValuePair<string, string>>();
|
||||
|
||||
try
|
||||
{
|
||||
this.encoder.Decode(this, this.headerView, headers);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("HTTP2Stream", string.Format("[{0}] ProcessIncomingFrames - Header Frames: {1}, Encoder: {2}", this.Id, this.headerView.ToString(), this.encoder.ToString()), ex, this.Context);
|
||||
}
|
||||
|
||||
this.headerView.Close();
|
||||
this.headerView = null;
|
||||
|
||||
this.AssignedRequest.Timing.StartNext(TimingEventNames.Response_Received);
|
||||
|
||||
if (this.isRSTFrameSent)
|
||||
{
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.response == null)
|
||||
this.AssignedRequest.Response = this.response = new HTTP2Response(this.AssignedRequest, false);
|
||||
|
||||
this.response.AddHeaders(headers);
|
||||
|
||||
if (this.isEndSTRReceived)
|
||||
{
|
||||
// If there's any trailing header, no data frame has an END_STREAM flag
|
||||
this.response.FinishProcessData();
|
||||
|
||||
FinishRequest();
|
||||
|
||||
if (this.State == HTTP2StreamStates.HalfClosedLocal)
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
else
|
||||
this.State = HTTP2StreamStates.HalfClosedRemote;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.DATA:
|
||||
ProcessIncomingDATAFrame(ref frame);
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.WINDOW_UPDATE:
|
||||
HTTP2WindowUpdateFrame windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(frame);
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Received Window Update: {1:N0}, new remoteWindow: {2:N0}, initial remote window: {3:N0}, total data sent: {4:N0}", this.Id, windowUpdateFrame.WindowSizeIncrement, this.remoteWindow + windowUpdateFrame.WindowSizeIncrement, this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE], this.sentData), this.Context);
|
||||
#endif
|
||||
|
||||
this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.RST_STREAM:
|
||||
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
|
||||
|
||||
// It's possible to receive an RST_STREAM on a closed stream. In this case, we have to ignore it.
|
||||
if (this.State == HTTP2StreamStates.Closed)
|
||||
break;
|
||||
|
||||
var rstStreamFrame = HTTP2FrameHelper.ReadRST_StreamFrame(frame);
|
||||
|
||||
//HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] RST Stream frame ({1}) received in state {2}!", this.Id, rstStreamFrame, this.State), this.Context);
|
||||
|
||||
Abort(string.Format("RST_STREAM frame received! Error code: {0}({1})", rstStreamFrame.Error.ToString(), rstStreamFrame.ErrorCode));
|
||||
break;
|
||||
|
||||
default:
|
||||
HTTPManager.Logger.Warning("HTTP2Stream", string.Format("[{0}] Unexpected frame ({1}, Payload: {2}) in state {3}!", this.Id, frame, frame.PayloadAsHex(), this.State), this.Context);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!frame.DontUseMemPool)
|
||||
BufferPool.Release(frame.Payload);
|
||||
}
|
||||
}
|
||||
|
||||
void IDownloadContentBufferAvailable.BufferAvailable(DownloadContentStream stream)
|
||||
{
|
||||
// Signal the http2 thread, window update will be sent out in ProcessOpenState.
|
||||
this.ParentHandler.SignalThread(SignalHandlerTypes.Signal);
|
||||
}
|
||||
|
||||
protected virtual void ProcessIncomingDATAFrame(ref HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open)
|
||||
{
|
||||
// ERROR!
|
||||
return;
|
||||
}
|
||||
|
||||
HTTP2DataFrame dataFrame = HTTP2FrameHelper.ReadDataFrame(frame);
|
||||
|
||||
this.downloaded += (uint)dataFrame.Data.Count;
|
||||
|
||||
this.response.Prepare(this);
|
||||
|
||||
this.response.ProcessData(dataFrame.Data);
|
||||
frame.DontUseMemPool = true;
|
||||
|
||||
// Because of padding, frame.Payload.Count can be larger than dataFrame.Data.Count!
|
||||
// "The entire DATA frame payload is included in flow control, including the Pad Length and Padding fields if present."
|
||||
this.localWindow -= frame.Payload.Count;
|
||||
|
||||
this.isEndSTRReceived = (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0;
|
||||
|
||||
if (this.isEndSTRReceived)
|
||||
{
|
||||
this.response.FinishProcessData();
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded), this.Context);
|
||||
#endif
|
||||
|
||||
FinishRequest();
|
||||
|
||||
if (this.State == HTTP2StreamStates.HalfClosedLocal)
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
else
|
||||
this.State = HTTP2StreamStates.HalfClosedRemote;
|
||||
}
|
||||
else if (this.AssignedRequest.DownloadSettings.OnDownloadProgress != null)
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest,
|
||||
RequestEvents.DownloadProgress,
|
||||
downloaded,
|
||||
this.response.ExpectedContentLength));
|
||||
}
|
||||
|
||||
protected void ProcessState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
switch (this.State)
|
||||
{
|
||||
case HTTP2StreamStates.Idle:
|
||||
// hpack encode the request's headers
|
||||
this.encoder.Encode(this, this.AssignedRequest, this.outgoing, this.Id);
|
||||
|
||||
// HTTP/2 uses DATA frames to carry message payloads.
|
||||
// The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
|
||||
|
||||
if (this.AssignedRequest.UploadSettings.UploadStream == null)
|
||||
{
|
||||
this.State = HTTP2StreamStates.HalfClosedLocal;
|
||||
//this.AssignedRequest.Timing.Finish(TimingEventNames.Request_Sent);
|
||||
this.AssignedRequest.Timing.StartNext(TimingEventNames.Waiting_TTFB);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.State = HTTP2StreamStates.Open;
|
||||
this.lastReadCount = 1;
|
||||
|
||||
if (this.AssignedRequest.UploadSettings.UploadStream is UploadStreamBase upStream)
|
||||
upStream.BeforeSendBody(this.AssignedRequest, this.ParentHandler);
|
||||
|
||||
if (this.AssignedRequest.UploadSettings != null && this.AssignedRequest.UploadSettings.UploadStream != null)
|
||||
this.uploadLength = this.AssignedRequest.UploadSettings.UploadStream.Length;
|
||||
}
|
||||
|
||||
// Change the initial window size to the request's DownloadSettings.ContentStreamMaxBuffered and send it to the server.
|
||||
// After this initial setup the sending out window_update frames faces two problems:
|
||||
// 1.) The local window should be bound to the Down-stream's MaxBuffered (ContentStreamMaxBuffered) and how its current length.
|
||||
// 2.) Even while the its bound to the stream's current values, when the download finishes, we still have to update the global window.
|
||||
// So, there's two options to follow:
|
||||
// 1.) Update the local window based on the stream's usage
|
||||
// 2.a) Send global window_update for every DATA frame processed/received
|
||||
|
||||
UInt32 initiatedInitialWindowSize = this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
|
||||
this.localWindow = initiatedInitialWindowSize;
|
||||
|
||||
// Maximize max buffered to HTTP/2's limit
|
||||
if (this.AssignedRequest.DownloadSettings.ContentStreamMaxBuffered > HTTP2ContentConsumer.MaxValueFor31Bits)
|
||||
this.AssignedRequest.DownloadSettings.ContentStreamMaxBuffered = HTTP2ContentConsumer.MaxValueFor31Bits;
|
||||
|
||||
long localWindowDiff = this.AssignedRequest.DownloadSettings.ContentStreamMaxBuffered - this.localWindow;
|
||||
|
||||
if (localWindowDiff > 0)
|
||||
{
|
||||
this.localWindow += localWindowDiff;
|
||||
this.outgoing.Enqueue(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, (UInt32)localWindowDiff, this.Context));
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP2StreamStates.Open:
|
||||
ProcessOpenState(outgoingFrames);
|
||||
//HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] New DATA frame created! remoteWindow: {1:N0}", this.Id, this.remoteWindow), this.Context);
|
||||
break;
|
||||
|
||||
case HTTP2StreamStates.HalfClosedLocal:
|
||||
if (this.response?.DownStream != null)
|
||||
{
|
||||
var windowIncrement = this.response.DownStream.MaxBuffered - this.localWindow - this.response.DownStream.Length;
|
||||
if (windowIncrement > 0)
|
||||
{
|
||||
this.localWindow += windowIncrement;
|
||||
outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, (UInt32)windowIncrement, this.Context));
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", $"[{this.Id}] Sending window inc. update: {windowIncrement:N0}, {this.localWindow:N0}/{this.response.DownStream.MaxBuffered:N0}", this.Context);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP2StreamStates.HalfClosedRemote:
|
||||
break;
|
||||
|
||||
case HTTP2StreamStates.Closed:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ProcessOpenState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
// remote Window can be negative! See https://httpwg.org/specs/rfc7540.html#InitialWindowSize
|
||||
if (this.remoteWindow <= 0)
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Skipping data sending as remote Window is {1}!", this.Id, this.remoteWindow), this.Context);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// This step will send one frame per ProcessOpenState call.
|
||||
|
||||
Int64 maxFrameSize = Math.Min(this.AssignedRequest.UploadSettings.UploadChunkSize, Math.Min(this.remoteWindow, this.settings.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE]));
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.DATA;
|
||||
frame.StreamId = this.Id;
|
||||
|
||||
frame.Payload = BufferPool.Get(maxFrameSize, true)
|
||||
.AsBuffer((int)maxFrameSize);
|
||||
|
||||
// Expect a readCount of zero if it's end of the stream. But, to enable non-blocking scenario to wait for data, going to treat a negative value as no data.
|
||||
this.lastReadCount = this.AssignedRequest.UploadSettings.UploadStream.Read(frame.Payload.Data, 0, (int)Math.Min(maxFrameSize, int.MaxValue));
|
||||
if (this.lastReadCount <= 0)
|
||||
{
|
||||
BufferPool.Release(frame.Payload);
|
||||
|
||||
frame.Payload = BufferSegment.Empty;
|
||||
|
||||
if (this.lastReadCount < 0)
|
||||
return;
|
||||
}
|
||||
else
|
||||
frame.Payload = frame.Payload.Slice(0, this.lastReadCount);
|
||||
|
||||
frame.DontUseMemPool = false;
|
||||
|
||||
if (this.lastReadCount <= 0)
|
||||
{
|
||||
this.AssignedRequest.UploadSettings.Dispose();
|
||||
|
||||
frame.Flags = (byte)(HTTP2DataFlags.END_STREAM);
|
||||
|
||||
this.State = HTTP2StreamStates.HalfClosedLocal;
|
||||
|
||||
this.AssignedRequest.Timing.StartNext(TimingEventNames.Waiting_TTFB);
|
||||
}
|
||||
|
||||
this.outgoing.Enqueue(frame);
|
||||
|
||||
this.remoteWindow -= frame.Payload.Count;
|
||||
|
||||
this.sentData += (uint)frame.Payload.Count;
|
||||
|
||||
if (this.AssignedRequest.UploadSettings.OnUploadProgress != null)
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.UploadProgress, this.sentData, uploadLength));
|
||||
}
|
||||
|
||||
protected void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
|
||||
{
|
||||
switch (setting)
|
||||
{
|
||||
case HTTP2Settings.INITIAL_WINDOW_SIZE:
|
||||
// https://httpwg.org/specs/rfc7540.html#InitialWindowSize
|
||||
// "Prior to receiving a SETTINGS frame that sets a value for SETTINGS_INITIAL_WINDOW_SIZE,
|
||||
// an endpoint can only use the default initial window size when sending flow-controlled frames."
|
||||
// "In addition to changing the flow-control window for streams that are not yet active,
|
||||
// a SETTINGS frame can alter the initial flow-control window size for streams with active flow-control windows
|
||||
// (that is, streams in the "open" or "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE changes,
|
||||
// a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value."
|
||||
|
||||
// So, if we created a stream before the remote peer's initial settings frame is received, we
|
||||
// will adjust the window size. For example: initial window size by default is 65535, if we later
|
||||
// receive a change to 1048576 (1 MB) we will increase the current remoteWindow by (1 048 576 - 65 535 =) 983 041
|
||||
|
||||
// But because initial window size in a setting frame can be smaller then the default 65535 bytes,
|
||||
// the difference can be negative:
|
||||
// "A change to SETTINGS_INITIAL_WINDOW_SIZE can cause the available space in a flow-control window to become negative.
|
||||
// A sender MUST track the negative flow-control window and MUST NOT send new flow-controlled frames
|
||||
// until it receives WINDOW_UPDATE frames that cause the flow-control window to become positive.
|
||||
|
||||
// For example, if the client sends 60 KB immediately on connection establishment
|
||||
// and the server sets the initial window size to be 16 KB, the client will recalculate
|
||||
// the available flow - control window to be - 44 KB on receipt of the SETTINGS frame.
|
||||
// The client retains a negative flow-control window until WINDOW_UPDATE frames restore the
|
||||
// window to being positive, after which the client can resume sending."
|
||||
|
||||
this.remoteWindow += newValue - oldValue;
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Remote Setting's Initial Window Updated from {1:N0} to {2:N0}, diff: {3:N0}, new remoteWindow: {4:N0}, total data sent: {5:N0}", this.Id, oldValue, newValue, newValue - oldValue, this.remoteWindow, this.sentData), this.Context);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void FinishRequest()
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", $"FinishRequest({this.Id})", this.Context);
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
this.AssignedRequest.Timing.StartNext(TimingEventNames.Queued);
|
||||
|
||||
bool resendRequest;
|
||||
HTTPConnectionStates proposedConnectionStates; // ignored
|
||||
KeepAliveHeader keepAliveHeader = null; // ignored
|
||||
|
||||
ConnectionHelper.HandleResponse(this.AssignedRequest, out resendRequest, out proposedConnectionStates, ref keepAliveHeader, this.Context);
|
||||
|
||||
if (resendRequest && !this.AssignedRequest.IsCancellationRequested)
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.Resend));
|
||||
else
|
||||
{
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, HTTPRequestStates.Finished, null));
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("HTTP2Stream", "FinishRequest", ex, this.Context);
|
||||
}
|
||||
}
|
||||
|
||||
public void Removed()
|
||||
{
|
||||
this.AssignedRequest.UploadSettings.Dispose();
|
||||
|
||||
// After receiving a RST_STREAM on a stream, the receiver MUST NOT send additional frames for that stream, with the exception of PRIORITY.
|
||||
this.outgoing.Clear();
|
||||
|
||||
// https://github.com/Benedicht/BestHTTP-Issues/issues/77
|
||||
// Unsubscribe from OnSettingChangedEvent to remove reference to this instance.
|
||||
this.settings.RemoteSettings.OnSettingChangedEvent -= OnRemoteSettingChanged;
|
||||
|
||||
this.headerView?.Close();
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
HTTPManager.Logger.Information("HTTP2Stream", "Stream removed: " + this.Id.ToString(), this.Context);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2eadc0f2891358d4c9754796420a8e15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,201 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Best.HTTP.Shared.PlatformSupport.Text;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
sealed class HeaderTable
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#static.table.definition
|
||||
// Valid indexes starts with 1, so there's an empty entry.
|
||||
static string[] StaticTableValues = new string[] { string.Empty, string.Empty, "GET", "POST", "/", "/index.html", "http", "https", "200", "204", "206", "304", "400", "404", "500", string.Empty, "gzip, deflate" };
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#static.table.definition
|
||||
// Valid indexes starts with 1, so there's an empty entry.
|
||||
static string[] StaticTable = new string[62]
|
||||
{
|
||||
string.Empty,
|
||||
":authority",
|
||||
":method", // GET
|
||||
":method", // POST
|
||||
":path", // /
|
||||
":path", // index.html
|
||||
":scheme", // http
|
||||
":scheme", // https
|
||||
":status", // 200
|
||||
":status", // 204
|
||||
":status", // 206
|
||||
":status", // 304
|
||||
":status", // 400
|
||||
":status", // 404
|
||||
":status", // 500
|
||||
"accept-charset",
|
||||
"accept-encoding", // gzip, deflate
|
||||
"accept-language",
|
||||
"accept-ranges",
|
||||
"accept",
|
||||
"access-control-allow-origin",
|
||||
"age",
|
||||
"allow",
|
||||
"authorization",
|
||||
"cache-control",
|
||||
"content-disposition",
|
||||
"content-encoding",
|
||||
"content-language",
|
||||
"content-length",
|
||||
"content-location",
|
||||
"content-range",
|
||||
"content-type",
|
||||
"cookie",
|
||||
"date",
|
||||
"etag",
|
||||
"expect",
|
||||
"expires",
|
||||
"from",
|
||||
"host",
|
||||
"if-match",
|
||||
"if-modified-since",
|
||||
"if-none-match",
|
||||
"if-range",
|
||||
"if-unmodified-since",
|
||||
"last-modified",
|
||||
"link",
|
||||
"location",
|
||||
"max-forwards",
|
||||
"proxy-authenticate",
|
||||
"proxy-authorization",
|
||||
"range",
|
||||
"referer",
|
||||
"refresh",
|
||||
"retry-after",
|
||||
"server",
|
||||
"set-cookie",
|
||||
"strict-transport-security",
|
||||
"transfer-encoding",
|
||||
"user-agent",
|
||||
"vary",
|
||||
"via",
|
||||
"www-authenticate",
|
||||
};
|
||||
|
||||
public UInt32 DynamicTableSize { get; private set; }
|
||||
public UInt32 MaxDynamicTableSize {
|
||||
get { return this._maxDynamicTableSize; }
|
||||
set
|
||||
{
|
||||
this._maxDynamicTableSize = value;
|
||||
EvictEntries(0);
|
||||
}
|
||||
}
|
||||
private UInt32 _maxDynamicTableSize;
|
||||
|
||||
private List<KeyValuePair<string, string>> DynamicTable = new List<KeyValuePair<string, string>>();
|
||||
private HTTP2SettingsRegistry settingsRegistry;
|
||||
|
||||
public HeaderTable(HTTP2SettingsRegistry registry)
|
||||
{
|
||||
this.settingsRegistry = registry;
|
||||
this.MaxDynamicTableSize = this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE];
|
||||
}
|
||||
|
||||
public KeyValuePair<UInt32, UInt32> GetIndex(string key, string value)
|
||||
{
|
||||
for (int i = 0; i < DynamicTable.Count; ++i)
|
||||
{
|
||||
var kvp = DynamicTable[i];
|
||||
|
||||
// Exact match for both key and value
|
||||
if (kvp.Key.Equals(key, StringComparison.OrdinalIgnoreCase) && kvp.Value.Equals(value, StringComparison.OrdinalIgnoreCase))
|
||||
return new KeyValuePair<UInt32, UInt32>((UInt32)(StaticTable.Length + i), (UInt32)(StaticTable.Length + i));
|
||||
}
|
||||
|
||||
KeyValuePair<UInt32, UInt32> bestMatch = new KeyValuePair<UInt32, UInt32>(0, 0);
|
||||
for (int i = 0; i < StaticTable.Length; ++i)
|
||||
{
|
||||
if (StaticTable[i].Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (i < StaticTableValues.Length && !string.IsNullOrEmpty(StaticTableValues[i]) && StaticTableValues[i].Equals(value, StringComparison.OrdinalIgnoreCase))
|
||||
return new KeyValuePair<UInt32, UInt32>((UInt32)i, (UInt32)i);
|
||||
else
|
||||
bestMatch = new KeyValuePair<UInt32, UInt32>((UInt32)i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
public string GetKey(UInt32 index)
|
||||
{
|
||||
if (index < StaticTable.Length)
|
||||
return StaticTable[index];
|
||||
|
||||
return this.DynamicTable[(int)(index - StaticTable.Length)].Key;
|
||||
}
|
||||
|
||||
public KeyValuePair<string, string> GetHeader(UInt32 index)
|
||||
{
|
||||
if (index < StaticTable.Length)
|
||||
return new KeyValuePair<string, string>(StaticTable[index],
|
||||
index < StaticTableValues.Length ? StaticTableValues[index] : null);
|
||||
|
||||
return this.DynamicTable[(int)(index - StaticTable.Length)];
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, string> header)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#calculating.table.size
|
||||
// The size of an entry is the sum of its name's length in octets (as defined in Section 5.2),
|
||||
// its value's length in octets, and 32.
|
||||
UInt32 newHeaderSize = CalculateEntrySize(header);
|
||||
|
||||
EvictEntries(newHeaderSize);
|
||||
|
||||
// If the size of the new entry is less than or equal to the maximum size, that entry is added to the table.
|
||||
// It is not an error to attempt to add an entry that is larger than the maximum size;
|
||||
// an attempt to add an entry larger than the maximum size causes the table to be
|
||||
// emptied of all existing entries and results in an empty table.
|
||||
if (this.DynamicTableSize + newHeaderSize <= this.MaxDynamicTableSize)
|
||||
{
|
||||
this.DynamicTable.Insert(0, header);
|
||||
this.DynamicTableSize += (UInt32)newHeaderSize;
|
||||
}
|
||||
}
|
||||
|
||||
private UInt32 CalculateEntrySize(KeyValuePair<string, string> entry)
|
||||
{
|
||||
return 32 + (UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Key) +
|
||||
(UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Value);
|
||||
}
|
||||
|
||||
private void EvictEntries(uint newHeaderSize)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#entry.addition
|
||||
// Before a new entry is added to the dynamic table, entries are evicted from the end of the dynamic
|
||||
// table until the size of the dynamic table is less than or equal to (maximum size - new entry size) or until the table is empty.
|
||||
while (this.DynamicTableSize + newHeaderSize > this.MaxDynamicTableSize && this.DynamicTable.Count > 0)
|
||||
{
|
||||
KeyValuePair<string, string> entry = this.DynamicTable[this.DynamicTable.Count - 1];
|
||||
this.DynamicTable.RemoveAt(this.DynamicTable.Count - 1);
|
||||
this.DynamicTableSize -= CalculateEntrySize(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
System.Text.StringBuilder sb = StringBuilderPool.Get(this.DynamicTable.Count + 3);
|
||||
sb.Append("[HeaderTable ");
|
||||
sb.AppendFormat("DynamicTable count: {0}, DynamicTableSize: {1}, MaxDynamicTableSize: {2}, ", this.DynamicTable.Count, this.DynamicTableSize, this.MaxDynamicTableSize);
|
||||
|
||||
foreach(var kvp in this.DynamicTable)
|
||||
sb.AppendFormat("\"{0}\": \"{1}\", ", kvp.Key, kvp.Value);
|
||||
|
||||
sb.Append("]");
|
||||
return StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a0cd78904e5b8e4095111b9a2d439e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,933 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
|
||||
using System;
|
||||
|
||||
namespace Best.HTTP.Hosts.Connections.HTTP2
|
||||
{
|
||||
/// <summary>
|
||||
/// A pre-generated table entry in a Huffman-Tree.
|
||||
/// </summary>
|
||||
public readonly struct HuffmanTableEntry
|
||||
{
|
||||
public readonly UInt32 Code;
|
||||
public readonly byte Bits;
|
||||
|
||||
public HuffmanTableEntry(UInt32 code, byte bits)
|
||||
{
|
||||
this.Code = code;
|
||||
this.Bits = bits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// It must return 0 or 1 at bit index. Indexing will be relative to the Bits representing the current code. Idx grows from left to right. Idx must be between [1..Bits].
|
||||
/// </summary>
|
||||
public byte GetBitAtIdx(byte idx)
|
||||
{
|
||||
return (byte)((this.Code >> (this.Bits - idx)) & 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[TableEntry Code: 0x{0:X}, Bits: {1}]", this.Code, this.Bits);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct HuffmanTreeNode
|
||||
{
|
||||
public readonly UInt16 Value;
|
||||
|
||||
public readonly UInt16 NextZeroIdx;
|
||||
public readonly UInt16 NextOneIdx;
|
||||
|
||||
public HuffmanTreeNode(UInt16 value, UInt16 nextZeroIdx, UInt16 nextOneIdx)
|
||||
{
|
||||
this.Value = value;
|
||||
this.NextZeroIdx = nextZeroIdx;
|
||||
this.NextOneIdx = nextOneIdx;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[TreeNode Value: {0}, NextZeroIdx: {1}, NextOneIdx: {2}]",
|
||||
this.Value, this.NextZeroIdx, this.NextOneIdx);
|
||||
}
|
||||
}
|
||||
|
||||
[Best.HTTP.Shared.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstruction]
|
||||
static class HuffmanEncoder
|
||||
{
|
||||
public const UInt16 EOS = 256;
|
||||
|
||||
static readonly HuffmanTableEntry[] StaticTable = new HuffmanTableEntry[257]
|
||||
{
|
||||
new HuffmanTableEntry( 0x1ff8 , 13 ),
|
||||
new HuffmanTableEntry( 0x7fffd8 , 23 ),
|
||||
new HuffmanTableEntry( 0xfffffe2, 28 ),
|
||||
new HuffmanTableEntry( 0xfffffe3, 28 ),
|
||||
new HuffmanTableEntry( 0xfffffe4, 28 ),
|
||||
new HuffmanTableEntry( 0xfffffe5, 28 ),
|
||||
new HuffmanTableEntry( 0xfffffe6, 28 ),
|
||||
new HuffmanTableEntry( 0xfffffe7, 28 ),
|
||||
new HuffmanTableEntry( 0xfffffe8, 28 ),
|
||||
new HuffmanTableEntry( 0xffffea, 24 ),
|
||||
new HuffmanTableEntry( 0x3ffffffc , 30 ),
|
||||
new HuffmanTableEntry( 0xfffffe9 , 28 ),
|
||||
new HuffmanTableEntry( 0xfffffea , 28 ),
|
||||
new HuffmanTableEntry( 0x3ffffffd , 30 ),
|
||||
new HuffmanTableEntry( 0xfffffeb , 28 ),
|
||||
new HuffmanTableEntry( 0xfffffec , 28 ),
|
||||
new HuffmanTableEntry( 0xfffffed , 28 ),
|
||||
new HuffmanTableEntry( 0xfffffee , 28 ),
|
||||
new HuffmanTableEntry( 0xfffffef , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff0 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff1 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff2 , 28 ),
|
||||
new HuffmanTableEntry( 0x3ffffffe, 30 ),
|
||||
new HuffmanTableEntry( 0xffffff3 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff4 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff5 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff6 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff7 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff8 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffff9 , 28 ),
|
||||
new HuffmanTableEntry( 0xffffffa , 28 ),
|
||||
new HuffmanTableEntry( 0xffffffb , 28 ),
|
||||
new HuffmanTableEntry( 0x14 , 6 ),
|
||||
new HuffmanTableEntry( 0x3f8, 10 ),
|
||||
new HuffmanTableEntry( 0x3f9, 10 ),
|
||||
new HuffmanTableEntry( 0xffa, 12 ),
|
||||
new HuffmanTableEntry( 0x1ff9 , 13 ),
|
||||
new HuffmanTableEntry( 0x15 , 6 ),
|
||||
new HuffmanTableEntry( 0xf8 , 8 ),
|
||||
new HuffmanTableEntry( 0x7fa, 11 ),
|
||||
new HuffmanTableEntry( 0x3fa, 10 ),
|
||||
new HuffmanTableEntry( 0x3fb, 10 ),
|
||||
new HuffmanTableEntry( 0xf9 , 8 ),
|
||||
new HuffmanTableEntry( 0x7fb, 11 ),
|
||||
new HuffmanTableEntry( 0xfa , 8 ),
|
||||
new HuffmanTableEntry( 0x16 , 6 ),
|
||||
new HuffmanTableEntry( 0x17 , 6 ),
|
||||
new HuffmanTableEntry( 0x18 , 6 ),
|
||||
new HuffmanTableEntry( 0x0 , 5 ),
|
||||
new HuffmanTableEntry( 0x1 , 5 ),
|
||||
new HuffmanTableEntry( 0x2 , 5 ),
|
||||
new HuffmanTableEntry( 0x19 , 6 ),
|
||||
new HuffmanTableEntry( 0x1a , 6 ),
|
||||
new HuffmanTableEntry( 0x1b , 6 ),
|
||||
new HuffmanTableEntry( 0x1c , 6 ),
|
||||
new HuffmanTableEntry( 0x1d , 6 ),
|
||||
new HuffmanTableEntry( 0x1e , 6 ),
|
||||
new HuffmanTableEntry( 0x1f , 6 ),
|
||||
new HuffmanTableEntry( 0x5c , 7 ),
|
||||
new HuffmanTableEntry( 0xfb , 8 ),
|
||||
new HuffmanTableEntry( 0x7ffc , 15 ),
|
||||
new HuffmanTableEntry( 0x20 , 6 ),
|
||||
new HuffmanTableEntry( 0xffb, 12 ),
|
||||
new HuffmanTableEntry( 0x3fc, 10 ),
|
||||
new HuffmanTableEntry( 0x1ffa , 13 ),
|
||||
new HuffmanTableEntry( 0x21, 6 ),
|
||||
new HuffmanTableEntry( 0x5d, 7 ),
|
||||
new HuffmanTableEntry( 0x5e, 7 ),
|
||||
new HuffmanTableEntry( 0x5f, 7 ),
|
||||
new HuffmanTableEntry( 0x60, 7 ),
|
||||
new HuffmanTableEntry( 0x61, 7 ),
|
||||
new HuffmanTableEntry( 0x62, 7 ),
|
||||
new HuffmanTableEntry( 0x63, 7 ),
|
||||
new HuffmanTableEntry( 0x64, 7 ),
|
||||
new HuffmanTableEntry( 0x65, 7 ),
|
||||
new HuffmanTableEntry( 0x66, 7 ),
|
||||
new HuffmanTableEntry( 0x67, 7 ),
|
||||
new HuffmanTableEntry( 0x68, 7 ),
|
||||
new HuffmanTableEntry( 0x69, 7 ),
|
||||
new HuffmanTableEntry( 0x6a, 7 ),
|
||||
new HuffmanTableEntry( 0x6b, 7 ),
|
||||
new HuffmanTableEntry( 0x6c, 7 ),
|
||||
new HuffmanTableEntry( 0x6d, 7 ),
|
||||
new HuffmanTableEntry( 0x6e, 7 ),
|
||||
new HuffmanTableEntry( 0x6f, 7 ),
|
||||
new HuffmanTableEntry( 0x70, 7 ),
|
||||
new HuffmanTableEntry( 0x71, 7 ),
|
||||
new HuffmanTableEntry( 0x72, 7 ),
|
||||
new HuffmanTableEntry( 0xfc, 8 ),
|
||||
new HuffmanTableEntry( 0x73, 7 ),
|
||||
new HuffmanTableEntry( 0xfd, 8 ),
|
||||
new HuffmanTableEntry( 0x1ffb, 13 ),
|
||||
new HuffmanTableEntry( 0x7fff0, 19 ),
|
||||
new HuffmanTableEntry( 0x1ffc, 13 ),
|
||||
new HuffmanTableEntry( 0x3ffc, 14 ),
|
||||
new HuffmanTableEntry( 0x22, 6 ),
|
||||
new HuffmanTableEntry( 0x7ffd, 15 ),
|
||||
new HuffmanTableEntry( 0x3, 5 ),
|
||||
new HuffmanTableEntry( 0x23, 6 ),
|
||||
new HuffmanTableEntry( 0x4, 5 ),
|
||||
new HuffmanTableEntry( 0x24, 6 ),
|
||||
new HuffmanTableEntry( 0x5, 5 ),
|
||||
new HuffmanTableEntry( 0x25, 6 ),
|
||||
new HuffmanTableEntry( 0x26, 6 ),
|
||||
new HuffmanTableEntry( 0x27, 6 ),
|
||||
new HuffmanTableEntry( 0x6 , 5 ),
|
||||
new HuffmanTableEntry( 0x74, 7 ),
|
||||
new HuffmanTableEntry( 0x75, 7 ),
|
||||
new HuffmanTableEntry( 0x28, 6 ),
|
||||
new HuffmanTableEntry( 0x29, 6 ),
|
||||
new HuffmanTableEntry( 0x2a, 6 ),
|
||||
new HuffmanTableEntry( 0x7 , 5 ),
|
||||
new HuffmanTableEntry( 0x2b, 6 ),
|
||||
new HuffmanTableEntry( 0x76, 7 ),
|
||||
new HuffmanTableEntry( 0x2c, 6 ),
|
||||
new HuffmanTableEntry( 0x8 , 5 ),
|
||||
new HuffmanTableEntry( 0x9 , 5 ),
|
||||
new HuffmanTableEntry( 0x2d, 6 ),
|
||||
new HuffmanTableEntry( 0x77, 7 ),
|
||||
new HuffmanTableEntry( 0x78, 7 ),
|
||||
new HuffmanTableEntry( 0x79, 7 ),
|
||||
new HuffmanTableEntry( 0x7a, 7 ),
|
||||
new HuffmanTableEntry( 0x7b, 7 ),
|
||||
new HuffmanTableEntry( 0x7ffe, 15 ),
|
||||
new HuffmanTableEntry( 0x7fc, 11 ),
|
||||
new HuffmanTableEntry( 0x3ffd, 14 ),
|
||||
new HuffmanTableEntry( 0x1ffd, 13 ),
|
||||
new HuffmanTableEntry( 0xffffffc, 28 ),
|
||||
new HuffmanTableEntry( 0xfffe6 , 20 ),
|
||||
new HuffmanTableEntry( 0x3fffd2, 22 ),
|
||||
new HuffmanTableEntry( 0xfffe7 , 20 ),
|
||||
new HuffmanTableEntry( 0xfffe8 , 20 ),
|
||||
new HuffmanTableEntry( 0x3fffd3, 22 ),
|
||||
new HuffmanTableEntry( 0x3fffd4, 22 ),
|
||||
new HuffmanTableEntry( 0x3fffd5, 22 ),
|
||||
new HuffmanTableEntry( 0x7fffd9, 23 ),
|
||||
new HuffmanTableEntry( 0x3fffd6, 22 ),
|
||||
new HuffmanTableEntry( 0x7fffda, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffdb, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffdc, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffdd, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffde, 23 ),
|
||||
new HuffmanTableEntry( 0xffffeb, 24 ),
|
||||
new HuffmanTableEntry( 0x7fffdf, 23 ),
|
||||
new HuffmanTableEntry( 0xffffec, 24 ),
|
||||
new HuffmanTableEntry( 0xffffed, 24 ),
|
||||
new HuffmanTableEntry( 0x3fffd7, 22 ),
|
||||
new HuffmanTableEntry( 0x7fffe0, 23 ),
|
||||
new HuffmanTableEntry( 0xffffee, 24 ),
|
||||
new HuffmanTableEntry( 0x7fffe1, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffe2, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffe3, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffe4, 23 ),
|
||||
new HuffmanTableEntry( 0x1fffdc, 21 ),
|
||||
new HuffmanTableEntry( 0x3fffd8, 22 ),
|
||||
new HuffmanTableEntry( 0x7fffe5, 23 ),
|
||||
new HuffmanTableEntry( 0x3fffd9, 22 ),
|
||||
new HuffmanTableEntry( 0x7fffe6, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffe7, 23 ),
|
||||
new HuffmanTableEntry( 0xffffef, 24 ),
|
||||
new HuffmanTableEntry( 0x3fffda, 22 ),
|
||||
new HuffmanTableEntry( 0x1fffdd, 21 ),
|
||||
new HuffmanTableEntry( 0xfffe9 , 20 ),
|
||||
new HuffmanTableEntry( 0x3fffdb, 22 ),
|
||||
new HuffmanTableEntry( 0x3fffdc, 22 ),
|
||||
new HuffmanTableEntry( 0x7fffe8, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffe9, 23 ),
|
||||
new HuffmanTableEntry( 0x1fffde, 21 ),
|
||||
new HuffmanTableEntry( 0x7fffea, 23 ),
|
||||
new HuffmanTableEntry( 0x3fffdd, 22 ),
|
||||
new HuffmanTableEntry( 0x3fffde, 22 ),
|
||||
new HuffmanTableEntry( 0xfffff0, 24 ),
|
||||
new HuffmanTableEntry( 0x1fffdf, 21 ),
|
||||
new HuffmanTableEntry( 0x3fffdf, 22 ),
|
||||
new HuffmanTableEntry( 0x7fffeb, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffec, 23 ),
|
||||
new HuffmanTableEntry( 0x1fffe0, 21 ),
|
||||
new HuffmanTableEntry( 0x1fffe1, 21 ),
|
||||
new HuffmanTableEntry( 0x3fffe0, 22 ),
|
||||
new HuffmanTableEntry( 0x1fffe2, 21 ),
|
||||
new HuffmanTableEntry( 0x7fffed, 23 ),
|
||||
new HuffmanTableEntry( 0x3fffe1, 22 ),
|
||||
new HuffmanTableEntry( 0x7fffee, 23 ),
|
||||
new HuffmanTableEntry( 0x7fffef, 23 ),
|
||||
new HuffmanTableEntry( 0xfffea , 20 ),
|
||||
new HuffmanTableEntry( 0x3fffe2, 22 ),
|
||||
new HuffmanTableEntry( 0x3fffe3, 22 ),
|
||||
new HuffmanTableEntry( 0x3fffe4, 22 ),
|
||||
new HuffmanTableEntry( 0x7ffff0, 23 ),
|
||||
new HuffmanTableEntry( 0x3fffe5, 22 ),
|
||||
new HuffmanTableEntry( 0x3fffe6, 22 ),
|
||||
new HuffmanTableEntry( 0x7ffff1, 23 ),
|
||||
new HuffmanTableEntry( 0x3ffffe0, 26 ),
|
||||
new HuffmanTableEntry( 0x3ffffe1, 26 ),
|
||||
new HuffmanTableEntry( 0xfffeb , 20 ),
|
||||
new HuffmanTableEntry( 0x7fff1 , 19 ),
|
||||
new HuffmanTableEntry( 0x3fffe7, 22 ),
|
||||
new HuffmanTableEntry( 0x7ffff2, 23 ),
|
||||
new HuffmanTableEntry( 0x3fffe8, 22 ),
|
||||
new HuffmanTableEntry( 0x1ffffec, 25 ),
|
||||
new HuffmanTableEntry( 0x3ffffe2, 26 ),
|
||||
new HuffmanTableEntry( 0x3ffffe3, 26 ),
|
||||
new HuffmanTableEntry( 0x3ffffe4, 26 ),
|
||||
new HuffmanTableEntry( 0x7ffffde, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffdf, 27 ),
|
||||
new HuffmanTableEntry( 0x3ffffe5, 26 ),
|
||||
new HuffmanTableEntry( 0xfffff1 , 24 ),
|
||||
new HuffmanTableEntry( 0x1ffffed, 25 ),
|
||||
new HuffmanTableEntry( 0x7fff2 , 19 ),
|
||||
new HuffmanTableEntry( 0x1fffe3 , 21 ),
|
||||
new HuffmanTableEntry( 0x3ffffe6, 26 ),
|
||||
new HuffmanTableEntry( 0x7ffffe0, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffe1, 27 ),
|
||||
new HuffmanTableEntry( 0x3ffffe7, 26 ),
|
||||
new HuffmanTableEntry( 0x7ffffe2, 27 ),
|
||||
new HuffmanTableEntry( 0xfffff2 , 24 ),
|
||||
new HuffmanTableEntry( 0x1fffe4 , 21 ),
|
||||
new HuffmanTableEntry( 0x1fffe5 , 21 ),
|
||||
new HuffmanTableEntry( 0x3ffffe8, 26 ),
|
||||
new HuffmanTableEntry( 0x3ffffe9, 26 ),
|
||||
new HuffmanTableEntry( 0xffffffd, 28 ),
|
||||
new HuffmanTableEntry( 0x7ffffe3, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffe4, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffe5, 27 ),
|
||||
new HuffmanTableEntry( 0xfffec , 20 ),
|
||||
new HuffmanTableEntry( 0xfffff3, 24 ),
|
||||
new HuffmanTableEntry( 0xfffed , 20 ),
|
||||
new HuffmanTableEntry( 0x1fffe6, 21 ),
|
||||
new HuffmanTableEntry( 0x3fffe9, 22 ),
|
||||
new HuffmanTableEntry( 0x1fffe7, 21 ),
|
||||
new HuffmanTableEntry( 0x1fffe8, 21 ),
|
||||
new HuffmanTableEntry( 0x7ffff3, 23 ),
|
||||
new HuffmanTableEntry( 0x3fffea, 22 ),
|
||||
new HuffmanTableEntry( 0x3fffeb, 22 ),
|
||||
new HuffmanTableEntry( 0x1ffffee, 25 ),
|
||||
new HuffmanTableEntry( 0x1ffffef, 25 ),
|
||||
new HuffmanTableEntry( 0xfffff4 , 24 ),
|
||||
new HuffmanTableEntry( 0xfffff5 , 24 ),
|
||||
new HuffmanTableEntry( 0x3ffffea, 26 ),
|
||||
new HuffmanTableEntry( 0x7ffff4 , 23 ),
|
||||
new HuffmanTableEntry( 0x3ffffeb, 26 ),
|
||||
new HuffmanTableEntry( 0x7ffffe6, 27 ),
|
||||
new HuffmanTableEntry( 0x3ffffec, 26 ),
|
||||
new HuffmanTableEntry( 0x3ffffed, 26 ),
|
||||
new HuffmanTableEntry( 0x7ffffe7, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffe8, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffe9, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffea, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffeb, 27 ),
|
||||
new HuffmanTableEntry( 0xffffffe, 28 ),
|
||||
new HuffmanTableEntry( 0x7ffffec, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffed, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffee, 27 ),
|
||||
new HuffmanTableEntry( 0x7ffffef, 27 ),
|
||||
new HuffmanTableEntry( 0x7fffff0, 27 ),
|
||||
new HuffmanTableEntry( 0x3ffffee, 26 ),
|
||||
new HuffmanTableEntry( 0x3fffffff, 30 )
|
||||
};
|
||||
//static List<TreeNode> entries = new List<TreeNode>();
|
||||
|
||||
static readonly HuffmanTreeNode[] HuffmanTree = new HuffmanTreeNode[]
|
||||
{
|
||||
new HuffmanTreeNode ( 0, 98, 1 ),
|
||||
new HuffmanTreeNode ( 0, 151, 2 ),
|
||||
new HuffmanTreeNode ( 0, 173, 3 ),
|
||||
new HuffmanTreeNode ( 0, 204, 4 ),
|
||||
new HuffmanTreeNode ( 0, 263, 5 ),
|
||||
new HuffmanTreeNode ( 0, 113, 6 ),
|
||||
new HuffmanTreeNode ( 0, 211, 7 ),
|
||||
new HuffmanTreeNode ( 0, 104, 8 ),
|
||||
new HuffmanTreeNode ( 0, 116, 9 ),
|
||||
new HuffmanTreeNode ( 0, 108, 10 ),
|
||||
new HuffmanTreeNode ( 0, 11, 14 ),
|
||||
new HuffmanTreeNode ( 0, 12, 166 ),
|
||||
new HuffmanTreeNode ( 0, 13, 111 ),
|
||||
new HuffmanTreeNode ( 0, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 220, 15 ),
|
||||
new HuffmanTreeNode ( 0, 222, 16 ),
|
||||
new HuffmanTreeNode ( 0, 158, 17 ),
|
||||
new HuffmanTreeNode ( 0, 270, 18 ),
|
||||
new HuffmanTreeNode ( 0, 216, 19 ),
|
||||
new HuffmanTreeNode ( 0, 279, 20 ),
|
||||
new HuffmanTreeNode ( 0, 21, 27 ),
|
||||
new HuffmanTreeNode ( 0, 377, 22 ),
|
||||
new HuffmanTreeNode ( 0, 414, 23 ),
|
||||
new HuffmanTreeNode ( 0, 24, 301 ),
|
||||
new HuffmanTreeNode ( 0, 25, 298 ),
|
||||
new HuffmanTreeNode ( 0, 26, 295 ),
|
||||
new HuffmanTreeNode ( 1, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 314, 28 ),
|
||||
new HuffmanTreeNode ( 0, 50, 29 ),
|
||||
new HuffmanTreeNode ( 0, 362, 30 ),
|
||||
new HuffmanTreeNode ( 0, 403, 31 ),
|
||||
new HuffmanTreeNode ( 0, 440, 32 ),
|
||||
new HuffmanTreeNode ( 0, 33, 55 ),
|
||||
new HuffmanTreeNode ( 0, 34, 46 ),
|
||||
new HuffmanTreeNode ( 0, 35, 39 ),
|
||||
new HuffmanTreeNode ( 0, 510, 36 ),
|
||||
new HuffmanTreeNode ( 0, 37, 38 ),
|
||||
new HuffmanTreeNode ( 2, 0, 0 ),
|
||||
new HuffmanTreeNode ( 3, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 40, 43 ),
|
||||
new HuffmanTreeNode ( 0, 41, 42 ),
|
||||
new HuffmanTreeNode ( 4, 0, 0 ),
|
||||
new HuffmanTreeNode ( 5, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 44, 45 ),
|
||||
new HuffmanTreeNode ( 6, 0, 0 ),
|
||||
new HuffmanTreeNode ( 7, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 47, 67 ),
|
||||
new HuffmanTreeNode ( 0, 48, 63 ),
|
||||
new HuffmanTreeNode ( 0, 49, 62 ),
|
||||
new HuffmanTreeNode ( 8, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 396, 51 ),
|
||||
new HuffmanTreeNode ( 0, 52, 309 ),
|
||||
new HuffmanTreeNode ( 0, 486, 53 ),
|
||||
new HuffmanTreeNode ( 0, 54, 307 ),
|
||||
new HuffmanTreeNode ( 9, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 74, 56 ),
|
||||
new HuffmanTreeNode ( 0, 91, 57 ),
|
||||
new HuffmanTreeNode ( 0, 274, 58 ),
|
||||
new HuffmanTreeNode ( 0, 502, 59 ),
|
||||
new HuffmanTreeNode ( 0, 60, 81 ),
|
||||
new HuffmanTreeNode ( 0, 61, 65 ),
|
||||
new HuffmanTreeNode ( 10, 0, 0 ),
|
||||
new HuffmanTreeNode ( 11, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 64, 66 ),
|
||||
new HuffmanTreeNode ( 12, 0, 0 ),
|
||||
new HuffmanTreeNode ( 13, 0, 0 ),
|
||||
new HuffmanTreeNode ( 14, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 68, 71 ),
|
||||
new HuffmanTreeNode ( 0, 69, 70 ),
|
||||
new HuffmanTreeNode ( 15, 0, 0 ),
|
||||
new HuffmanTreeNode ( 16, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 72, 73 ),
|
||||
new HuffmanTreeNode ( 17, 0, 0 ),
|
||||
new HuffmanTreeNode ( 18, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 75, 84 ),
|
||||
new HuffmanTreeNode ( 0, 76, 79 ),
|
||||
new HuffmanTreeNode ( 0, 77, 78 ),
|
||||
new HuffmanTreeNode ( 19, 0, 0 ),
|
||||
new HuffmanTreeNode ( 20, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 80, 83 ),
|
||||
new HuffmanTreeNode ( 21, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 82, 512 ),
|
||||
new HuffmanTreeNode ( 22, 0, 0 ),
|
||||
new HuffmanTreeNode ( 23, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 85, 88 ),
|
||||
new HuffmanTreeNode ( 0, 86, 87 ),
|
||||
new HuffmanTreeNode ( 24, 0, 0 ),
|
||||
new HuffmanTreeNode ( 25, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 89, 90 ),
|
||||
new HuffmanTreeNode ( 26, 0, 0 ),
|
||||
new HuffmanTreeNode ( 27, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 92, 95 ),
|
||||
new HuffmanTreeNode ( 0, 93, 94 ),
|
||||
new HuffmanTreeNode ( 28, 0, 0 ),
|
||||
new HuffmanTreeNode ( 29, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 96, 97 ),
|
||||
new HuffmanTreeNode ( 30, 0, 0 ),
|
||||
new HuffmanTreeNode ( 31, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 133, 99 ),
|
||||
new HuffmanTreeNode ( 0, 100, 129 ),
|
||||
new HuffmanTreeNode ( 0, 258, 101 ),
|
||||
new HuffmanTreeNode ( 0, 102, 126 ),
|
||||
new HuffmanTreeNode ( 0, 103, 112 ),
|
||||
new HuffmanTreeNode ( 32, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 105, 119 ),
|
||||
new HuffmanTreeNode ( 0, 106, 107 ),
|
||||
new HuffmanTreeNode ( 33, 0, 0 ),
|
||||
new HuffmanTreeNode ( 34, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 271, 109 ),
|
||||
new HuffmanTreeNode ( 0, 110, 164 ),
|
||||
new HuffmanTreeNode ( 35, 0, 0 ),
|
||||
new HuffmanTreeNode ( 36, 0, 0 ),
|
||||
new HuffmanTreeNode ( 37, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 114, 124 ),
|
||||
new HuffmanTreeNode ( 0, 115, 122 ),
|
||||
new HuffmanTreeNode ( 38, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 165, 117 ),
|
||||
new HuffmanTreeNode ( 0, 118, 123 ),
|
||||
new HuffmanTreeNode ( 39, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 120, 121 ),
|
||||
new HuffmanTreeNode ( 40, 0, 0 ),
|
||||
new HuffmanTreeNode ( 41, 0, 0 ),
|
||||
new HuffmanTreeNode ( 42, 0, 0 ),
|
||||
new HuffmanTreeNode ( 43, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 125, 157 ),
|
||||
new HuffmanTreeNode ( 44, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 127, 128 ),
|
||||
new HuffmanTreeNode ( 45, 0, 0 ),
|
||||
new HuffmanTreeNode ( 46, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 130, 144 ),
|
||||
new HuffmanTreeNode ( 0, 131, 141 ),
|
||||
new HuffmanTreeNode ( 0, 132, 140 ),
|
||||
new HuffmanTreeNode ( 47, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 134, 229 ),
|
||||
new HuffmanTreeNode ( 0, 135, 138 ),
|
||||
new HuffmanTreeNode ( 0, 136, 137 ),
|
||||
new HuffmanTreeNode ( 48, 0, 0 ),
|
||||
new HuffmanTreeNode ( 49, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 139, 227 ),
|
||||
new HuffmanTreeNode ( 50, 0, 0 ),
|
||||
new HuffmanTreeNode ( 51, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 142, 143 ),
|
||||
new HuffmanTreeNode ( 52, 0, 0 ),
|
||||
new HuffmanTreeNode ( 53, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 145, 148 ),
|
||||
new HuffmanTreeNode ( 0, 146, 147 ),
|
||||
new HuffmanTreeNode ( 54, 0, 0 ),
|
||||
new HuffmanTreeNode ( 55, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 149, 150 ),
|
||||
new HuffmanTreeNode ( 56, 0, 0 ),
|
||||
new HuffmanTreeNode ( 57, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 160, 152 ),
|
||||
new HuffmanTreeNode ( 0, 246, 153 ),
|
||||
new HuffmanTreeNode ( 0, 256, 154 ),
|
||||
new HuffmanTreeNode ( 0, 155, 170 ),
|
||||
new HuffmanTreeNode ( 0, 156, 169 ),
|
||||
new HuffmanTreeNode ( 58, 0, 0 ),
|
||||
new HuffmanTreeNode ( 59, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 159, 226 ),
|
||||
new HuffmanTreeNode ( 60, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 161, 232 ),
|
||||
new HuffmanTreeNode ( 0, 162, 224 ),
|
||||
new HuffmanTreeNode ( 0, 163, 168 ),
|
||||
new HuffmanTreeNode ( 61, 0, 0 ),
|
||||
new HuffmanTreeNode ( 62, 0, 0 ),
|
||||
new HuffmanTreeNode ( 63, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 167, 215 ),
|
||||
new HuffmanTreeNode ( 64, 0, 0 ),
|
||||
new HuffmanTreeNode ( 65, 0, 0 ),
|
||||
new HuffmanTreeNode ( 66, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 171, 172 ),
|
||||
new HuffmanTreeNode ( 67, 0, 0 ),
|
||||
new HuffmanTreeNode ( 68, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 174, 189 ),
|
||||
new HuffmanTreeNode ( 0, 175, 182 ),
|
||||
new HuffmanTreeNode ( 0, 176, 179 ),
|
||||
new HuffmanTreeNode ( 0, 177, 178 ),
|
||||
new HuffmanTreeNode ( 69, 0, 0 ),
|
||||
new HuffmanTreeNode ( 70, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 180, 181 ),
|
||||
new HuffmanTreeNode ( 71, 0, 0 ),
|
||||
new HuffmanTreeNode ( 72, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 183, 186 ),
|
||||
new HuffmanTreeNode ( 0, 184, 185 ),
|
||||
new HuffmanTreeNode ( 73, 0, 0 ),
|
||||
new HuffmanTreeNode ( 74, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 187, 188 ),
|
||||
new HuffmanTreeNode ( 75, 0, 0 ),
|
||||
new HuffmanTreeNode ( 76, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 190, 197 ),
|
||||
new HuffmanTreeNode ( 0, 191, 194 ),
|
||||
new HuffmanTreeNode ( 0, 192, 193 ),
|
||||
new HuffmanTreeNode ( 77, 0, 0 ),
|
||||
new HuffmanTreeNode ( 78, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 195, 196 ),
|
||||
new HuffmanTreeNode ( 79, 0, 0 ),
|
||||
new HuffmanTreeNode ( 80, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 198, 201 ),
|
||||
new HuffmanTreeNode ( 0, 199, 200 ),
|
||||
new HuffmanTreeNode ( 81, 0, 0 ),
|
||||
new HuffmanTreeNode ( 82, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 202, 203 ),
|
||||
new HuffmanTreeNode ( 83, 0, 0 ),
|
||||
new HuffmanTreeNode ( 84, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 205, 242 ),
|
||||
new HuffmanTreeNode ( 0, 206, 209 ),
|
||||
new HuffmanTreeNode ( 0, 207, 208 ),
|
||||
new HuffmanTreeNode ( 85, 0, 0 ),
|
||||
new HuffmanTreeNode ( 86, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 210, 213 ),
|
||||
new HuffmanTreeNode ( 87, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 212, 214 ),
|
||||
new HuffmanTreeNode ( 88, 0, 0 ),
|
||||
new HuffmanTreeNode ( 89, 0, 0 ),
|
||||
new HuffmanTreeNode ( 90, 0, 0 ),
|
||||
new HuffmanTreeNode ( 91, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 217, 286 ),
|
||||
new HuffmanTreeNode ( 0, 218, 276 ),
|
||||
new HuffmanTreeNode ( 0, 219, 410 ),
|
||||
new HuffmanTreeNode ( 92, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 221, 273 ),
|
||||
new HuffmanTreeNode ( 93, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 223, 272 ),
|
||||
new HuffmanTreeNode ( 94, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 225, 228 ),
|
||||
new HuffmanTreeNode ( 95, 0, 0 ),
|
||||
new HuffmanTreeNode ( 96, 0, 0 ),
|
||||
new HuffmanTreeNode ( 97, 0, 0 ),
|
||||
new HuffmanTreeNode ( 98, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 230, 240 ),
|
||||
new HuffmanTreeNode ( 0, 231, 235 ),
|
||||
new HuffmanTreeNode ( 99, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 233, 237 ),
|
||||
new HuffmanTreeNode ( 0, 234, 236 ),
|
||||
new HuffmanTreeNode ( 100, 0, 0 ),
|
||||
new HuffmanTreeNode ( 101, 0, 0 ),
|
||||
new HuffmanTreeNode ( 102, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 238, 239 ),
|
||||
new HuffmanTreeNode ( 103, 0, 0 ),
|
||||
new HuffmanTreeNode ( 104, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 241, 252 ),
|
||||
new HuffmanTreeNode ( 105, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 243, 254 ),
|
||||
new HuffmanTreeNode ( 0, 244, 245 ),
|
||||
new HuffmanTreeNode ( 106, 0, 0 ),
|
||||
new HuffmanTreeNode ( 107, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 247, 250 ),
|
||||
new HuffmanTreeNode ( 0, 248, 249 ),
|
||||
new HuffmanTreeNode ( 108, 0, 0 ),
|
||||
new HuffmanTreeNode ( 109, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 251, 253 ),
|
||||
new HuffmanTreeNode ( 110, 0, 0 ),
|
||||
new HuffmanTreeNode ( 111, 0, 0 ),
|
||||
new HuffmanTreeNode ( 112, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 255, 262 ),
|
||||
new HuffmanTreeNode ( 113, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 257, 261 ),
|
||||
new HuffmanTreeNode ( 114, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 259, 260 ),
|
||||
new HuffmanTreeNode ( 115, 0, 0 ),
|
||||
new HuffmanTreeNode ( 116, 0, 0 ),
|
||||
new HuffmanTreeNode ( 117, 0, 0 ),
|
||||
new HuffmanTreeNode ( 118, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 264, 267 ),
|
||||
new HuffmanTreeNode ( 0, 265, 266 ),
|
||||
new HuffmanTreeNode ( 119, 0, 0 ),
|
||||
new HuffmanTreeNode ( 120, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 268, 269 ),
|
||||
new HuffmanTreeNode ( 121, 0, 0 ),
|
||||
new HuffmanTreeNode ( 122, 0, 0 ),
|
||||
new HuffmanTreeNode ( 123, 0, 0 ),
|
||||
new HuffmanTreeNode ( 124, 0, 0 ),
|
||||
new HuffmanTreeNode ( 125, 0, 0 ),
|
||||
new HuffmanTreeNode ( 126, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 275, 459 ),
|
||||
new HuffmanTreeNode ( 127, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 436, 277 ),
|
||||
new HuffmanTreeNode ( 0, 278, 285 ),
|
||||
new HuffmanTreeNode ( 128, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 372, 280 ),
|
||||
new HuffmanTreeNode ( 0, 281, 332 ),
|
||||
new HuffmanTreeNode ( 0, 282, 291 ),
|
||||
new HuffmanTreeNode ( 0, 473, 283 ),
|
||||
new HuffmanTreeNode ( 0, 284, 290 ),
|
||||
new HuffmanTreeNode ( 129, 0, 0 ),
|
||||
new HuffmanTreeNode ( 130, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 287, 328 ),
|
||||
new HuffmanTreeNode ( 0, 288, 388 ),
|
||||
new HuffmanTreeNode ( 0, 289, 345 ),
|
||||
new HuffmanTreeNode ( 131, 0, 0 ),
|
||||
new HuffmanTreeNode ( 132, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 292, 296 ),
|
||||
new HuffmanTreeNode ( 0, 293, 294 ),
|
||||
new HuffmanTreeNode ( 133, 0, 0 ),
|
||||
new HuffmanTreeNode ( 134, 0, 0 ),
|
||||
new HuffmanTreeNode ( 135, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 297, 313 ),
|
||||
new HuffmanTreeNode ( 136, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 299, 300 ),
|
||||
new HuffmanTreeNode ( 137, 0, 0 ),
|
||||
new HuffmanTreeNode ( 138, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 302, 305 ),
|
||||
new HuffmanTreeNode ( 0, 303, 304 ),
|
||||
new HuffmanTreeNode ( 139, 0, 0 ),
|
||||
new HuffmanTreeNode ( 140, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 306, 308 ),
|
||||
new HuffmanTreeNode ( 141, 0, 0 ),
|
||||
new HuffmanTreeNode ( 142, 0, 0 ),
|
||||
new HuffmanTreeNode ( 143, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 310, 319 ),
|
||||
new HuffmanTreeNode ( 0, 311, 312 ),
|
||||
new HuffmanTreeNode ( 144, 0, 0 ),
|
||||
new HuffmanTreeNode ( 145, 0, 0 ),
|
||||
new HuffmanTreeNode ( 146, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 315, 350 ),
|
||||
new HuffmanTreeNode ( 0, 316, 325 ),
|
||||
new HuffmanTreeNode ( 0, 317, 322 ),
|
||||
new HuffmanTreeNode ( 0, 318, 321 ),
|
||||
new HuffmanTreeNode ( 147, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 320, 341 ),
|
||||
new HuffmanTreeNode ( 148, 0, 0 ),
|
||||
new HuffmanTreeNode ( 149, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 323, 324 ),
|
||||
new HuffmanTreeNode ( 150, 0, 0 ),
|
||||
new HuffmanTreeNode ( 151, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 326, 338 ),
|
||||
new HuffmanTreeNode ( 0, 327, 336 ),
|
||||
new HuffmanTreeNode ( 152, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 465, 329 ),
|
||||
new HuffmanTreeNode ( 0, 330, 355 ),
|
||||
new HuffmanTreeNode ( 0, 331, 344 ),
|
||||
new HuffmanTreeNode ( 153, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 333, 347 ),
|
||||
new HuffmanTreeNode ( 0, 334, 342 ),
|
||||
new HuffmanTreeNode ( 0, 335, 337 ),
|
||||
new HuffmanTreeNode ( 154, 0, 0 ),
|
||||
new HuffmanTreeNode ( 155, 0, 0 ),
|
||||
new HuffmanTreeNode ( 156, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 339, 340 ),
|
||||
new HuffmanTreeNode ( 157, 0, 0 ),
|
||||
new HuffmanTreeNode ( 158, 0, 0 ),
|
||||
new HuffmanTreeNode ( 159, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 343, 346 ),
|
||||
new HuffmanTreeNode ( 160, 0, 0 ),
|
||||
new HuffmanTreeNode ( 161, 0, 0 ),
|
||||
new HuffmanTreeNode ( 162, 0, 0 ),
|
||||
new HuffmanTreeNode ( 163, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 348, 360 ),
|
||||
new HuffmanTreeNode ( 0, 349, 359 ),
|
||||
new HuffmanTreeNode ( 164, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 351, 369 ),
|
||||
new HuffmanTreeNode ( 0, 352, 357 ),
|
||||
new HuffmanTreeNode ( 0, 353, 354 ),
|
||||
new HuffmanTreeNode ( 165, 0, 0 ),
|
||||
new HuffmanTreeNode ( 166, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 356, 366 ),
|
||||
new HuffmanTreeNode ( 167, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 358, 368 ),
|
||||
new HuffmanTreeNode ( 168, 0, 0 ),
|
||||
new HuffmanTreeNode ( 169, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 361, 367 ),
|
||||
new HuffmanTreeNode ( 170, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 363, 417 ),
|
||||
new HuffmanTreeNode ( 0, 364, 449 ),
|
||||
new HuffmanTreeNode ( 0, 365, 434 ),
|
||||
new HuffmanTreeNode ( 171, 0, 0 ),
|
||||
new HuffmanTreeNode ( 172, 0, 0 ),
|
||||
new HuffmanTreeNode ( 173, 0, 0 ),
|
||||
new HuffmanTreeNode ( 174, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 370, 385 ),
|
||||
new HuffmanTreeNode ( 0, 371, 383 ),
|
||||
new HuffmanTreeNode ( 175, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 373, 451 ),
|
||||
new HuffmanTreeNode ( 0, 374, 381 ),
|
||||
new HuffmanTreeNode ( 0, 375, 376 ),
|
||||
new HuffmanTreeNode ( 176, 0, 0 ),
|
||||
new HuffmanTreeNode ( 177, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 378, 393 ),
|
||||
new HuffmanTreeNode ( 0, 379, 390 ),
|
||||
new HuffmanTreeNode ( 0, 380, 384 ),
|
||||
new HuffmanTreeNode ( 178, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 382, 437 ),
|
||||
new HuffmanTreeNode ( 179, 0, 0 ),
|
||||
new HuffmanTreeNode ( 180, 0, 0 ),
|
||||
new HuffmanTreeNode ( 181, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 386, 387 ),
|
||||
new HuffmanTreeNode ( 182, 0, 0 ),
|
||||
new HuffmanTreeNode ( 183, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 389, 409 ),
|
||||
new HuffmanTreeNode ( 184, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 391, 392 ),
|
||||
new HuffmanTreeNode ( 185, 0, 0 ),
|
||||
new HuffmanTreeNode ( 186, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 394, 400 ),
|
||||
new HuffmanTreeNode ( 0, 395, 399 ),
|
||||
new HuffmanTreeNode ( 187, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 397, 412 ),
|
||||
new HuffmanTreeNode ( 0, 398, 402 ),
|
||||
new HuffmanTreeNode ( 188, 0, 0 ),
|
||||
new HuffmanTreeNode ( 189, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 401, 411 ),
|
||||
new HuffmanTreeNode ( 190, 0, 0 ),
|
||||
new HuffmanTreeNode ( 191, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 404, 427 ),
|
||||
new HuffmanTreeNode ( 0, 405, 424 ),
|
||||
new HuffmanTreeNode ( 0, 406, 421 ),
|
||||
new HuffmanTreeNode ( 0, 407, 408 ),
|
||||
new HuffmanTreeNode ( 192, 0, 0 ),
|
||||
new HuffmanTreeNode ( 193, 0, 0 ),
|
||||
new HuffmanTreeNode ( 194, 0, 0 ),
|
||||
new HuffmanTreeNode ( 195, 0, 0 ),
|
||||
new HuffmanTreeNode ( 196, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 413, 474 ),
|
||||
new HuffmanTreeNode ( 197, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 415, 475 ),
|
||||
new HuffmanTreeNode ( 0, 416, 471 ),
|
||||
new HuffmanTreeNode ( 198, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 481, 418 ),
|
||||
new HuffmanTreeNode ( 0, 419, 478 ),
|
||||
new HuffmanTreeNode ( 0, 420, 435 ),
|
||||
new HuffmanTreeNode ( 199, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 422, 423 ),
|
||||
new HuffmanTreeNode ( 200, 0, 0 ),
|
||||
new HuffmanTreeNode ( 201, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 425, 438 ),
|
||||
new HuffmanTreeNode ( 0, 426, 433 ),
|
||||
new HuffmanTreeNode ( 202, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 455, 428 ),
|
||||
new HuffmanTreeNode ( 0, 490, 429 ),
|
||||
new HuffmanTreeNode ( 0, 511, 430 ),
|
||||
new HuffmanTreeNode ( 0, 431, 432 ),
|
||||
new HuffmanTreeNode ( 203, 0, 0 ),
|
||||
new HuffmanTreeNode ( 204, 0, 0 ),
|
||||
new HuffmanTreeNode ( 205, 0, 0 ),
|
||||
new HuffmanTreeNode ( 206, 0, 0 ),
|
||||
new HuffmanTreeNode ( 207, 0, 0 ),
|
||||
new HuffmanTreeNode ( 208, 0, 0 ),
|
||||
new HuffmanTreeNode ( 209, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 439, 446 ),
|
||||
new HuffmanTreeNode ( 210, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 441, 494 ),
|
||||
new HuffmanTreeNode ( 0, 442, 461 ),
|
||||
new HuffmanTreeNode ( 0, 443, 447 ),
|
||||
new HuffmanTreeNode ( 0, 444, 445 ),
|
||||
new HuffmanTreeNode ( 211, 0, 0 ),
|
||||
new HuffmanTreeNode ( 212, 0, 0 ),
|
||||
new HuffmanTreeNode ( 213, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 448, 460 ),
|
||||
new HuffmanTreeNode ( 214, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 450, 467 ),
|
||||
new HuffmanTreeNode ( 215, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 452, 469 ),
|
||||
new HuffmanTreeNode ( 0, 453, 454 ),
|
||||
new HuffmanTreeNode ( 216, 0, 0 ),
|
||||
new HuffmanTreeNode ( 217, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 456, 484 ),
|
||||
new HuffmanTreeNode ( 0, 457, 458 ),
|
||||
new HuffmanTreeNode ( 218, 0, 0 ),
|
||||
new HuffmanTreeNode ( 219, 0, 0 ),
|
||||
new HuffmanTreeNode ( 220, 0, 0 ),
|
||||
new HuffmanTreeNode ( 221, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 462, 488 ),
|
||||
new HuffmanTreeNode ( 0, 463, 464 ),
|
||||
new HuffmanTreeNode ( 222, 0, 0 ),
|
||||
new HuffmanTreeNode ( 223, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 466, 468 ),
|
||||
new HuffmanTreeNode ( 224, 0, 0 ),
|
||||
new HuffmanTreeNode ( 225, 0, 0 ),
|
||||
new HuffmanTreeNode ( 226, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 470, 472 ),
|
||||
new HuffmanTreeNode ( 227, 0, 0 ),
|
||||
new HuffmanTreeNode ( 228, 0, 0 ),
|
||||
new HuffmanTreeNode ( 229, 0, 0 ),
|
||||
new HuffmanTreeNode ( 230, 0, 0 ),
|
||||
new HuffmanTreeNode ( 231, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 476, 477 ),
|
||||
new HuffmanTreeNode ( 232, 0, 0 ),
|
||||
new HuffmanTreeNode ( 233, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 479, 480 ),
|
||||
new HuffmanTreeNode ( 234, 0, 0 ),
|
||||
new HuffmanTreeNode ( 235, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 482, 483 ),
|
||||
new HuffmanTreeNode ( 236, 0, 0 ),
|
||||
new HuffmanTreeNode ( 237, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 485, 487 ),
|
||||
new HuffmanTreeNode ( 238, 0, 0 ),
|
||||
new HuffmanTreeNode ( 239, 0, 0 ),
|
||||
new HuffmanTreeNode ( 240, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 489, 493 ),
|
||||
new HuffmanTreeNode ( 241, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 491, 492 ),
|
||||
new HuffmanTreeNode ( 242, 0, 0 ),
|
||||
new HuffmanTreeNode ( 243, 0, 0 ),
|
||||
new HuffmanTreeNode ( 244, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 495, 503 ),
|
||||
new HuffmanTreeNode ( 0, 496, 499 ),
|
||||
new HuffmanTreeNode ( 0, 497, 498 ),
|
||||
new HuffmanTreeNode ( 245, 0, 0 ),
|
||||
new HuffmanTreeNode ( 246, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 500, 501 ),
|
||||
new HuffmanTreeNode ( 247, 0, 0 ),
|
||||
new HuffmanTreeNode ( 248, 0, 0 ),
|
||||
new HuffmanTreeNode ( 249, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 504, 507 ),
|
||||
new HuffmanTreeNode ( 0, 505, 506 ),
|
||||
new HuffmanTreeNode ( 250, 0, 0 ),
|
||||
new HuffmanTreeNode ( 251, 0, 0 ),
|
||||
new HuffmanTreeNode ( 0, 508, 509 ),
|
||||
new HuffmanTreeNode ( 252, 0, 0 ),
|
||||
new HuffmanTreeNode ( 253, 0, 0 ),
|
||||
new HuffmanTreeNode ( 254, 0, 0 ),
|
||||
new HuffmanTreeNode ( 255, 0, 0 ),
|
||||
new HuffmanTreeNode ( 256, 0, 0 )
|
||||
};
|
||||
|
||||
//static HuffmanEncoder()
|
||||
//{
|
||||
// BuildTree();
|
||||
//}
|
||||
//
|
||||
//private static void BuildTree()
|
||||
//{
|
||||
// // Add root
|
||||
// entries.Add(new TreeNode());
|
||||
//
|
||||
// for (int i = 0; i < StaticTable.Length; ++i)
|
||||
// {
|
||||
// var tableEntry = StaticTable[i];
|
||||
// var currentNode = entries[0];
|
||||
// int currentNodeIdx = 0;
|
||||
//
|
||||
// for (byte bitIdx = 1; bitIdx <= tableEntry.Bits; bitIdx++)
|
||||
// {
|
||||
// byte bit = tableEntry.GetBitAtIdx(bitIdx);
|
||||
//
|
||||
// switch(bit)
|
||||
// {
|
||||
// case 0:
|
||||
// if (currentNode.NextZeroIdx == 0)
|
||||
// {
|
||||
// currentNode.NextZeroIdx = (UInt16)entries.Count;
|
||||
// entries[currentNodeIdx] = currentNode;
|
||||
// entries.Add(new TreeNode());
|
||||
// }
|
||||
//
|
||||
// currentNodeIdx = currentNode.NextZeroIdx;
|
||||
// currentNode = entries[currentNodeIdx];
|
||||
// break;
|
||||
//
|
||||
// case 1:
|
||||
// if (currentNode.NextOneIdx == 0)
|
||||
// {
|
||||
// currentNode.NextOneIdx = (UInt16)entries.Count;
|
||||
// entries[currentNodeIdx] = currentNode;
|
||||
// entries.Add(new TreeNode());
|
||||
// }
|
||||
//
|
||||
// currentNodeIdx = currentNode.NextOneIdx;
|
||||
// currentNode = entries[currentNodeIdx];
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// HTTPManager.Logger.Information("HuffmanEncoder", "BuildTree - GetBitAtIdx returned with an unsupported value: " + bit);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// entries[currentNodeIdx] = new TreeNode { Value = (UInt16)i };
|
||||
//
|
||||
// //HTTPManager.Logger.Information("HuffmanEncoder", string.Format("BuildTree - {0} - Entry({1}) added to idx: {2}", i, entries[currentNodeIdx], currentNodeIdx));
|
||||
// }
|
||||
//
|
||||
// //HTTPManager.Logger.Information("HuffmanEncoder", "BuildTree - entries: " + entries.Count);
|
||||
// //for (int i = 0; i < entries.Count; ++i)
|
||||
// // HTTPManager.Logger.Information("HuffmanEncoder", string.Format("{0} - Entry : {1}", i, entries[i]));
|
||||
// System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
// for (int i = 0; i < entries.Count; ++i)
|
||||
// {
|
||||
// sb.AppendFormat("new TreeNode {{ Value = {0}, NextZeroIdx = {1}, NextOneIdx = {2} }},\n", entries[i].Value, entries[i].NextZeroIdx, entries[i].NextOneIdx);
|
||||
// }
|
||||
// UnityEngine.Debug.Log(sb.ToString());
|
||||
//}
|
||||
|
||||
public static HuffmanTreeNode GetRoot()
|
||||
{
|
||||
return HuffmanTree[0];
|
||||
}
|
||||
|
||||
public static HuffmanTreeNode GetNext(HuffmanTreeNode current, byte bit)
|
||||
{
|
||||
switch(bit)
|
||||
{
|
||||
case 0:
|
||||
return HuffmanTree[current.NextZeroIdx];
|
||||
case 1:
|
||||
return HuffmanTree[current.NextOneIdx];
|
||||
}
|
||||
|
||||
throw new Exception("HuffmanEncoder - GetNext - unsupported bit: " + bit);
|
||||
}
|
||||
|
||||
public static HuffmanTableEntry GetEntryForCodePoint(UInt16 codePoint)
|
||||
{
|
||||
return StaticTable[codePoint];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: babdeee42980ac84fb76ef946256b076
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user